Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
67b87f8
Update the README file
harendra-kumar Feb 20, 2026
16ff997
Update cabal file, hide String, Uniq modules
harendra-kumar Feb 20, 2026
504fd7d
Add flake.nix
harendra-kumar Feb 20, 2026
85de64c
Replace Switch with Bool
harendra-kumar Feb 20, 2026
9a7eb5a
Break FileTest module and overhaul it
harendra-kumar Feb 20, 2026
4293a45
Add the ability to pass FilePath in predicates
harendra-kumar Mar 4, 2026
e614996
Rename apply to testGeneral
harendra-kumar Mar 4, 2026
03db365
Rename predicate etc to withStatus etc.
harendra-kumar Mar 4, 2026
a0fef08
Fix doesExist not checking the status
harendra-kumar Apr 5, 2026
8549c2d
Swap the order of arguments in ComparedTo preds
harendra-kumar Mar 5, 2026
f892a58
Fix posix isReadable, isWritable, isExecutable
harendra-kumar Mar 4, 2026
4f750fe
Fix the predicates in the Windows module
harendra-kumar Mar 4, 2026
202c0d3
Do not use the deprecated "isExisting" function
harendra-kumar Mar 5, 2026
f5432d4
Expose mode based access checks
harendra-kumar Mar 5, 2026
dfc8372
Expose some covenient size, time combinators
harendra-kumar Mar 6, 2026
bf8cbdd
Make small documentation updates
harendra-kumar Mar 6, 2026
1d4049e
Rename modifiedAfter/before to newerThan/olderThan
harendra-kumar Mar 6, 2026
e88c2fd
Add modifiedSinceLastAccess
harendra-kumar Mar 6, 2026
63fd54e
Add accessTime and metadataChangeTime
harendra-kumar Mar 6, 2026
8c431db
Implement sameFileAs
harendra-kumar Mar 6, 2026
1926893
Add isTerminalFd
harendra-kumar Mar 6, 2026
edc5e9a
Use isWritableByMode in Rm implementation
harendra-kumar Mar 6, 2026
6a55406
Fix rm module: update docs, API compatibility with GNU rm
harendra-kumar Apr 5, 2026
0859d0a
Add a test suite for Rm
harendra-kumar Apr 5, 2026
d0db3ef
Add hie.yaml
harendra-kumar Apr 6, 2026
4507261
Disable streamly master branch build
harendra-kumar Mar 6, 2026
b90ecf0
Update github CI config
harendra-kumar Mar 6, 2026
9f22aa2
Update appveyor.yaml
harendra-kumar Mar 6, 2026
1afde11
Update .packcheck.ignore, add flake, remove stale entries
harendra-kumar Mar 6, 2026
849e603
Remove the use of list "head" partial function
harendra-kumar Apr 6, 2026
35dee67
Fix unused function warning
harendra-kumar Apr 6, 2026
ab7cc98
Add version bounds on Win32
harendra-kumar Apr 6, 2026
c874917
Move docs to extra-doc-files section
harendra-kumar Apr 6, 2026
1340989
Fix doctests in FileTest module
harendra-kumar Apr 7, 2026
f2b31e8
Rename testGeneral to testWithStatus
harendra-kumar Apr 7, 2026
229ff10
Fix the Windows module for FileTest
harendra-kumar Apr 6, 2026
d6b5423
Remove windows unsupported APIs from common module
harendra-kumar Apr 7, 2026
b892495
Shorten the paths on windows
harendra-kumar Apr 7, 2026
48c03cb
Use getTemporaryDirectory for Windows/Posix portability
harendra-kumar Apr 8, 2026
dccf4f4
Use split caching to cache deps even if the later build fails
harendra-kumar Apr 8, 2026
52583f0
Fix removal of dir symlinks on windows
harendra-kumar Apr 10, 2026
bb7e54f
Add some rm idioms
harendra-kumar Apr 11, 2026
06a3c25
Fix removing write-protected files on Windows
harendra-kumar Apr 11, 2026
2d00774
Fix force removal on Windows
harendra-kumar Apr 11, 2026
adbd430
Disable checking of parent dir perms on Windows
harendra-kumar Apr 11, 2026
f8a2f03
Rename doesExist to doesItExist
harendra-kumar Apr 11, 2026
35d0302
Fix the docspec setup in the Sh module
harendra-kumar Apr 12, 2026
6c769d1
Update the tested-with field
harendra-kumar Apr 12, 2026
6c0885e
Fix warnings and enable Werror on Windows
harendra-kumar Apr 12, 2026
9e22c4b
Sync github CI with packcheck
harendra-kumar Apr 19, 2026
3b846be
Update design notes
harendra-kumar Apr 19, 2026
f0e4c6a
Rename Mkdir to MkdirOptions, update docs
harendra-kumar Apr 19, 2026
f8824eb
Fix doc issues
harendra-kumar Apr 19, 2026
e134d3d
Cleanup and doc updates
harendra-kumar Apr 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
577 changes: 470 additions & 107 deletions .github/workflows/haskell.yml

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions .packcheck.ignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
.cirrus.yml
.packcheck.ignore
stack.yaml
.github/workflows/haskell.yml
.gitignore
default.nix
appveyor.yml
benchmark/Main.hs
hie.yaml
cabal.project.d/master
cabal.project.d/master-Werror
cabal.project.d/streamly-0.9.0
cabal.project.d/streamly-0.10.0
flake.lock
flake.nix
packages.nix
stack.yaml
test/Main.hs
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,35 @@
# Shell commands using streams
# Streamly Coreutils

Port useful commands from the GNU `coreutils` to Haskell functions using
streamly.
This repository provides Haskell functions that reimplement common
GNU `coreutils` commands, utilizing the `streamly` library for
efficient, streaming data processing where applicable. The goal is to
offer a functional and highly performant alternative to traditional
shell commands within Haskell applications, enabling complex data
transformations, system programming and scripting using a pure functional
paradigm. Where applicable, the implementation is designed to be
highly concurrent, for example, the `ls` equivalent can list directory
contents concurrently for improved performance.

## Implemented Commands

Currently, this library provides implementations for the
following coreutils-inspired as well as some additional commands:

* Dir traversal: `ls`, `cp -r`, `rm -r`
* Dir modify: `touch`, `ln`, `cp`, `mkdir`, `rm`, `mv`
* File stat: `test`, `stat`, `touch`
* File read/write: `cp`
* Symlink read: `readlink`
* Processes: `cd`, `pwd`, `sleep`
* Environment: `home`
* Text Processing: `cut`, `tail`
* Shell: streaming composition of shell commands
* Paths: `dirname`, `which`

## Important API Notice

**Please be aware that the API of this library is subject to heavy
change in future releases.** This project is under active development,
and function signatures, module organization, and overall design may
evolve significantly. Users should expect breaking changes and plan
accordingly.
66 changes: 30 additions & 36 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# packcheck-0.4.2
# packcheck-0.7.1
# You can use any of the options supported by packcheck as environment
# variables here. See https://github.com/composewell/packcheck for all
# options and their explanation.

branches:
only:
- master
Expand All @@ -14,43 +15,34 @@ environment:
# ------------------------------------------------------------------------
# Common options
# ------------------------------------------------------------------------
# GHC_OPTIONS: "-Werror"
CABAL_REINIT_CONFIG: "y"
LC_ALL: "C.UTF-8"

# ------------------------------------------------------------------------
# How to build
# ------------------------------------------------------------------------
#
GHCUP_VERSION: "0.1.50.2"
GHCVER: "9.14.1"
#CABALVER: "3.10.3.0"

# ------------------------------------------------------------------------
# What to build
# ------------------------------------------------------------------------
# DISABLE_TEST: "y"
# DISABLE_BENCH: "y"
# DISABLE_DOCS: "y"
DISABLE_SDIST_BUILD: "y"
DISABLE_DIST_CHECKS: "y"
ENABLE_INSTALL: "y"

# ------------------------------------------------------------------------
# stack options
# ------------------------------------------------------------------------
# Note requiring a specific version of stack using STACKVER may fail due to
# github API limit while checking and upgrading/downgrading to the specific
# version.
#STACKVER: "1.6.5"
STACK_UPGRADE: "y"
RESOLVER: "lts-22.33"
STACK_ROOT: "c:\\sr"
STACK_YAML: "stack.yaml"
# DISABLE_DIST_CHECKS: "y"
# DISABLE_SDIST_BUILD: "y"
# Note: these require the "diff" utility.
# DISABLE_SDIST_GIT_CHECK: "y"
DISABLE_SDIST_PROJECT_CHECK: "y"

# ------------------------------------------------------------------------
# cabal options
# ------------------------------------------------------------------------
CABAL_CHECK_RELAX: "y"
CABAL_HACKAGE_MIRROR: "hackage.haskell.org:http://hackage.fpcomplete.com"

# ------------------------------------------------------------------------
# Where to find the required tools
# ------------------------------------------------------------------------
PATH: "%PATH%;%APPDATA%\\local\\bin"
LOCAL_BIN: "%APPDATA%\\local\\bin"
#CABAL_PROJECT: "cabal.project"

# ------------------------------------------------------------------------
# Location of packcheck.sh (the shell script invoked to perform CI tests ).
Expand All @@ -63,31 +55,33 @@ environment:
# If you have not committed packcheck.sh in your repo at PACKCHECK_LOCAL_PATH
# then it is automatically pulled from this URL.
PACKCHECK_GITHUB_URL: "https://raw.githubusercontent.com/composewell/packcheck"
PACKCHECK_GITHUB_COMMIT: "a68b7b9c7c21eef8ed273e67030efb1d4fec027c"
PACKCHECK_GITHUB_COMMIT: "b91dd7daf50d91047ca8c1cce47e8634e34dfcbc"

# Override the temp directory to avoid sed escaping issues
# See https://github.com/haskell/cabal/issues/5386
TMP: "c:\\tmp"

# Bump the -> version to clear the cache
# packcheck uses "%APPDATA%\\local" to install tools like hlint etc.
# cabal may use "%APPDATA%\\cabal" or "c:\\cabal"
# ghcup may use "%APPDATA%\\ghcup" or "c:\\ghcup"
cache:
- "%STACK_ROOT%"
- "%LOCAL_BIN%"
- "%APPDATA%\\local\\bin -> v1"
- "%APPDATA%\\cabal"
- "%APPDATA%\\ghc"
# - "%LOCALAPPDATA%\\Programs\\stack"
- "%LOCALAPPDATA%\\cabal"
- "C:\\ghcup"
- "C:\\cabal"

# Folder where the repository is cloned.
clone_folder: "c:\\pkg"
build: off

before_test:
- if not exist %PACKCHECK_LOCAL_PATH% curl -sSkL -o%PACKCHECK_LOCAL_PATH% %PACKCHECK_GITHUB_URL%/%PACKCHECK_GITHUB_COMMIT%/packcheck.sh
- if not exist %LOCAL_BIN% mkdir %LOCAL_BIN%
- where stack.exe || curl -sSkL -ostack.zip http://www.stackage.org/stack/windows-x86_64 && 7z x stack.zip stack.exe && move stack.exe %LOCAL_BIN%
- if defined STACKVER (stack upgrade --binary-only --binary-version %STACKVER%) else (stack upgrade --binary-only || ver > nul)
- stack --version
- if not exist %PACKCHECK_LOCAL_PATH% curl --fail -sSL -o%PACKCHECK_LOCAL_PATH% %PACKCHECK_GITHUB_URL%/%PACKCHECK_GITHUB_COMMIT%/packcheck.sh

test_script:
- stack setup > nul
- for /f "usebackq tokens=*" %%i in (`where 7z.exe`) do set PATH7Z=%%i\..
- for /f "usebackq tokens=*" %%i in (`where git.exe`) do set PATHGIT=%%i\..
- chcp 65001 && stack exec bash -- -c "chmod +x %PACKCHECK_LOCAL_PATH%; %PACKCHECK_LOCAL_PATH% stack PATH=/usr/bin:\"%PATH7Z%\":\"%PATHGIT%\""
- for /f "usebackq tokens=*" %%i in (`where curl.exe`) do set PATHCURL=%%i\..
- chcp 65001
- bash %PACKCHECK_LOCAL_PATH% cabal PATH="/usr/bin:%PATH7Z%:%PATHGIT%:%PATHCURL%"
20 changes: 10 additions & 10 deletions cabal.project.d/master-Werror
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ packages: .
package streamly-coreutils
ghc-options: -Werror

source-repository-package
type: git
location: https://github.com/composewell/streamly.git
tag: master

source-repository-package
type: git
location: https://github.com/composewell/streamly.git
tag: master
subdir: core
-- source-repository-package
-- type: git
-- location: https://github.com/composewell/streamly.git
-- tag: master
--
-- source-repository-package
-- type: git
-- location: https://github.com/composewell/streamly.git
-- tag: master
-- subdir: core
69 changes: 62 additions & 7 deletions design/design-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,71 @@ sugegsted above.
We can basically wrap the Haskell functions in CLI parser and an output
renderer to make a Unix like utility.

## Module structure
## Module Naming

We can either keep each utility in its own module or bundle them into a smaller
set of modules. The latter would help reduce the imports. But it may be easier
to remember the module names if they are based on the utility name.

We should keep the naming non-conflicting such that if some wants to bundle
them in a single module and re-export it should be possible. In fact we can
provide a single coreutils module exporting everything.

Each command must have its own module e.g. Coreutils.Cp for the cp command.

The command options could be called "Options" in each module but then
we may not be able to export all commands from a single module which
is desirable. So we can choose to call the options of a command by the
same name as the command itself e.g. "Cp". We would not expose the Cp
structure outside the module but we would still need to export the
type. Using a unique type for each command would help us use it
we may not be able to export all commands from a single module which is
desirable. So we can choose to call the options of a command by the same
name as the command itself e.g. "CpOptions". We would not expose the
CpOptions record outside the module but we would still need to export
the type. Using a unique type for each command would help us use it
unqualified.

The command runner would be called "cp" so it would be "cp :: Cp -> IO ()".
The command runner would be called "cp" so it would be "cp :: CpOptions -> IO ()".

## API for utilities

There are two ways of writing the API, (1) use separate functions
for each combination of options e.g. rm, rmForce, rmRecursive,
rmRecursiveForce (2) use options to control the behavior e.g. rm id,
rm force, rm recursive, rm (recursive . force). We choose the latter
because the number of functions can explode with more combinations,
and in the latter we need to remember only one function and use any
combination of options. That keeps the organization better.

## Directory Operations

Summary of important dir and file operations:
* dir read operations (readdir, readdir with stat)
* dir modify operations (file create, delete, rename)
* file inode read operations:
* stat, lstat, fstatat
* file inode modify operations:
* permissions (chmod)
* ownership (chown)
* timestamps (utime)
* file access control (access, acls, getxattr)

## Directory Package

The directory package provides a potpourri of operations:
* dir modify operations (mkdir, rmdir, rm, recursive rm, mv, cp),
* readdir operations, recursive traversal (listDirectory, findFiles),
* file stat (getFileSize, permissions, timestamps, isSymlink),
* symlink read,
* path (makeAbsolute, canonicalize),
* process environment (pwd, cd, PATH, findExecutable),
* OS environment (home, xdg, temp).

## Classification

We can define the following categories based on underlying filesystem
functionality:
* readdir: FileSystem.DirIO module
* Dir traversal: ls, find, cp -r, rm -r
* Dir modify: mkdir, rmdir, rm, mv, cp
* stat: test, testl
* symlink read: readlink
* path: makeAbsolute, canonicalize
* environment: pwd, cd, findInPATH, home, xdg, temp
* access control: acls, getxattr
2 changes: 0 additions & 2 deletions design/proposal.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ and output just the way Unix commands work, reading from stdin and writing to
stdout and composed with pipes. We should be able to chain the functions in a
stream processing pipeline.

Use the Haskell `path` package for file system path representation where needed.

## References

* https://github.com/coreutils/coreutils
Expand Down
88 changes: 88 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
description = "Streamly Coreutils Development Environment";

inputs = {
basepkgs.url = "git+ssh://git@github.com/composewell/streamly-packages?rev=76420910d9c74e5fc1d92d680499c97e4f94e873";
nixpkgs.follows = "basepkgs/nixpkgs";
nixpkgs-darwin.follows = "basepkgs/nixpkgs-darwin";
};

outputs = { self, nixpkgs, nixpkgs-darwin, basepkgs }:
basepkgs.nixpack.mkOutputs {
inherit nixpkgs nixpkgs-darwin basepkgs;
name = "streamly-coreutils";
sources = basepkgs.nixpack.lib.localSource "streamly-coreutils" ./.;
#packages = basepkgs.nixpack.lib.devPackage "streamly-coreutils";
#sources = import ./sources.nix;
packages = import ./packages.nix;
};
}
Loading
Loading