diff --git a/.changeset/wet-flies-march.md b/.changeset/wet-flies-march.md
new file mode 100644
index 0000000..914bb86
--- /dev/null
+++ b/.changeset/wet-flies-march.md
@@ -0,0 +1,5 @@
+---
+"@effect/cli": patch
+---
+
+add support for auto-generating completions for a cli program
diff --git a/.vscode/snippets.code-snippets b/.vscode/snippets.code-snippets
index 87ad337..5bad1bf 100644
--- a/.vscode/snippets.code-snippets
+++ b/.vscode/snippets.code-snippets
@@ -1,5 +1,5 @@
{
- "Gen Function $": {
+ "Gen Function _": {
"prefix": "gg",
"body": [
"Effect.gen(function*(_) {",
@@ -8,7 +8,7 @@
],
"description": "Generator Function with a _ parameter"
},
- "Gen Yield $": {
+ "Gen Yield _": {
"prefix": "yy",
"body": [
"yield* _($0)"
diff --git a/package.json b/package.json
index e817af7..d34cd43 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"coverage": "vitest run --coverage"
},
"peerDependencies": {
+ "@effect/platform": "^0.27.3",
"@effect/printer": "^0.22.1",
"@effect/printer-ansi": "^0.22.1",
"@effect/schema": "^0.47.2",
@@ -52,21 +53,23 @@
},
"devDependencies": {
"@babel/cli": "^7.23.0",
- "@babel/core": "^7.23.0",
+ "@babel/core": "^7.23.3",
"@babel/plugin-transform-export-namespace-from": "^7.22.11",
- "@babel/plugin-transform-modules-commonjs": "^7.23.0",
+ "@babel/plugin-transform-modules-commonjs": "^7.23.3",
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.2",
- "@effect/build-utils": "^0.3.1",
- "@effect/docgen": "^0.3.0",
+ "@effect/build-utils": "^0.4.0",
+ "@effect/docgen": "^0.3.1",
"@effect/eslint-plugin": "^0.1.2",
"@effect/language-service": "^0.0.21",
+ "@effect/platform": "^0.28.0",
+ "@effect/platform-node": "^0.29.0",
"@effect/printer": "^0.22.1",
"@effect/printer-ansi": "^0.22.1",
- "@effect/schema": "^0.47.2",
+ "@effect/schema": "^0.47.3",
"@preconstruct/cli": "^2.8.1",
- "@types/chai": "^4.3.9",
- "@types/node": "^20.8.10",
+ "@types/chai": "^4.3.10",
+ "@types/node": "^20.9.0",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@vitejs/plugin-react": "^4.1.1",
@@ -85,7 +88,7 @@
"madge": "^6.1.0",
"prettier": "^3.0.3",
"stackframe": "^1.3.4",
- "tsx": "^3.14.0",
+ "tsx": "^4.1.0",
"typescript": "^5.2.2",
"vite": "^4.5.0",
"vitest": "^0.34.6"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 44c2d0e..cfa6a8c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7,16 +7,16 @@ settings:
devDependencies:
'@babel/cli':
specifier: ^7.23.0
- version: 7.23.0(@babel/core@7.23.2)
+ version: 7.23.0(@babel/core@7.23.3)
'@babel/core':
- specifier: ^7.23.0
- version: 7.23.2
+ specifier: ^7.23.3
+ version: 7.23.3
'@babel/plugin-transform-export-namespace-from':
specifier: ^7.22.11
- version: 7.23.3(@babel/core@7.23.2)
+ version: 7.23.3(@babel/core@7.23.3)
'@babel/plugin-transform-modules-commonjs':
- specifier: ^7.23.0
- version: 7.23.0(@babel/core@7.23.2)
+ specifier: ^7.23.3
+ version: 7.23.3(@babel/core@7.23.3)
'@changesets/changelog-github':
specifier: ^0.4.8
version: 0.4.8
@@ -24,17 +24,23 @@ devDependencies:
specifier: ^2.26.2
version: 2.26.2
'@effect/build-utils':
- specifier: ^0.3.1
- version: 0.3.1
+ specifier: ^0.4.0
+ version: 0.4.0
'@effect/docgen':
- specifier: ^0.3.0
- version: 0.3.0(fast-check@3.13.2)(tsx@3.14.0)(typescript@5.2.2)
+ specifier: ^0.3.1
+ version: 0.3.1(fast-check@3.13.2)(tsx@4.1.0)(typescript@5.2.2)
'@effect/eslint-plugin':
specifier: ^0.1.2
version: 0.1.2
'@effect/language-service':
specifier: ^0.0.21
version: 0.0.21
+ '@effect/platform':
+ specifier: ^0.28.0
+ version: 0.28.0(@effect/schema@0.47.3)(effect@2.0.0-next.54)
+ '@effect/platform-node':
+ specifier: ^0.29.0
+ version: 0.29.0(@effect/schema@0.47.3)(effect@2.0.0-next.54)
'@effect/printer':
specifier: ^0.22.1
version: 0.22.1(@effect/typeclass@0.14.1)(effect@2.0.0-next.54)
@@ -42,17 +48,17 @@ devDependencies:
specifier: ^0.22.1
version: 0.22.1(@effect/typeclass@0.14.1)(effect@2.0.0-next.54)
'@effect/schema':
- specifier: ^0.47.2
- version: 0.47.2(effect@2.0.0-next.54)(fast-check@3.13.2)
+ specifier: ^0.47.3
+ version: 0.47.3(effect@2.0.0-next.54)(fast-check@3.13.2)
'@preconstruct/cli':
specifier: ^2.8.1
version: 2.8.1
'@types/chai':
- specifier: ^4.3.9
- version: 4.3.9
+ specifier: ^4.3.10
+ version: 4.3.10
'@types/node':
- specifier: ^20.8.10
- version: 20.8.10
+ specifier: ^20.9.0
+ version: 20.9.0
'@typescript-eslint/eslint-plugin':
specifier: ^6.10.0
version: 6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.2.2)
@@ -67,7 +73,7 @@ devDependencies:
version: 0.34.6(vitest@0.34.6)
babel-plugin-annotate-pure-calls:
specifier: ^0.4.0
- version: 0.4.0(@babel/core@7.23.2)
+ version: 0.4.0(@babel/core@7.23.3)
effect:
specifier: 2.0.0-next.54
version: 2.0.0-next.54
@@ -108,14 +114,14 @@ devDependencies:
specifier: ^1.3.4
version: 1.3.4
tsx:
- specifier: ^3.14.0
- version: 3.14.0
+ specifier: ^4.1.0
+ version: 4.1.0
typescript:
specifier: ^5.2.2
version: 5.2.2
vite:
specifier: ^4.5.0
- version: 4.5.0(@types/node@20.8.10)
+ version: 4.5.0(@types/node@20.9.0)
vitest:
specifier: ^0.34.6
version: 0.34.6
@@ -135,14 +141,14 @@ packages:
'@jridgewell/trace-mapping': 0.3.20
dev: true
- /@babel/cli@7.23.0(@babel/core@7.23.2):
+ /@babel/cli@7.23.0(@babel/core@7.23.3):
resolution: {integrity: sha512-17E1oSkGk2IwNILM4jtfAvgjt+ohmpfBky8aLerUfYZhiPNg7ca+CRCxZn8QDxwNhV/upsc2VHBCqGFIR+iBfA==}
engines: {node: '>=6.9.0'}
hasBin: true
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.23.2
+ '@babel/core': 7.23.3
'@jridgewell/trace-mapping': 0.3.20
commander: 4.1.1
convert-source-map: 2.0.0
@@ -163,25 +169,25 @@ packages:
chalk: 2.4.2
dev: true
- /@babel/compat-data@7.23.2:
- resolution: {integrity: sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==}
+ /@babel/compat-data@7.23.3:
+ resolution: {integrity: sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==}
engines: {node: '>=6.9.0'}
dev: true
- /@babel/core@7.23.2:
- resolution: {integrity: sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==}
+ /@babel/core@7.23.3:
+ resolution: {integrity: sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==}
engines: {node: '>=6.9.0'}
dependencies:
'@ampproject/remapping': 2.2.1
'@babel/code-frame': 7.22.13
- '@babel/generator': 7.23.0
+ '@babel/generator': 7.23.3
'@babel/helper-compilation-targets': 7.22.15
- '@babel/helper-module-transforms': 7.23.0(@babel/core@7.23.2)
+ '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3)
'@babel/helpers': 7.23.2
- '@babel/parser': 7.23.0
+ '@babel/parser': 7.23.3
'@babel/template': 7.22.15
- '@babel/traverse': 7.23.2
- '@babel/types': 7.23.0
+ '@babel/traverse': 7.23.3
+ '@babel/types': 7.23.3
convert-source-map: 2.0.0
debug: 4.3.4
gensync: 1.0.0-beta.2
@@ -209,11 +215,21 @@ packages:
jsesc: 2.5.2
dev: true
+ /@babel/generator@7.23.3:
+ resolution: {integrity: sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.3
+ '@jridgewell/gen-mapping': 0.3.3
+ '@jridgewell/trace-mapping': 0.3.20
+ jsesc: 2.5.2
+ dev: true
+
/@babel/helper-compilation-targets@7.22.15:
resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/compat-data': 7.23.2
+ '@babel/compat-data': 7.23.3
'@babel/helper-validator-option': 7.22.15
browserslist: 4.22.1
lru-cache: 5.1.1
@@ -247,13 +263,13 @@ packages:
'@babel/types': 7.23.0
dev: true
- /@babel/helper-module-transforms@7.23.0(@babel/core@7.23.2):
- resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==}
+ /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.3):
+ resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
- '@babel/core': 7.23.2
+ '@babel/core': 7.23.3
'@babel/helper-environment-visitor': 7.22.20
'@babel/helper-module-imports': 7.22.15
'@babel/helper-simple-access': 7.22.5
@@ -270,7 +286,7 @@ packages:
resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.23.0
+ '@babel/types': 7.23.3
dev: true
/@babel/helper-split-export-declaration@7.22.6:
@@ -300,8 +316,8 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.22.15
- '@babel/traverse': 7.23.2
- '@babel/types': 7.23.0
+ '@babel/traverse': 7.23.3
+ '@babel/types': 7.23.3
transitivePeerDependencies:
- supports-color
dev: true
@@ -323,55 +339,63 @@ packages:
'@babel/types': 7.23.0
dev: true
- /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.23.2):
+ /@babel/parser@7.23.3:
+ resolution: {integrity: sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+ dependencies:
+ '@babel/types': 7.23.3
+ dev: true
+
+ /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.23.3):
resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.23.2
+ '@babel/core': 7.23.3
'@babel/helper-plugin-utils': 7.22.5
dev: true
- /@babel/plugin-transform-export-namespace-from@7.23.3(@babel/core@7.23.2):
+ /@babel/plugin-transform-export-namespace-from@7.23.3(@babel/core@7.23.3):
resolution: {integrity: sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.23.2
+ '@babel/core': 7.23.3
'@babel/helper-plugin-utils': 7.22.5
- '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.2)
+ '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.3)
dev: true
- /@babel/plugin-transform-modules-commonjs@7.23.0(@babel/core@7.23.2):
- resolution: {integrity: sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==}
+ /@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.23.3):
+ resolution: {integrity: sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.23.2
- '@babel/helper-module-transforms': 7.23.0(@babel/core@7.23.2)
+ '@babel/core': 7.23.3
+ '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3)
'@babel/helper-plugin-utils': 7.22.5
'@babel/helper-simple-access': 7.22.5
dev: true
- /@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.23.2):
+ /@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.23.3):
resolution: {integrity: sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.23.2
+ '@babel/core': 7.23.3
'@babel/helper-plugin-utils': 7.22.5
dev: true
- /@babel/plugin-transform-react-jsx-source@7.22.5(@babel/core@7.23.2):
+ /@babel/plugin-transform-react-jsx-source@7.22.5(@babel/core@7.23.3):
resolution: {integrity: sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.23.2
+ '@babel/core': 7.23.3
'@babel/helper-plugin-utils': 7.22.5
dev: true
@@ -394,8 +418,8 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.22.13
- '@babel/parser': 7.23.0
- '@babel/types': 7.23.0
+ '@babel/parser': 7.23.3
+ '@babel/types': 7.23.3
dev: true
/@babel/traverse@7.23.2:
@@ -416,6 +440,24 @@ packages:
- supports-color
dev: true
+ /@babel/traverse@7.23.3:
+ resolution: {integrity: sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.22.13
+ '@babel/generator': 7.23.3
+ '@babel/helper-environment-visitor': 7.22.20
+ '@babel/helper-function-name': 7.23.0
+ '@babel/helper-hoist-variables': 7.22.5
+ '@babel/helper-split-export-declaration': 7.22.6
+ '@babel/parser': 7.23.3
+ '@babel/types': 7.23.3
+ debug: 4.3.4
+ globals: 11.12.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@babel/types@7.23.0:
resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==}
engines: {node: '>=6.9.0'}
@@ -425,6 +467,15 @@ packages:
to-fast-properties: 2.0.0
dev: true
+ /@babel/types@7.23.3:
+ resolution: {integrity: sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-string-parser': 7.22.5
+ '@babel/helper-validator-identifier': 7.22.20
+ to-fast-properties: 2.0.0
+ dev: true
+
/@bcoe/v8-coverage@0.2.3:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
dev: true
@@ -648,22 +699,22 @@ packages:
resolution: {integrity: sha512-rPwwm/RrFIolz6xHa8Kzpshuwpe+xu/XcEw9iUmRF2tnyIwxxaW7XoFKaQ+GfPju81cKpH4vJeq7/2IizKvyjg==}
dev: true
- /@effect/build-utils@0.3.1:
- resolution: {integrity: sha512-MoyN7zj3Y068rV08SDaOSAyoKXjOSgG2C4O1fHuDmihnti5XLNt0rpCORbPhQhIwzFp14YLYeZihp3ypDmWlQQ==}
+ /@effect/build-utils@0.4.0:
+ resolution: {integrity: sha512-1dL2qgzfGECCvMZ2BXhNjAdSDsKS9zpNJne8caxXRI8FtjFNPuj3HYOhYZVmaYfSvzwlC+p9nPBTRaiczoLe8w==}
engines: {node: '>=16.17.1'}
hasBin: true
dev: true
- /@effect/docgen@0.3.0(fast-check@3.13.2)(tsx@3.14.0)(typescript@5.2.2):
- resolution: {integrity: sha512-QxhLddKwnxZ/BA/1iZGs53yuKorCF6BJmPbs0XXliwbOzgrSIjrSsS+satyis3ChzrtQ3vY1xHNwB6hgSifauA==}
+ /@effect/docgen@0.3.1(fast-check@3.13.2)(tsx@4.1.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-4Lc+fKBkxz9rC+XjI3buB3bSNkiBaYT4cxynxt+G4n0pP+BDsztn75Bl+tKlGCTJUROhD2bhadmn3R2ydRslqA==}
engines: {node: '>=16.17.1'}
hasBin: true
peerDependencies:
tsx: ^3.14.0
typescript: ^5.2.2
dependencies:
- '@effect/platform-node': 0.28.2(@effect/schema@0.47.2)(effect@2.0.0-next.54)
- '@effect/schema': 0.47.2(effect@2.0.0-next.54)(fast-check@3.13.2)
+ '@effect/platform-node': 0.28.4(@effect/schema@0.47.3)(effect@2.0.0-next.54)
+ '@effect/schema': 0.47.3(effect@2.0.0-next.54)(fast-check@3.13.2)
chalk: 5.3.0
doctrine: 3.0.0
effect: 2.0.0-next.54
@@ -672,7 +723,7 @@ packages:
prettier: 3.0.3
ts-morph: 20.0.0
tsconfck: 3.0.0(typescript@5.2.2)
- tsx: 3.14.0
+ tsx: 4.1.0
typescript: 5.2.2
transitivePeerDependencies:
- fast-check
@@ -690,13 +741,12 @@ packages:
resolution: {integrity: sha512-e8vfKbjnbYiyneBincEFS0tzXluopGK77OkVFbPRtUbNDS5tJfb+jiwOQEiqASDsadcZmd+9J9+Q6v/z7GuN2g==}
dev: true
- /@effect/platform-node@0.28.2(@effect/schema@0.47.2)(effect@2.0.0-next.54):
- resolution: {integrity: sha512-KU+ta/gantpbD5lnZBcgTuu7lgxeHQCy0dvvY2iieOG2UMzo8yogBLTTrjlpcbruFsriXEkF5+/y2uzyb17SDg==}
- engines: {node: '>=18.0.0'}
+ /@effect/platform-node@0.28.4(@effect/schema@0.47.3)(effect@2.0.0-next.54):
+ resolution: {integrity: sha512-0AK4PPfKf2z1pZHMbdWth58A54rfn8C7QSBToDooQ0jft/Ssj1Ys0RIuwb49ZUKGGY+XNLzkBEeJYzdMRdKNIQ==}
peerDependencies:
effect: 2.0.0-next.54
dependencies:
- '@effect/platform': 0.27.2(@effect/schema@0.47.2)(effect@2.0.0-next.54)
+ '@effect/platform': 0.27.4(@effect/schema@0.47.3)(effect@2.0.0-next.54)
busboy: 1.6.0
effect: 2.0.0-next.54
mime: 3.0.0
@@ -704,13 +754,38 @@ packages:
- '@effect/schema'
dev: true
- /@effect/platform@0.27.2(@effect/schema@0.47.2)(effect@2.0.0-next.54):
- resolution: {integrity: sha512-Ya8clQi5FWdlrsyPfhsh4zldvDM+NZe50QPANVXByqOpAWTw3XgPNRTMom/Yx464+VTORN6Pc4q82THWat1iOA==}
+ /@effect/platform-node@0.29.0(@effect/schema@0.47.3)(effect@2.0.0-next.54):
+ resolution: {integrity: sha512-P5kA72swAni4D38eI5eovGIve0fhMLpWJf/IfMwMbfhGbNRo8cZ+/Q0KYbmMn9ilp2fxdEw6MEX9hu/cqYw56A==}
+ peerDependencies:
+ effect: 2.0.0-next.54
+ dependencies:
+ '@effect/platform': 0.28.0(@effect/schema@0.47.3)(effect@2.0.0-next.54)
+ busboy: 1.6.0
+ effect: 2.0.0-next.54
+ mime: 3.0.0
+ transitivePeerDependencies:
+ - '@effect/schema'
+ dev: true
+
+ /@effect/platform@0.27.4(@effect/schema@0.47.3)(effect@2.0.0-next.54):
+ resolution: {integrity: sha512-SpxQMN4WqV+alpKmjj9Hq8UP15V4GsksADvmxmbKk4Yu2ocrxYlcIUlgIGM0u9WxF34dEjl0skbQoAMo2gewbQ==}
+ peerDependencies:
+ '@effect/schema': ^0.47.1
+ effect: 2.0.0-next.54
+ dependencies:
+ '@effect/schema': 0.47.3(effect@2.0.0-next.54)(fast-check@3.13.2)
+ effect: 2.0.0-next.54
+ find-my-way: 7.7.0
+ path-browserify: 1.0.1
+ dev: true
+
+ /@effect/platform@0.28.0(@effect/schema@0.47.3)(effect@2.0.0-next.54):
+ resolution: {integrity: sha512-zPtn6PMFGJ+hHtCVC9vKSEo4ZSNtMoQhHaC2k+6TNpCT+xaiKTYmwuEImuHf1qdWSQ0py2m5ivo/8kR5ryoeeg==}
peerDependencies:
'@effect/schema': ^0.47.1
effect: 2.0.0-next.54
dependencies:
- '@effect/schema': 0.47.2(effect@2.0.0-next.54)(fast-check@3.13.2)
+ '@effect/schema': 0.47.3(effect@2.0.0-next.54)(fast-check@3.13.2)
effect: 2.0.0-next.54
find-my-way: 7.7.0
path-browserify: 1.0.1
@@ -737,8 +812,8 @@ packages:
effect: 2.0.0-next.54
dev: true
- /@effect/schema@0.47.2(effect@2.0.0-next.54)(fast-check@3.13.2):
- resolution: {integrity: sha512-AwZg9c/NCD2rKEQuWjQUwFMWBi7xokJyL3jid5jtiLB7kjmrtRnHBxleeOEVoSrdBMJu6yXle3HvdHXefyA2jQ==}
+ /@effect/schema@0.47.3(effect@2.0.0-next.54)(fast-check@3.13.2):
+ resolution: {integrity: sha512-n8dYPhKqiP6h11ABCljv7EgzX+QfIBAippvsCHcgxmbvtWpDww3QnZ96RRjCwaHYM22ypqbYPGbvBqgNNWTpXg==}
peerDependencies:
effect: 2.0.0-next.54
fast-check: ^3.13.2
@@ -1040,7 +1115,7 @@ packages:
dependencies:
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
- '@types/node': 20.8.10
+ '@types/node': 20.9.0
'@types/yargs': 15.0.17
chalk: 4.1.2
dev: true
@@ -1148,7 +1223,7 @@ packages:
hasBin: true
dependencies:
'@babel/code-frame': 7.22.13
- '@babel/core': 7.23.2
+ '@babel/core': 7.23.3
'@babel/helper-module-imports': 7.22.15
'@babel/runtime': 7.23.2
'@preconstruct/hook': 0.4.0
@@ -1192,8 +1267,8 @@ packages:
/@preconstruct/hook@0.4.0:
resolution: {integrity: sha512-a7mrlPTM3tAFJyz43qb4pPVpUx8j8TzZBFsNFqcKcE/sEakNXRlQAuCT4RGZRf9dQiiUnBahzSIWawU4rENl+Q==}
dependencies:
- '@babel/core': 7.23.2
- '@babel/plugin-transform-modules-commonjs': 7.23.0(@babel/core@7.23.2)
+ '@babel/core': 7.23.3
+ '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.3)
pirates: 4.0.6
source-map-support: 0.5.21
transitivePeerDependencies:
@@ -1317,11 +1392,11 @@ packages:
/@types/chai-subset@1.3.3:
resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
dependencies:
- '@types/chai': 4.3.9
+ '@types/chai': 4.3.10
dev: true
- /@types/chai@4.3.9:
- resolution: {integrity: sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==}
+ /@types/chai@4.3.10:
+ resolution: {integrity: sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg==}
dev: true
/@types/estree@0.0.39:
@@ -1382,8 +1457,8 @@ packages:
resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==}
dev: true
- /@types/node@20.8.10:
- resolution: {integrity: sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==}
+ /@types/node@20.9.0:
+ resolution: {integrity: sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==}
dependencies:
undici-types: 5.26.5
dev: true
@@ -1395,7 +1470,7 @@ packages:
/@types/resolve@1.17.1:
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
dependencies:
- '@types/node': 20.8.10
+ '@types/node': 20.9.0
dev: true
/@types/semver@7.5.3:
@@ -1690,12 +1765,12 @@ packages:
peerDependencies:
vite: ^4.2.0
dependencies:
- '@babel/core': 7.23.2
- '@babel/plugin-transform-react-jsx-self': 7.22.5(@babel/core@7.23.2)
- '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.23.2)
+ '@babel/core': 7.23.3
+ '@babel/plugin-transform-react-jsx-self': 7.22.5(@babel/core@7.23.3)
+ '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.23.3)
'@types/babel__core': 7.20.3
react-refresh: 0.14.0
- vite: 4.5.0(@types/node@20.8.10)
+ vite: 4.5.0(@types/node@20.9.0)
transitivePeerDependencies:
- supports-color
dev: true
@@ -1960,12 +2035,12 @@ packages:
engines: {node: '>= 0.4'}
dev: true
- /babel-plugin-annotate-pure-calls@0.4.0(@babel/core@7.23.2):
+ /babel-plugin-annotate-pure-calls@0.4.0(@babel/core@7.23.3):
resolution: {integrity: sha512-oi4M/PWUJOU9ZyRGoPTfPMqdyMp06jbJAomd3RcyYuzUtBOddv98BqLm96Lucpi2QFoQHkdGQt0ACvw7VzVEQA==}
peerDependencies:
'@babel/core': ^6.0.0-0 || 7.x
dependencies:
- '@babel/core': 7.23.2
+ '@babel/core': 7.23.3
dev: true
/balanced-match@1.0.2:
@@ -2030,7 +2105,7 @@ packages:
hasBin: true
dependencies:
caniuse-lite: 1.0.30001561
- electron-to-chromium: 1.4.576
+ electron-to-chromium: 1.4.580
node-releases: 2.0.13
update-browserslist-db: 1.0.13(browserslist@4.22.1)
dev: true
@@ -2646,8 +2721,8 @@ packages:
resolution: {integrity: sha512-qROhKMxlm6fpa90YRfWSgKeelDfhaDq2igPK+pIKupGehiCnZH4vd2qrY71HVZ10qZgXxh0VXpGyDQxJC+EQqw==}
dev: true
- /electron-to-chromium@1.4.576:
- resolution: {integrity: sha512-yXsZyXJfAqzWk1WKryr0Wl0MN2D47xodPvEEwlVePBnhU5E7raevLQR+E6b9JAD3GfL/7MbAL9ZtWQQPcLx7wA==}
+ /electron-to-chromium@1.4.580:
+ resolution: {integrity: sha512-T5q3pjQon853xxxHUq3ZP68ZpvJHuSMY2+BZaW3QzjS4HvNuvsMmZ/+lU+nCrftre1jFZ+OSlExynXWBihnXzw==}
dev: true
/emoji-regex@8.0.0:
@@ -2944,7 +3019,7 @@ packages:
resolution: {integrity: sha512-Sy5nJ7tMahHWygM02w2gAO70MX6Lp0ZK0PD9kMpPPGtoQhyS2n1oN7s9zLpDx5pmFDf3woj6LadqztNpJ5RepQ==}
engines: {node: '>=12.0.0'}
dependencies:
- '@babel/core': 7.23.2
+ '@babel/core': 7.23.3
'@babel/generator': 7.12.17
'@babel/parser': 7.23.0
'@babel/traverse': 7.23.2
@@ -4166,7 +4241,7 @@ packages:
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
engines: {node: '>= 10.13.0'}
dependencies:
- '@types/node': 20.8.10
+ '@types/node': 20.9.0
merge-stream: 2.0.0
supports-color: 7.2.0
dev: true
@@ -6000,8 +6075,9 @@ packages:
typescript: 5.2.2
dev: true
- /tsx@3.14.0:
- resolution: {integrity: sha512-xHtFaKtHxM9LOklMmJdI3BEnQq/D5F73Of2E1GDrITi9sgoVkvIsrQUTY1G8FlmGtA+awCI4EBlTRRYxkL2sRg==}
+ /tsx@4.1.0:
+ resolution: {integrity: sha512-u4l17Yd63Wsk2fzNn1wZCmcS9kwJ/2ysl7wuoVggv2hd3NjLA5JQPpyJMXoWSXOwOvoQUzNcu/sf/35HEsnXsg==}
+ engines: {node: '>=18.0.0'}
hasBin: true
dependencies:
esbuild: 0.18.20
@@ -6189,7 +6265,7 @@ packages:
spdx-expression-parse: 3.0.1
dev: true
- /vite-node@0.34.6(@types/node@20.8.10):
+ /vite-node@0.34.6(@types/node@20.9.0):
resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==}
engines: {node: '>=v14.18.0'}
hasBin: true
@@ -6199,7 +6275,7 @@ packages:
mlly: 1.4.2
pathe: 1.1.1
picocolors: 1.0.0
- vite: 4.5.0(@types/node@20.8.10)
+ vite: 4.5.0(@types/node@20.9.0)
transitivePeerDependencies:
- '@types/node'
- less
@@ -6211,7 +6287,7 @@ packages:
- terser
dev: true
- /vite@4.5.0(@types/node@20.8.10):
+ /vite@4.5.0(@types/node@20.9.0):
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
@@ -6239,7 +6315,7 @@ packages:
terser:
optional: true
dependencies:
- '@types/node': 20.8.10
+ '@types/node': 20.9.0
esbuild: 0.18.20
postcss: 8.4.31
rollup: 3.29.4
@@ -6278,9 +6354,9 @@ packages:
webdriverio:
optional: true
dependencies:
- '@types/chai': 4.3.9
+ '@types/chai': 4.3.10
'@types/chai-subset': 1.3.3
- '@types/node': 20.8.10
+ '@types/node': 20.9.0
'@vitest/expect': 0.34.6
'@vitest/runner': 0.34.6
'@vitest/snapshot': 0.34.6
@@ -6299,8 +6375,8 @@ packages:
strip-literal: 1.3.0
tinybench: 2.5.1
tinypool: 0.7.0
- vite: 4.5.0(@types/node@20.8.10)
- vite-node: 0.34.6(@types/node@20.8.10)
+ vite: 4.5.0(@types/node@20.9.0)
+ vite-node: 0.34.6(@types/node@20.9.0)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
diff --git a/src/Args.ts b/src/Args.ts
index f4982f4..50617bb 100644
--- a/src/Args.ts
+++ b/src/Args.ts
@@ -1,6 +1,7 @@
/**
* @since 1.0.0
*/
+import type { FileSystem } from "@effect/platform/FileSystem"
import type { Effect } from "effect/Effect"
import type { Either } from "effect/Either"
import type { Option } from "effect/Option"
@@ -10,6 +11,8 @@ import type { CliConfig } from "./CliConfig.js"
import type { HelpDoc } from "./HelpDoc.js"
import * as InternalArgs from "./internal/args.js"
import type { Parameter } from "./Parameter.js"
+import type { Primitive } from "./Primitive.js"
+import type { RegularLanguage } from "./RegularLanguage.js"
import type { Usage } from "./Usage.js"
import type { ValidationError } from "./ValidationError.js"
@@ -39,7 +42,7 @@ export interface Args extends Args.Variance, Parameter, Pipeable {
validate(
args: ReadonlyArray,
config: CliConfig
- ): Effect, A]>
+ ): Effect, A]>
addDescription(description: string): Args
}
@@ -61,9 +64,17 @@ export declare namespace Args {
* @since 1.0.0
* @category models
*/
- export interface ArgsConfig {
+ export interface BaseArgsConfig {
readonly name?: string
}
+
+ /**
+ * @since 1.0.0
+ * @category models
+ */
+ export interface PathArgsConfig extends BaseArgsConfig {
+ readonly exists?: Primitive.PathExists
+ }
}
/**
@@ -166,7 +177,7 @@ export const between: {
* @since 1.0.0
* @category constructors
*/
-export const boolean: (options?: Args.ArgsConfig) => Args = InternalArgs.boolean
+export const boolean: (options?: Args.BaseArgsConfig) => Args = InternalArgs.boolean
/**
* Creates a choice argument.
@@ -178,7 +189,7 @@ export const boolean: (options?: Args.ArgsConfig) => Args = InternalArg
*/
export const choice: (
choices: NonEmptyReadonlyArray<[string, A]>,
- config?: Args.ArgsConfig
+ config?: Args.BaseArgsConfig
) => Args = InternalArgs.choice
/**
@@ -189,7 +200,27 @@ export const choice: (
* @since 1.0.0
* @category constructors
*/
-export const date: (config?: Args.ArgsConfig) => Args = InternalArgs.date
+export const date: (config?: Args.BaseArgsConfig) => Args = InternalArgs.date
+
+/**
+ * Creates a directory argument.
+ *
+ * Can optionally provide a custom argument name (defaults to `"directory"`).
+ *
+ * @since 1.0.0
+ * @category constructors
+ */
+export const directory: (config?: Args.PathArgsConfig) => Args = InternalArgs.directory
+
+/**
+ * Creates a file argument.
+ *
+ * Can optionally provide a custom argument name (defaults to `"file"`).
+ *
+ * @since 1.0.0
+ * @category constructors
+ */
+export const file: (config?: Args.PathArgsConfig) => Args = InternalArgs.file
/**
* Creates a floating point number argument.
@@ -199,7 +230,7 @@ export const date: (config?: Args.ArgsConfig) => Args = Interna
* @since 1.0.0
* @category constructors
*/
-export const float: (config?: Args.ArgsConfig) => Args = InternalArgs.float
+export const float: (config?: Args.BaseArgsConfig) => Args = InternalArgs.float
/**
* Creates an integer argument.
@@ -209,7 +240,7 @@ export const float: (config?: Args.ArgsConfig) => Args = InternalArgs.fl
* @since 1.0.0
* @category constructors
*/
-export const integer: (config?: Args.ArgsConfig) => Args = InternalArgs.integer
+export const integer: (config?: Args.BaseArgsConfig) => Args = InternalArgs.integer
/**
* @since 1.0.0
@@ -246,6 +277,16 @@ export const mapTryCatch: {
*/
export const none: Args = InternalArgs.none
+/**
+ * Creates a path argument.
+ *
+ * Can optionally provide a custom argument name (defaults to `"path"`).
+ *
+ * @since 1.0.0
+ * @category constructors
+ */
+export const path: (config?: Args.PathArgsConfig) => Args = InternalArgs.path
+
/**
* @since 1.0.0
* @category combinators
@@ -267,4 +308,13 @@ export const repeatedAtLeastOnce: (self: Args) => Args Args = InternalArgs.text
+export const text: (config?: Args.BaseArgsConfig) => Args = InternalArgs.text
+
+/**
+ * Returns a `RegularLanguage` whose accepted language is equivalent to the language accepted by the provided `Args`.
+ *
+ * @since 1.0.0
+ * @category combinators
+ */
+export const toRegularLanguage: (self: Args) => RegularLanguage =
+ InternalArgs.toRegularLanguage
diff --git a/src/CliApp.ts b/src/CliApp.ts
index 557a4c3..7d9ad16 100644
--- a/src/CliApp.ts
+++ b/src/CliApp.ts
@@ -1,6 +1,9 @@
/**
* @since 1.0.0
*/
+import type { CommandExecutor } from "@effect/platform/CommandExecutor"
+import type { FileSystem } from "@effect/platform/FileSystem"
+import type { Path } from "@effect/platform/Path"
import type { Effect } from "effect/Effect"
import type { Command } from "./Command.js"
import type { HelpDoc } from "./HelpDoc.js"
@@ -22,6 +25,17 @@ export interface CliApp {
readonly footer: HelpDoc
}
+/**
+ * @since 1.0.0
+ */
+export declare namespace CliApp {
+ /**
+ * @since 1.0.0
+ * @category models
+ */
+ export type Environment = CommandExecutor | FileSystem | Path
+}
+
/**
* @since 1.0.0
* @category constructors
@@ -43,11 +57,11 @@ export const make: (
export const run: {
(
args: ReadonlyArray,
- f: (a: A) => Effect
- ): (self: CliApp) => Effect
+ execute: (a: A) => Effect
+ ): (self: CliApp) => Effect
(
self: CliApp,
args: ReadonlyArray,
- f: (a: A) => Effect
- ): Effect
+ execute: (a: A) => Effect
+ ): Effect
} = InternalCliApp.run
diff --git a/src/Command.ts b/src/Command.ts
index e17e466..9600333 100644
--- a/src/Command.ts
+++ b/src/Command.ts
@@ -1,6 +1,7 @@
/**
* @since 1.0.0
*/
+import type { FileSystem } from "@effect/platform/FileSystem"
import type { Effect } from "effect/Effect"
import type { Either } from "effect/Either"
import type { HashMap } from "effect/HashMap"
@@ -15,6 +16,7 @@ import * as InternalCommand from "./internal/command.js"
import type { Options } from "./Options.js"
import type { Named } from "./Parameter.js"
import type { Prompt } from "./Prompt.js"
+import type { RegularLanguage } from "./RegularLanguage.js"
import type { Terminal } from "./Terminal.js"
import type { Usage } from "./Usage.js"
import type { ValidationError } from "./ValidationError.js"
@@ -47,7 +49,7 @@ export interface Command extends Command.Variance, Named, Pipeable {
parse(
args: ReadonlyArray,
config: CliConfig
- ): Effect>
+ ): Effect>
}
/**
@@ -190,6 +192,18 @@ export const subcommands: {
>
} = InternalCommand.subcommands
+/**
+ * Returns a `RegularLanguage` whose accepted language is equivalent to the
+ * language accepted by the provided `Command`.
+ *
+ * @since 1.0.0
+ * @category combinators
+ */
+export const toRegularLanguage: {
+ (allowAlias: boolean): (self: Command) => RegularLanguage
+ (self: Command, allowAlias: boolean): RegularLanguage
+} = InternalCommand.toRegularLanguage
+
/**
* @since 1.0.0
* @category combinators
diff --git a/src/Compgen.ts b/src/Compgen.ts
new file mode 100644
index 0000000..b1fca8c
--- /dev/null
+++ b/src/Compgen.ts
@@ -0,0 +1,39 @@
+/**
+ * @since 1.0.0
+ */
+import type { CommandExecutor } from "@effect/platform/CommandExecutor"
+import type { PlatformError } from "@effect/platform/Error"
+import type { Tag } from "effect/Context"
+import type { Effect } from "effect/Effect"
+import type { Layer } from "effect/Layer"
+import * as InternalCompgen from "./internal/compgen.js"
+
+/**
+ * `Compgen` simplifies the process of calling Bash's built-in `compgen` command.
+ *
+ * @since 1.0.0
+ * @category models
+ */
+export interface Compgen {
+ completeFileNames(word: string): Effect>
+ completeDirectoryNames(word: string): Effect>
+}
+
+/**
+ * @since 1.0.0
+ * @category context
+ */
+export const Compgen: Tag = InternalCompgen.Tag
+
+/**
+ * @since 1.0.0
+ * @category context
+ */
+export const LiveCompgen: Layer = InternalCompgen.LiveCompgen
+
+/**
+ * @since 1.0.0
+ * @category context
+ */
+export const TestCompgen: (workingDirectory: string) => Layer =
+ InternalCompgen.TestCompgen
diff --git a/src/Completion.ts b/src/Completion.ts
new file mode 100644
index 0000000..c2fc380
--- /dev/null
+++ b/src/Completion.ts
@@ -0,0 +1,35 @@
+/**
+ * @since 1.0.0
+ */
+import type { FileSystem } from "@effect/platform/FileSystem"
+import type { Path } from "@effect/platform/Path"
+import type { Effect } from "effect/Effect"
+import type { NonEmptyReadonlyArray } from "effect/ReadonlyArray"
+import type { CliConfig } from "./CliConfig.js"
+import type { Command } from "./Command.js"
+import type { Compgen } from "./Compgen.js"
+import * as InternalCompletion from "./internal/completion.js"
+import type { ShellType } from "./ShellType.js"
+
+/**
+ * @since 1.0.0
+ * @category completions
+ */
+export const getCompletions: (
+ words: ReadonlyArray,
+ index: number,
+ command: Command,
+ config: CliConfig,
+ compgen: Compgen
+) => Effect> = InternalCompletion.getCompletions
+
+/**
+ * @since 1.0.0
+ * @category completions
+ */
+export const getCompletionScript: (
+ pathToExecutable: string,
+ programNames: NonEmptyReadonlyArray,
+ shellType: ShellType,
+ path: Path
+) => string = InternalCompletion.getCompletionScript
diff --git a/src/Exists.ts b/src/Exists.ts
deleted file mode 100644
index b6d103e..0000000
--- a/src/Exists.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * @since 1.0.0
- */
-
-/**
- * Describes whether the command-line application wants a file/directory to
- * exist or not exist.
- *
- * @since 1.0.0
- */
-export type Exists = "yes" | "no" | "either"
diff --git a/src/Options.ts b/src/Options.ts
index 10e353b..0a13415 100644
--- a/src/Options.ts
+++ b/src/Options.ts
@@ -1,6 +1,7 @@
/**
* @since 1.0.0
*/
+import type { FileSystem } from "@effect/platform/FileSystem"
import type { Effect } from "effect/Effect"
import type { Either } from "effect/Either"
import type { HashMap } from "effect/HashMap"
@@ -11,6 +12,8 @@ import type { CliConfig } from "./CliConfig.js"
import type { HelpDoc } from "./HelpDoc.js"
import * as InternalOptions from "./internal/options.js"
import type { Input, Parameter } from "./Parameter.js"
+import type { Primitive } from "./Primitive.js"
+import type { RegularLanguage } from "./RegularLanguage.js"
import type { Usage } from "./Usage.js"
import type { ValidationError } from "./ValidationError.js"
@@ -37,7 +40,7 @@ export interface Options extends Options.Variance, Parameter, Pipeable {
validate(
args: HashMap>,
config: CliConfig
- ): Effect
+ ): Effect
/** @internal */
modifySingle(f: <_>(single: InternalOptions.Single<_>) => InternalOptions.Single<_>): Options
}
@@ -60,11 +63,19 @@ export declare namespace Options {
* @since 1.0.0
* @category models
*/
- export interface BooleanOptionConfig {
+ export interface BooleanOptionsConfig {
readonly ifPresent?: boolean
readonly negationNames?: NonEmptyReadonlyArray
readonly aliases?: NonEmptyReadonlyArray
}
+
+ /**
+ * @since 1.0.0
+ * @category models
+ */
+ export interface PathOptionsConfig {
+ readonly exists?: Primitive.PathExists
+ }
}
/**
@@ -140,7 +151,7 @@ export const all: <
* @since 1.0.0
* @category constructors
*/
-export const boolean: (name: string, options?: Options.BooleanOptionConfig) => Options =
+export const boolean: (name: string, options?: Options.BooleanOptionsConfig) => Options =
InternalOptions.boolean
/**
@@ -204,6 +215,24 @@ export const choiceWithValue: >(
*/
export const date: (name: string) => Options = InternalOptions.date
+/**
+ * Creates a parameter expecting path to a directory.
+ *
+ * @since 1.0.0
+ * @category constructors
+ */
+export const directory: (name: string, config: Options.PathOptionsConfig) => Options =
+ InternalOptions.directory
+
+/**
+ * Creates a parameter expecting path to a file.
+ *
+ * @since 1.0.0
+ * @category constructors
+ */
+export const file: (name: string, config: Options.PathOptionsConfig) => Options =
+ InternalOptions.file
+
/**
* @since 1.0.0
* @category constructors
@@ -308,6 +337,16 @@ export const orElseEither: {
(self: Options, that: Options): Options>
} = InternalOptions.orElseEither
+/**
+ * Returns a `RegularLanguage` whose accepted language is equivalent to the language accepted by the provided
+ * `Options`.
+ *
+ * @since 1.0.0
+ * @category combinators
+ */
+export const toRegularLanguage: (self: Options) => RegularLanguage =
+ InternalOptions.toRegularLanguage
+
/**
* @since 1.0.0
* @category combinators
@@ -318,12 +357,20 @@ export const validate: {
config: CliConfig
): (
self: Options
- ) => Effect, ReadonlyArray, A]>
+ ) => Effect<
+ FileSystem,
+ ValidationError,
+ readonly [Option, ReadonlyArray, A]
+ >
(
self: Options,
args: ReadonlyArray,
config: CliConfig
- ): Effect, ReadonlyArray, A]>
+ ): Effect<
+ FileSystem,
+ ValidationError,
+ readonly [Option, ReadonlyArray, A]
+ >
} = InternalOptions.validate
/**
diff --git a/src/Parameter.ts b/src/Parameter.ts
index 1f20c8f..14d33fa 100644
--- a/src/Parameter.ts
+++ b/src/Parameter.ts
@@ -1,6 +1,7 @@
/**
* @since 1.0.0
*/
+import type { FileSystem } from "@effect/platform/FileSystem"
import type { Effect } from "effect/Effect"
import type { HashSet } from "effect/HashSet"
import type { CliConfig } from "./CliConfig.js"
@@ -26,7 +27,10 @@ export interface Parameter {
* @since 1.0.0
*/
export interface Input extends Parameter {
- isValid(input: string, config: CliConfig): Effect>
+ isValid(
+ input: string,
+ config: CliConfig
+ ): Effect>
parse(
args: ReadonlyArray,
config: CliConfig
diff --git a/src/Primitive.ts b/src/Primitive.ts
index 53b461f..2474440 100644
--- a/src/Primitive.ts
+++ b/src/Primitive.ts
@@ -1,6 +1,7 @@
/**
* @since 1.0.0
*/
+import type { FileSystem } from "@effect/platform/FileSystem"
import type { Effect } from "effect/Effect"
import type { Option } from "effect/Option"
import type { Pipeable } from "effect/Pipeable"
@@ -33,7 +34,7 @@ export interface Primitive extends Primitive.Variance {
get typeName(): string
get help(): Span
get choices(): Option
- validate(value: Option, config: CliConfig): Effect
+ validate(value: Option, config: CliConfig): Effect
}
/**
@@ -50,6 +51,18 @@ export declare namespace Primitive {
}
}
+ /**
+ * @since 1.0.0
+ * @category models
+ */
+ export type PathExists = "yes" | "no" | "either"
+
+ /**
+ * @since 1.0.0
+ * @category models
+ */
+ export type PathType = "file" | "directory" | "either"
+
/**
* @since 1.0.0
* @category models
diff --git a/src/RegularLanguage.ts b/src/RegularLanguage.ts
new file mode 100644
index 0000000..d56d5bd
--- /dev/null
+++ b/src/RegularLanguage.ts
@@ -0,0 +1,413 @@
+/**
+ * @since 1.0.0
+ */
+import type { FileSystem } from "@effect/platform/FileSystem"
+import type { Case } from "effect/Data"
+import type { Effect } from "effect/Effect"
+import type { Option } from "effect/Option"
+import type { Pipeable } from "effect/Pipeable"
+import type { CliConfig } from "./CliConfig.js"
+import * as InternalRegularLanguage from "./internal/regularLanguage.js"
+import type { Primitive } from "./Primitive.js"
+
+/**
+ * @since 1.0.0
+ * @category symbols
+ */
+export const RegularLanguageTypeId: unique symbol = InternalRegularLanguage.RegularLanguageTypeId
+
+/**
+ * @since 1.0.0
+ * @category symbols
+ */
+export type RegularLanguageTypeId = typeof RegularLanguageTypeId
+
+/**
+ * `RegularLanguage` is an implementation of "Parsing With Derivatives" (Might
+ * et al. 2011) that is used for CLI tab completion. Unlike your usual regular
+ * languages that are sets of strings of symbols, our regular languages are sets
+ * of lists of tokens, where tokens can be strings or `Primitive` instances. (If
+ * you think about it, `Primitive.validate` is an intensional definition of a
+ * set of strings.)
+ *
+ * @since 1.0.0
+ * @category models
+ */
+export type RegularLanguage =
+ | Empty
+ | Epsilon
+ | StringToken
+ | AnyStringToken
+ | PrimitiveToken
+ | Cat
+ | Alt
+ | Repeat
+ | Permutation
+
+/**
+ * @since 1.0.0
+ */
+export declare namespace RegularLanguage {
+ /**
+ * @since 1.0.0
+ * @category models
+ */
+ export interface Proto {
+ readonly [RegularLanguageTypeId]: (_: never) => never
+ }
+
+ /**
+ * @since 1.0.0
+ * @category models
+ */
+ export interface RepetitionConfiguration {
+ readonly min?: number
+ readonly max?: number
+ }
+}
+
+/**
+ * The `Empty` language (`∅`) accepts no strings.
+ *
+ * @since 1.0.0
+ * @category models
+ */
+export interface Empty extends RegularLanguage.Proto, Case, Pipeable {
+ readonly _tag: "Empty"
+}
+
+/**
+ * The `Epsilon` language (`ε`) accepts only the empty string.
+ *
+ * @since 1.0.0
+ * @category models
+ */
+export interface Epsilon extends RegularLanguage.Proto, Case, Pipeable {
+ readonly _tag: "Epsilon"
+}
+
+/**
+ * A `StringToken` language represents the regular language that contains only
+ * `value`.
+ *
+ * @since 1.0.0
+ * @category models
+ */
+export interface StringToken extends RegularLanguage.Proto, Case, Pipeable {
+ readonly _tag: "StringToken"
+ readonly value: string
+}
+
+/**
+ * `AnyStringToken` represents the set of all strings. For tab completion
+ * purposes, this is used to represent the name of the executable (It may be
+ * aliased or renamed to be different).
+ *
+ * @since 1.0.0
+ * @category models
+ */
+export interface AnyStringToken extends RegularLanguage.Proto, Case, Pipeable {
+ readonly _tag: "AnyStringToken"
+}
+
+/**
+ * A `PrimitiveToken` language represents the regular language containing any
+ * strings `s` where `value.validate(s)` succeeds.
+ *
+ * @since 1.0.0
+ * @category models
+ */
+export interface PrimitiveToken extends RegularLanguage.Proto, Case, Pipeable {
+ readonly _tag: "PrimitiveToken"
+ readonly primitive: Primitive
+}
+
+/**
+ * `Cat` represents the concatenation of two regular languages.
+ *
+ * @since 1.0.0
+ * @category models
+ */
+export interface Cat extends RegularLanguage.Proto, Case, Pipeable {
+ readonly _tag: "Cat"
+ readonly left: RegularLanguage
+ readonly right: RegularLanguage
+}
+
+/**
+ * `Alt` represents the union of two regular languages. We call it "Alt" for
+ * consistency with the names used in the "Parsing With Derivatives" paper.
+ *
+ * @since 1.0.0
+ * @category models
+ */
+export interface Alt extends RegularLanguage.Proto, Case, Pipeable {
+ readonly _tag: "Alt"
+ readonly left: RegularLanguage
+ readonly right: RegularLanguage
+}
+
+/**
+ * `Repeat` represents the repetition of `language`. The number of repetitions
+ * can be bounded via `min` and `max`. Setting `max=None` represents the
+ * "Kleene star" of `language`.
+ *
+ * @since 1.0.0
+ * @category models
+ */
+export interface Repeat extends RegularLanguage.Proto, Case, Pipeable {
+ readonly _tag: "Repeat"
+ readonly language: RegularLanguage
+ readonly min: Option
+ readonly max: Option
+}
+
+/**
+ * Permutation is like `Cat`, but it is a commutative monoid. A
+ * `Permutation(a_1, a_2, ..., a_{k})` is equivalent to the following language:
+ *
+ * ```
+ * a2 ~ Permutation(a_1, a_3, ..., a_k) | a1 ~ Permutation(a_2, a_3, ..., a_k) | ... ak ~ Permutation(a_1, a_2, ..., a_{k - 1})
+ * ```
+ *
+ * So when we calculate its derivative, we apply the above "desugaring"
+ * transformation, then compute the derivative as usual.
+ *
+ * @since 1.0.0
+ * @category models
+ */
+export interface Permutation extends RegularLanguage.Proto, Case, Pipeable {
+ readonly _tag: "Permutation"
+ readonly values: ReadonlyArray
+}
+
+/**
+ * @since 1.0.0
+ * @category refinements
+ */
+export const isRegularLanguage: (u: unknown) => u is RegularLanguage =
+ InternalRegularLanguage.isRegularLanguage
+
+/**
+ * @since 1.0.0
+ * @category refinements
+ */
+export const isEmpty: (self: RegularLanguage) => self is Empty = InternalRegularLanguage.isEmpty
+
+/**
+ * @since 1.0.0
+ * @category refinements
+ */
+export const isEpsilon: (self: RegularLanguage) => self is Epsilon =
+ InternalRegularLanguage.isEpsilon
+
+/**
+ * @since 1.0.0
+ * @category refinements
+ */
+export const isStringToken: (self: RegularLanguage) => self is StringToken =
+ InternalRegularLanguage.isStringToken
+
+/**
+ * @since 1.0.0
+ * @category refinements
+ */
+export const isAnyStringToken: (self: RegularLanguage) => self is AnyStringToken =
+ InternalRegularLanguage.isAnyStringToken
+
+/**
+ * @since 1.0.0
+ * @category refinements
+ */
+export const isPrimitiveToken: (self: RegularLanguage) => self is PrimitiveToken =
+ InternalRegularLanguage.isPrimitiveToken
+
+/**
+ * @since 1.0.0
+ * @category refinements
+ */
+export const isCat: (self: RegularLanguage) => self is Cat = InternalRegularLanguage.isCat
+
+/**
+ * @since 1.0.0
+ * @category refinements
+ */
+export const isAlt: (self: RegularLanguage) => self is Alt = InternalRegularLanguage.isAlt
+
+/**
+ * @since 1.0.0
+ * @category refinements
+ */
+export const isRepeat: (self: RegularLanguage) => self is Repeat = InternalRegularLanguage.isRepeat
+
+/**
+ * @since 1.0.0
+ * @category refinements
+ */
+export const isPermutation: (self: RegularLanguage) => self is Permutation =
+ InternalRegularLanguage.isPermutation
+
+/**
+ * @since 1.0.0
+ * @category constructors
+ */
+export const anyString: RegularLanguage = InternalRegularLanguage.anyString
+
+/**
+ * @since 1.0.0
+ * @category combinators
+ */
+export const concat: {
+ (that: string | RegularLanguage): (self: RegularLanguage) => RegularLanguage
+ (self: RegularLanguage, that: string | RegularLanguage): RegularLanguage
+} = InternalRegularLanguage.concat
+
+/**
+ * Checks to see if the input token list is a member of the language.
+ *
+ * Returns `true` if and only if `tokens` is in the language.
+ *
+ * @since 1.0.0
+ * @category combinators
+ */
+export const contains: {
+ (
+ tokens: ReadonlyArray,
+ config: CliConfig
+ ): (self: RegularLanguage) => Effect
+ (
+ self: RegularLanguage,
+ tokens: ReadonlyArray,
+ config: CliConfig
+ ): Effect
+} = InternalRegularLanguage.contains
+
+/**
+ * Calculate the Brzozowski derivative of this language with respect to the given string. This is an effectful
+ * function because it can call PrimType.validate (e.g., when validating file paths, etc.).
+ *
+ * @param token
+ * The string to use for calculation of the Brzozowski derivative.
+ * @return
+ * Brzozowski derivative wrapped in an UIO instance.
+ *
+ * @since 1.0.0
+ * @category combinators
+ */
+export const derive: {
+ (
+ token: string,
+ config: CliConfig
+ ): (self: RegularLanguage) => Effect
+ (
+ self: RegularLanguage,
+ token: string,
+ config: CliConfig
+ ): Effect
+} = InternalRegularLanguage.derive
+
+/**
+ * @since 1.0.0
+ * @category constructors
+ */
+export const empty: RegularLanguage = InternalRegularLanguage.empty
+
+/**
+ * @since 1.0.0
+ * @category constructors
+ */
+export const epsilon: RegularLanguage = InternalRegularLanguage.epsilon
+
+/**
+ * Returns a set consisting of the first token of all strings in this language
+ * that are useful for CLI tab completion. For infinite or unwieldly languages,
+ * it is perfectly fine to return the empty set: This will simply not display
+ * any completions to the user.
+ *
+ * If you'd like the cursor to advance to the next word when tab completion
+ * unambiguously matches the prefix to a token, append a space (`" "`) character
+ * to the end of the returned token. Otherwise, the cursor will skip to the end
+ * of the completed token in the terminal.
+ *
+ * Some examples of different use cases:
+ * 1. Completing file/directory names:
+ * - Append a space to the ends of file names (e.g., `"bippy.pdf"`). This
+ * is because we want the cursor to jump to the next argument position if
+ * tab completion unambiguously succeeds.
+ *
+ * - Do not append a space to the end of a directory name (e.g., `"foo/"`).
+ * This is because we want the user to be able to press tab again to
+ * gradually complete a lengthy file path.
+ *
+ * - Append a space to the ends of string tokens.
+ *
+ * You may be asking why we don't try to use the `-o nospace` setting of
+ * `compgen` and `complete`. The answer is they appear to be all or nothing: For
+ * a given tab completion execution, you have to choose one behavior or the
+ * other. This does not work well when completing both file names and directory
+ * names at the same time.
+ *
+ * @since 1.0.0
+ * @category combinators
+ */
+export const firstTokens = InternalRegularLanguage.firstTokens
+
+/**
+ * This is the delta (`δ`) predicate from "Parsing With Derivatives", indicating
+ * whether this language contains the empty string.
+ *
+ * Returns `true` if and only if this language contains the empty string.
+ *
+ * @since 1.0.0
+ * @category combinators
+ */
+export const isNullable: (self: RegularLanguage) => boolean = InternalRegularLanguage.isNullable
+
+/**
+ * @since 1.0.0
+ * @category combinators
+ */
+export const optional: (self: RegularLanguage) => RegularLanguage = InternalRegularLanguage.optional
+
+/**
+ * @since 1.0.0
+ * @category combinators
+ */
+export const orElse: {
+ (that: string | RegularLanguage): (self: RegularLanguage) => RegularLanguage
+ (self: RegularLanguage, that: string | RegularLanguage): RegularLanguage
+} = InternalRegularLanguage.orElse
+
+/**
+ * @since 1.0.0
+ * @category constructors
+ */
+export const primitive: (primitive: Primitive) => RegularLanguage =
+ InternalRegularLanguage.primitive
+
+/**
+ * @since 1.0.0
+ * @category constructors
+ */
+export const permutation: (values: ReadonlyArray) => RegularLanguage =
+ InternalRegularLanguage.permutation
+
+/**
+ * @since 1.0.0
+ * @category combinators
+ */
+export const repeated: {
+ (
+ params?: Partial
+ ): (self: RegularLanguage) => RegularLanguage
+ (
+ self: RegularLanguage,
+ params?: Partial
+ ): RegularLanguage
+} = InternalRegularLanguage.repeated
+
+/**
+ * @since 1.0.0
+ * @category constructors
+ */
+export const string: (value: string) => RegularLanguage = InternalRegularLanguage.string
diff --git a/src/ShellType.ts b/src/ShellType.ts
index b7dfd2b..2fe35cb 100644
--- a/src/ShellType.ts
+++ b/src/ShellType.ts
@@ -8,7 +8,7 @@ import type { Options } from "./Options.js"
* @since 1.0.0
* @category models
*/
-export type ShellType = Bash | ZShell
+export type ShellType = Bash | Fish | Zsh
/**
* @since 1.0.0
@@ -22,8 +22,16 @@ export interface Bash {
* @since 1.0.0
* @category models
*/
-export interface ZShell {
- readonly _tag: "ZShell"
+export interface Fish {
+ readonly _tag: "Fish"
+}
+
+/**
+ * @since 1.0.0
+ * @category models
+ */
+export interface Zsh {
+ readonly _tag: "Zsh"
}
/**
@@ -36,7 +44,13 @@ export const bash: ShellType = InternalShellType.bash
* @since 1.0.0
* @category constructors
*/
-export const zShell: ShellType = InternalShellType.zShell
+export const fish: ShellType = InternalShellType.fish
+
+/**
+ * @since 1.0.0
+ * @category constructors
+ */
+export const zsh: ShellType = InternalShellType.zsh
/**
* @since 1.0.0
diff --git a/src/Terminal.ts b/src/Terminal.ts
index 4cabaca..d5fc1d7 100644
--- a/src/Terminal.ts
+++ b/src/Terminal.ts
@@ -73,4 +73,4 @@ export const Terminal: Context.Tag = internal.Tag
* @since 1.0.0
* @category context
*/
-export const layer: Layer.Layer = internal.layer
+export const LiveTerminal: Layer.Layer = internal.LiveTerminal
diff --git a/src/index.ts b/src/index.ts
index 6249551..b9f3543 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -36,7 +36,12 @@ export * as CommandDirective from "./CommandDirective.js"
/**
* @since 1.0.0
*/
-export * as Exists from "./Exists.js"
+export * as Compgen from "./Compgen.js"
+
+/**
+ * @since 1.0.0
+ */
+export * as Completion from "./Completion.js"
/**
* @since 1.0.0
@@ -63,6 +68,11 @@ export * as Primitive from "./Primitive.js"
*/
export * as Prompt from "./Prompt.js"
+/**
+ * @since 1.0.0
+ */
+export * as RegularLanguage from "./RegularLanguage.js"
+
/**
* @since 1.0.0
*/
diff --git a/src/internal/args.ts b/src/internal/args.ts
index be82623..9a30546 100644
--- a/src/internal/args.ts
+++ b/src/internal/args.ts
@@ -1,3 +1,4 @@
+import type * as FileSystem from "@effect/platform/FileSystem"
import * as Effect from "effect/Effect"
import * as Either from "effect/Either"
import { dual } from "effect/Function"
@@ -9,11 +10,13 @@ import type * as CliConfig from "../CliConfig.js"
import type * as HelpDoc from "../HelpDoc.js"
import type * as Parameter from "../Parameter.js"
import type * as Primitive from "../Primitive.js"
+import type * as RegularLanguage from "../RegularLanguage.js"
import type * as Usage from "../Usage.js"
import type * as ValidationError from "../ValidationError.js"
import * as InternalHelpDoc from "./helpDoc.js"
import * as InternalSpan from "./helpDoc/span.js"
import * as InternalPrimitive from "./primitive.js"
+import * as InternalRegularLanguage from "./regularLanguage.js"
import * as InternalUsage from "./usage.js"
import * as InternalValidationError from "./validationError.js"
@@ -117,7 +120,7 @@ export class Single implements Args.Args, Parameter.Input {
isValid(
input: string,
config: CliConfig.CliConfig
- ): Effect.Effect> {
+ ): Effect.Effect> {
const args = ReadonlyArray.of(input)
return this.validate(args, config).pipe(Effect.as(args))
}
@@ -125,7 +128,11 @@ export class Single implements Args.Args, Parameter.Input {
validate(
args: ReadonlyArray,
config: CliConfig.CliConfig
- ): Effect.Effect, A]> {
+ ): Effect.Effect<
+ FileSystem.FileSystem,
+ ValidationError.ValidationError,
+ readonly [ReadonlyArray, A]
+ > {
return Effect.suspend(() => {
if (ReadonlyArray.isNonEmptyReadonlyArray(args)) {
const head = ReadonlyArray.headNonEmpty(args)
@@ -226,7 +233,7 @@ export class Both implements Args.Args {
args: ReadonlyArray,
config: CliConfig.CliConfig
): Effect.Effect<
- never,
+ FileSystem.FileSystem,
ValidationError.ValidationError,
readonly [ReadonlyArray, readonly [A, B]]
> {
@@ -306,7 +313,7 @@ export class Variadic implements Args.Args>, Parameter.Input
args: ReadonlyArray,
config: CliConfig.CliConfig
): Effect.Effect<
- never,
+ FileSystem.FileSystem,
ValidationError.ValidationError,
readonly [ReadonlyArray, ReadonlyArray]
> {
@@ -316,7 +323,7 @@ export class Variadic implements Args.Args>, Parameter.Input
args: ReadonlyArray,
acc: ReadonlyArray
): Effect.Effect<
- never,
+ FileSystem.FileSystem,
ValidationError.ValidationError,
readonly [ReadonlyArray, ReadonlyArray]
> => {
@@ -350,7 +357,7 @@ export class Variadic implements Args.Args>, Parameter.Input
isValid(
input: string,
config: CliConfig.CliConfig
- ): Effect.Effect> {
+ ): Effect.Effect> {
const args = input.split(" ")
return this.validate(args, config).pipe(Effect.as(args))
}
@@ -401,7 +408,11 @@ export class Map implements Args.Args {
validate(
args: ReadonlyArray,
config: CliConfig.CliConfig
- ): Effect.Effect, B]> {
+ ): Effect.Effect<
+ FileSystem.FileSystem,
+ ValidationError.ValidationError,
+ readonly [ReadonlyArray, B]
+ > {
return this.args.validate(args, config).pipe(
Effect.flatMap(([leftover, a]) =>
Either.match(this.f(a), {
@@ -481,32 +492,53 @@ export const all: <
}
/** @internal */
-export const boolean = (config: Args.Args.ArgsConfig = {}): Args.Args =>
+export const boolean = (config: Args.Args.BaseArgsConfig = {}): Args.Args =>
new Single(Option.fromNullable(config.name), InternalPrimitive.boolean(Option.none()))
/** @internal */
export const choice = (
choices: ReadonlyArray.NonEmptyReadonlyArray<[string, A]>,
- config: Args.Args.ArgsConfig = {}
+ config: Args.Args.BaseArgsConfig = {}
): Args.Args => new Single(Option.fromNullable(config.name), InternalPrimitive.choice(choices))
/** @internal */
-export const date = (config: Args.Args.ArgsConfig = {}): Args.Args =>
+export const date = (config: Args.Args.BaseArgsConfig = {}): Args.Args =>
new Single(Option.fromNullable(config.name), InternalPrimitive.date)
/** @internal */
-export const float = (config: Args.Args.ArgsConfig = {}): Args.Args =>
+export const directory = (config: Args.Args.PathArgsConfig = {}): Args.Args =>
+ new Single(
+ Option.fromNullable(config.name),
+ InternalPrimitive.path("directory", config.exists || "either")
+ )
+
+/** @internal */
+export const file = (config: Args.Args.PathArgsConfig = {}): Args.Args =>
+ new Single(
+ Option.fromNullable(config.name),
+ InternalPrimitive.path("file", config.exists || "either")
+ )
+
+/** @internal */
+export const float = (config: Args.Args.BaseArgsConfig = {}): Args.Args =>
new Single(Option.fromNullable(config.name), InternalPrimitive.float)
/** @internal */
-export const integer = (config: Args.Args.ArgsConfig = {}): Args.Args =>
+export const integer = (config: Args.Args.BaseArgsConfig = {}): Args.Args =>
new Single(Option.fromNullable(config.name), InternalPrimitive.integer)
/** @internal */
export const none: Args.Args = new Empty()
/** @internal */
-export const text = (config: Args.Args.ArgsConfig = {}): Args.Args =>
+export const path = (config: Args.Args.PathArgsConfig = {}): Args.Args =>
+ new Single(
+ Option.fromNullable(config.name),
+ InternalPrimitive.path("either", config.exists || "either")
+ )
+
+/** @internal */
+export const text = (config: Args.Args.BaseArgsConfig = {}): Args.Args =>
new Single(Option.fromNullable(config.name), InternalPrimitive.text)
// =============================================================================
@@ -601,6 +633,37 @@ export const repeatedAtLeastOnce = (
throw new Error(`${message} is not respecting the required minimum of 1`)
})
+/** @internal */
+export const toRegularLanguage = (
+ self: Args.Args
+): RegularLanguage.RegularLanguage => {
+ if (isEmpty(self)) {
+ return InternalRegularLanguage.epsilon
+ }
+ if (isSingle(self)) {
+ return InternalRegularLanguage.primitive(self.primitiveType)
+ }
+ if (isBoth(self)) {
+ return InternalRegularLanguage.concat(
+ toRegularLanguage(self.left),
+ toRegularLanguage(self.right)
+ )
+ }
+ if (isVariadic(self)) {
+ return InternalRegularLanguage.repeated(toRegularLanguage(self.value), {
+ min: Option.getOrUndefined(self.min),
+ max: Option.getOrUndefined(self.max)
+ })
+ }
+ if (isMap(self)) {
+ return toRegularLanguage(self.args)
+ }
+ throw new Error(
+ "[BUG]: Args.toRegularLanguage - received unrecognized " +
+ `args type ${JSON.stringify(self)}`
+ )
+}
+
// =============================================================================
// Internals
// =============================================================================
diff --git a/src/internal/cliApp.ts b/src/internal/cliApp.ts
index 9818052..cbcac9d 100644
--- a/src/internal/cliApp.ts
+++ b/src/internal/cliApp.ts
@@ -1,18 +1,27 @@
+import type * as FileSystem from "@effect/platform/FileSystem"
+import * as Path from "@effect/platform/Path"
import * as Console from "effect/Console"
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
import { dual, pipe } from "effect/Function"
+import * as Layer from "effect/Layer"
import * as Option from "effect/Option"
+import * as Order from "effect/Order"
import * as ReadonlyArray from "effect/ReadonlyArray"
+import * as ReadonlyRecord from "effect/ReadonlyRecord"
+import { unify } from "effect/Unify"
import type * as BuiltInOptions from "../BuiltInOptions.js"
import type * as CliApp from "../CliApp.js"
import type * as CliConfig from "../CliConfig.js"
import type * as Command from "../Command.js"
+import type * as Compgen from "../Compgen.js"
import type * as HelpDoc from "../HelpDoc.js"
import type * as Span from "../HelpDoc/Span.js"
import type * as ValidationError from "../ValidationError.js"
import * as InternalCliConfig from "./cliConfig.js"
import * as InternalCommand from "./command.js"
+import * as InternalCompgen from "./compgen.js"
+import * as InternalCompletion from "./completion.js"
import * as InternalHelpDoc from "./helpDoc.js"
import * as InternalSpan from "./helpDoc/span.js"
import * as InternalTerminal from "./terminal.js"
@@ -41,34 +50,46 @@ export const make = (config: {
// Combinators
// =============================================================================
+const MainLive = Layer.merge(InternalTerminal.LiveTerminal, InternalCompgen.LiveCompgen)
+
/** @internal */
export const run = dual<
(
args: ReadonlyArray,
- f: (a: A) => Effect.Effect
- ) => (self: CliApp.CliApp) => Effect.Effect,
+ execute: (a: A) => Effect.Effect
+ ) => (
+ self: CliApp.CliApp
+ ) => Effect.Effect,
(
self: CliApp.CliApp,
args: ReadonlyArray,
- f: (a: A) => Effect.Effect
- ) => Effect.Effect
+ execute: (a: A) => Effect.Effect
+ ) => Effect.Effect
>(3, (
self: CliApp.CliApp,
args: ReadonlyArray,
- f: (a: A) => Effect.Effect
-): Effect.Effect =>
- Effect.contextWithEffect((context: Context.Context) => {
+ execute: (a: A) => Effect.Effect
+): Effect.Effect =>
+ Effect.gen(function*(_) {
+ const context = yield* _(Effect.context())
+
+ // Attempt to parse the CliConfig from the environment, falling back to the
+ // default CliConfig if none was provided
const config = Option.getOrElse(
Context.getOption(context, InternalCliConfig.Tag),
() => InternalCliConfig.defaultConfig
)
+
+ // Prefix the command name to the command line arguments
const prefixedArgs = ReadonlyArray.appendAll(prefixCommand(self.command), args)
- return self.command.parse(prefixedArgs, config).pipe(Effect.matchEffect({
+
+ // Handle the command
+ return yield* _(Effect.matchEffect(self.command.parse(prefixedArgs, config), {
onFailure: (e) => Effect.zipRight(printDocs(e.error), Effect.fail(e)),
- onSuccess: (directive): Effect.Effect => {
+ onSuccess: unify((directive) => {
switch (directive._tag) {
case "UserDefined": {
- return f(directive.value)
+ return execute(directive.value)
}
case "BuiltIn": {
return handleBuiltInOption(self, directive.option, config).pipe(
@@ -80,9 +101,9 @@ export const run = dual<
)
}
}
- }
+ })
}))
- }).pipe(Effect.provide(InternalTerminal.layer)))
+ }).pipe(Effect.provide(MainLive)))
// =============================================================================
// Internals
@@ -95,47 +116,85 @@ const handleBuiltInOption = (
self: CliApp.CliApp,
builtIn: BuiltInOptions.BuiltInOptions,
config: CliConfig.CliConfig
-): Effect.Effect => {
- switch (builtIn._tag) {
- case "ShowHelp": {
- const banner = InternalHelpDoc.h1(InternalSpan.code(self.name))
- const header = InternalHelpDoc.p(InternalSpan.concat(
- InternalSpan.text(`${self.name} ${self.version} -- `),
- self.summary
- ))
- const usage = InternalHelpDoc.sequence(
- InternalHelpDoc.h1("USAGE"),
- pipe(
- InternalUsage.enumerate(builtIn.usage, config),
- ReadonlyArray.map((span) =>
- InternalHelpDoc.p(InternalSpan.concat(InternalSpan.text("$ "), span))
- ),
- ReadonlyArray.reduceRight(
- InternalHelpDoc.empty,
- (left, right) => InternalHelpDoc.sequence(left, right)
+): Effect.Effect<
+ Compgen.Compgen | FileSystem.FileSystem | Path.Path,
+ ValidationError.ValidationError,
+ void
+> =>
+ Effect.gen(function*(_) {
+ switch (builtIn._tag) {
+ case "ShowHelp": {
+ const banner = InternalHelpDoc.h1(InternalSpan.code(self.name))
+ const header = InternalHelpDoc.p(InternalSpan.concat(
+ InternalSpan.text(`${self.name} ${self.version} -- `),
+ self.summary
+ ))
+ const usage = InternalHelpDoc.sequence(
+ InternalHelpDoc.h1("USAGE"),
+ pipe(
+ InternalUsage.enumerate(builtIn.usage, config),
+ ReadonlyArray.map((span) =>
+ InternalHelpDoc.p(InternalSpan.concat(InternalSpan.text("$ "), span))
+ ),
+ ReadonlyArray.reduceRight(
+ InternalHelpDoc.empty,
+ (left, right) => InternalHelpDoc.sequence(left, right)
+ )
)
)
- )
- const helpDoc = pipe(
- banner,
- InternalHelpDoc.sequence(header),
- InternalHelpDoc.sequence(usage),
- InternalHelpDoc.sequence(builtIn.helpDoc),
- InternalHelpDoc.sequence(self.footer)
- )
- return Console.log(InternalHelpDoc.toAnsiText(helpDoc))
- }
- case "ShowCompletionScript": {
- return Console.log("Showing completion script")
- }
- case "ShowCompletions": {
- return Console.log("Showing completions")
- }
- case "ShowWizard": {
- return Console.log("Showing the wizard")
+ const helpDoc = pipe(
+ banner,
+ InternalHelpDoc.sequence(header),
+ InternalHelpDoc.sequence(usage),
+ InternalHelpDoc.sequence(builtIn.helpDoc),
+ InternalHelpDoc.sequence(self.footer)
+ )
+ return yield* _(Console.log(InternalHelpDoc.toAnsiText(helpDoc)))
+ }
+ case "ShowCompletionScript": {
+ const path = yield* _(Path.Path)
+ const commandNames = ReadonlyArray.fromIterable(self.command.names)
+ const programNames = ReadonlyArray.isNonEmptyReadonlyArray(commandNames)
+ ? commandNames
+ : ReadonlyArray.of(self.name)
+ const script = InternalCompletion.getCompletionScript(
+ builtIn.pathToExecutable,
+ programNames,
+ builtIn.shellType,
+ path
+ )
+ return yield* _(Console.log(script))
+ }
+ case "ShowCompletions": {
+ const compgen = yield* _(InternalCompgen.Tag)
+ const env = yield* _(Effect.sync(() => process.env))
+ const tupleOrder = Order.mapInput(Order.number, (tuple: [number, string]) => tuple[0])
+ const compWords = pipe(
+ ReadonlyRecord.collect(
+ env,
+ (key, value) =>
+ key.startsWith("COMP_WORD_") && value !== undefined
+ ? Option.some<[number, string]>([key.replace("COMP_WORD_", "").length, value])
+ : Option.none()
+ ),
+ ReadonlyArray.compact,
+ ReadonlyArray.sortBy(tupleOrder),
+ ReadonlyArray.map(([, value]) => value)
+ )
+ const completions = yield* _(InternalCompletion.getCompletions(
+ compWords,
+ builtIn.index,
+ self.command,
+ config,
+ compgen
+ ))
+ return Effect.forEach(completions, (word) => Console.log(word), { discard: true })
+ }
+ case "ShowWizard": {
+ return yield* _(Console.log("Showing the wizard"))
+ }
}
- }
-}
+ })
const prefixCommand = (self: Command.Command): ReadonlyArray => {
let command: Command.Command | undefined = self
diff --git a/src/internal/command.ts b/src/internal/command.ts
index 0ef86f0..402e1bf 100644
--- a/src/internal/command.ts
+++ b/src/internal/command.ts
@@ -1,3 +1,4 @@
+import type * as FileSystem from "@effect/platform/FileSystem"
import * as Effect from "effect/Effect"
import * as Either from "effect/Either"
import { dual, pipe } from "effect/Function"
@@ -14,6 +15,7 @@ import * as HelpDoc from "../HelpDoc.js"
import type * as Span from "../HelpDoc/Span.js"
import type * as Options from "../Options.js"
import type * as Prompt from "../Prompt.js"
+import type * as RegularLanguage from "../RegularLanguage.js"
import type * as Terminal from "../Terminal.js"
import type * as Usage from "../Usage.js"
import type * as ValidationError from "../ValidationError.js"
@@ -25,6 +27,7 @@ import * as InternalHelpDoc from "./helpDoc.js"
import * as InternalSpan from "./helpDoc/span.js"
import * as InternalOptions from "./options.js"
import * as InternalPrompt from "./prompt.js"
+import * as InternalRegularLanguage from "./regularLanguage.js"
import * as InternalUsage from "./usage.js"
import * as InternalValidationError from "./validationError.js"
@@ -91,7 +94,7 @@ export class Standard
args: ReadonlyArray,
config: CliConfig.CliConfig
): Effect.Effect<
- never,
+ FileSystem.FileSystem,
ValidationError.ValidationError,
CommandDirective.CommandDirective<
Command.Command.ParsedStandardCommand
@@ -120,7 +123,7 @@ export class Standard
const parseBuiltInArgs = (
args: ReadonlyArray
): Effect.Effect<
- never,
+ FileSystem.FileSystem,
ValidationError.ValidationError,
CommandDirective.CommandDirective
> => {
@@ -143,7 +146,7 @@ export class Standard
const parseUserDefinedArgs = (
args: ReadonlyArray
): Effect.Effect<
- never,
+ FileSystem.FileSystem,
ValidationError.ValidationError,
CommandDirective.CommandDirective<
Command.Command.ParsedStandardCommand
@@ -175,7 +178,7 @@ export class Standard
const exhaustiveSearch = (
args: ReadonlyArray
): Effect.Effect<
- never,
+ FileSystem.FileSystem,
ValidationError.ValidationError,
CommandDirective.CommandDirective
> => {
@@ -305,7 +308,7 @@ export class Map implements Command.Command {
args: ReadonlyArray,
config: CliConfig.CliConfig
): Effect.Effect<
- Terminal.Terminal,
+ FileSystem.FileSystem | Terminal.Terminal,
ValidationError.ValidationError,
CommandDirective.CommandDirective
> {
@@ -353,7 +356,7 @@ export class OrElse implements Command.Command {
args: ReadonlyArray,
config: CliConfig.CliConfig
): Effect.Effect<
- Terminal.Terminal,
+ FileSystem.FileSystem | Terminal.Terminal,
ValidationError.ValidationError,
CommandDirective.CommandDirective
> {
@@ -476,7 +479,7 @@ export class Subcommands, B extends Command.Comma
args: ReadonlyArray,
config: CliConfig.CliConfig
): Effect.Effect<
- Terminal.Terminal,
+ FileSystem.FileSystem | Terminal.Terminal,
ValidationError.ValidationError,
CommandDirective.CommandDirective>
> {
@@ -696,6 +699,47 @@ export const orElseEither = dual<
(self: Command.Command, that: Command.Command) => Command.Command>
>(2, (self, that) => orElse(map(self, Either.left), map(that, Either.right)))
+/** @internal */
+export const toRegularLanguage = dual<
+ (allowAlias: boolean) => (self: Command.Command) => RegularLanguage.RegularLanguage,
+ (self: Command.Command, allowAlias: boolean) => RegularLanguage.RegularLanguage
+>(2, (self: Command.Command, allowAlias: boolean): RegularLanguage.RegularLanguage => {
+ if (isStandard(self)) {
+ const commandNameToken = allowAlias
+ ? InternalRegularLanguage.anyString :
+ InternalRegularLanguage.string(self.name)
+ return InternalRegularLanguage.concat(
+ commandNameToken,
+ InternalRegularLanguage.concat(
+ InternalOptions.toRegularLanguage(self.options),
+ InternalArgs.toRegularLanguage(self.args)
+ )
+ )
+ }
+ if (isGetUserInput(self)) {
+ throw Error()
+ }
+ if (isMap(self)) {
+ return toRegularLanguage(self.command, allowAlias)
+ }
+ if (isOrElse(self)) {
+ return InternalRegularLanguage.orElse(
+ toRegularLanguage(self.left, allowAlias),
+ toRegularLanguage(self.right, allowAlias)
+ )
+ }
+ if (isSubcommands(self)) {
+ return InternalRegularLanguage.concat(
+ toRegularLanguage(self.parent, allowAlias),
+ toRegularLanguage(self.child, false)
+ )
+ }
+ throw new Error(
+ "[BUG]: Command.toRegularLanguage - received unrecognized " +
+ `command ${JSON.stringify(self)}`
+ )
+})
+
/** @internal */
export const withHelp = dual<
(help: string | HelpDoc.HelpDoc) => (self: Command.Command) => Command.Command,
diff --git a/src/internal/compgen.ts b/src/internal/compgen.ts
new file mode 100644
index 0000000..45fd8fa
--- /dev/null
+++ b/src/internal/compgen.ts
@@ -0,0 +1,86 @@
+import * as Command from "@effect/platform/Command"
+import * as CommandExecutor from "@effect/platform/CommandExecutor"
+import type * as PlatformError from "@effect/platform/Error"
+import * as Context from "effect/Context"
+import * as Effect from "effect/Effect"
+import { pipe } from "effect/Function"
+import * as Layer from "effect/Layer"
+import * as Option from "effect/Option"
+import * as ReadonlyArray from "effect/ReadonlyArray"
+import type * as Compgen from "../Compgen.js"
+
+// =============================================================================
+// Constructors
+// =============================================================================
+
+/** @internal */
+export const make = (workingDirectory: Option.Option): Effect.Effect<
+ CommandExecutor.CommandExecutor,
+ never,
+ Compgen.Compgen
+> =>
+ Effect.gen(function*(_) {
+ const executor = yield* _(CommandExecutor.CommandExecutor)
+
+ const runShellCommand = (
+ command: string
+ ): Effect.Effect> => {
+ const cmd = Option.match(workingDirectory, {
+ onNone: () =>
+ Command.make(command).pipe(
+ Command.runInShell("/bin/bash")
+ ),
+ onSome: (cwd) =>
+ Command.make(command).pipe(
+ Command.workingDirectory(cwd),
+ Command.runInShell("/bin/bash")
+ )
+ })
+ return executor.lines(cmd)
+ }
+
+ const completeDirectoryNames = (
+ word: string
+ ): Effect.Effect> =>
+ runShellCommand(`compgen -o nospace -d -S / -- ${word}`)
+
+ const completeFileNames = (
+ word: string
+ ): Effect.Effect> =>
+ Effect.gen(function*(_) {
+ // For file names, we want the cursor to skip forward to the next
+ // argument position, so we append a space (" ") to the end of them
+ // below. For directory names, however, we don't want to skip to the
+ // next argument position, because we like being able to smash the tab
+ // key to keep walking down through a directory tree.
+ const files = yield* _(runShellCommand(`compgen -f -- ${word}`))
+ const directories = yield* _(completeDirectoryNames(word))
+ const directorySet = new Set(directories)
+ const filesFiltered = ReadonlyArray.filter(files, (file) => !directorySet.has(`${file}/`))
+ return pipe(
+ ReadonlyArray.map(filesFiltered, (file) => `${file} `),
+ ReadonlyArray.appendAll(directories)
+ )
+ })
+
+ return Tag.of({
+ completeFileNames,
+ completeDirectoryNames
+ })
+ })
+
+// =============================================================================
+// Context
+// =============================================================================
+
+/** @internal */
+export const Tag = Context.Tag()
+
+/** @internal */
+export const LiveCompgen: Layer.Layer =
+ Layer.effect(Tag, make(Option.none()))
+
+export const TestCompgen = (
+ workingDirectory: string
+): Layer.Layer =>
+ Layer.effect(Tag, make(Option.some(workingDirectory)))
diff --git a/src/internal/completion.ts b/src/internal/completion.ts
new file mode 100644
index 0000000..7c348cf
--- /dev/null
+++ b/src/internal/completion.ts
@@ -0,0 +1,113 @@
+import type * as FileSystem from "@effect/platform/FileSystem"
+import type * as Path from "@effect/platform/Path"
+import * as Effect from "effect/Effect"
+import { pipe } from "effect/Function"
+import * as Order from "effect/Order"
+import * as ReadonlyArray from "effect/ReadonlyArray"
+import * as String from "effect/String"
+import type * as CliConfig from "../CliConfig.js"
+import type * as Command from "../Command.js"
+import type * as Compgen from "../Compgen.js"
+import type * as ShellType from "../ShellType.js"
+import * as InternalCommand from "./command.js"
+import * as InternalRegularLanguage from "./regularLanguage.js"
+
+/** @internal */
+export const getCompletions = (
+ words: ReadonlyArray,
+ index: number,
+ command: Command.Command,
+ config: CliConfig.CliConfig,
+ compgen: Compgen.Compgen
+): Effect.Effect> => {
+ // Split the input words into two chunks:
+ // 1. The chunk that is strictly before the cursor, and
+ // 2. The chunk that is at or after the cursor.
+ const [splitted, _] = ReadonlyArray.splitAt(words, index)
+ // Calculate the `RegularLanguage` corresponding to the input command.
+ // Here, we allow the top-most `Command.Single.name` field to vary by setting
+ // `allowAlias = true`. This is because the first argument will be the name
+ // of the executable that is provided when the shell makes a tab completion
+ // request. Without doing so, tab completion would fail if the executable
+ // were renamed or invoked via an alias.
+ const language = InternalCommand.toRegularLanguage(command, true)
+ // Repeatedly differentiate the language w.r.t. each of the tokens that
+ // occur before the cursor.
+ const derivative = Effect.reduce(
+ splitted,
+ language,
+ (lang, word) => InternalRegularLanguage.derive(lang, word, config)
+ )
+ // Determine the word to complete
+ const wordToComplete = index >= 0 && index < words.length ? words[index]! : ""
+ // Finally, obtain the list of completions for the wordToComplete by:
+ // 1. Getting the list of all of the first tokens in the derivative
+ // 2. Retaining only those tokens that start with wordToComplete.
+ return derivative.pipe(
+ Effect.flatMap((lang) => InternalRegularLanguage.firstTokens(lang, wordToComplete, compgen)),
+ Effect.map((completions) =>
+ pipe(
+ ReadonlyArray.fromIterable(completions),
+ ReadonlyArray.sort(Order.string),
+ ReadonlyArray.filter((str) => str.length > 0)
+ )
+ )
+ )
+}
+
+/** @internal */
+export const getCompletionScript = (
+ pathToExecutable: string,
+ programNames: ReadonlyArray.NonEmptyReadonlyArray,
+ shellType: ShellType.ShellType,
+ path: Path.Path
+): string => {
+ switch (shellType._tag) {
+ case "Bash": {
+ return createBashCompletionScript(pathToExecutable, programNames, path)
+ }
+ case "Fish":
+ case "Zsh": {
+ throw new Error("Not implemented")
+ }
+ }
+}
+
+// =============================================================================
+// Internals
+// =============================================================================
+
+const createBashCompletionScript = (
+ pathToExecutable: string,
+ programNames: ReadonlyArray.NonEmptyReadonlyArray,
+ path: Path.Path
+): string => {
+ const script = String.stripMargin(
+ `|#!/usr/bin/env bash
+ |_${ReadonlyArray.headNonEmpty(programNames)}()
+ |{
+ | local CMDLINE
+ | local IFS=$'\n'
+ | CMDLINE=(--shell-type bash --shell-completion-index $COMP_CWORD)
+ |
+ | INDEX=0
+ | for arg in \${COMP_WORDS[@]}; do
+ | export COMP_WORD_$INDEX=\${arg}
+ | (( INDEX++ ))
+ | done
+ |
+ | COMPREPLY=( $(${pathToExecutable} "\${CMDLINE[@]}") )
+ |
+ | # Unset the environment variables.
+ | unset $(compgen -v | grep "^COMP_WORD_")
+ |}
+ |`
+ )
+ return pipe(
+ ReadonlyArray.prepend(programNames, script),
+ ReadonlyArray.map((programName) =>
+ `complete -F _${ReadonlyArray.headNonEmpty(programNames)} ${programName}`
+ ),
+ ReadonlyArray.join(path.sep)
+ )
+}
diff --git a/src/internal/options.ts b/src/internal/options.ts
index 3e898c2..0e79e80 100644
--- a/src/internal/options.ts
+++ b/src/internal/options.ts
@@ -1,3 +1,4 @@
+import type * as FileSystem from "@effect/platform/FileSystem"
import * as Schema from "@effect/schema/Schema"
import * as Effect from "effect/Effect"
import * as Either from "effect/Either"
@@ -12,6 +13,7 @@ import type * as HelpDoc from "../HelpDoc.js"
import type * as Options from "../Options.js"
import type * as Parameter from "../Parameter.js"
import type * as Primitive from "../Primitive.js"
+import type * as RegularLanguage from "../RegularLanguage.js"
import type * as Usage from "../Usage.js"
import type * as ValidationError from "../ValidationError.js"
import * as InternalAutoCorrect from "./autoCorrect.js"
@@ -19,6 +21,7 @@ import * as InternalCliConfig from "./cliConfig.js"
import * as InternalHelpDoc from "./helpDoc.js"
import * as InternalSpan from "./helpDoc/span.js"
import * as InternalPrimitive from "./primitive.js"
+import * as InternalRegularLanguage from "./regularLanguage.js"
import * as InternalUsage from "./usage.js"
import * as InternalValidationError from "./validationError.js"
@@ -203,7 +206,7 @@ export class Single implements Options.Options, Parameter.Input {
validate(
args: HashMap.HashMap>,
config: CliConfig.CliConfig
- ): Effect.Effect {
+ ): Effect.Effect {
const names = ReadonlyArray.filterMap(this.names, (name) => HashMap.get(args, name))
if (ReadonlyArray.isNonEmptyReadonlyArray(names)) {
const head = ReadonlyArray.headNonEmpty(names)
@@ -289,7 +292,7 @@ export class Map implements Options.Options {
validate(
args: HashMap.HashMap>,
config: CliConfig.CliConfig
- ): Effect.Effect {
+ ): Effect.Effect {
return this.options.validate(args, config).pipe(Effect.flatMap((a) => this.f(a)))
}
@@ -339,7 +342,7 @@ export class OrElse implements Options.Options> {
validate(
args: HashMap.HashMap>,
config: CliConfig.CliConfig
- ): Effect.Effect> {
+ ): Effect.Effect> {
return this.left.validate(args, config).pipe(
Effect.matchEffect({
onFailure: (err1) =>
@@ -424,7 +427,7 @@ export class Both implements Options.Options {
validate(
args: HashMap.HashMap>,
config: CliConfig.CliConfig
- ): Effect.Effect {
+ ): Effect.Effect {
return this.left.validate(args, config).pipe(
Effect.catchAll((err1) =>
this.right.validate(args, config).pipe(Effect.matchEffect({
@@ -521,7 +524,7 @@ export class WithDefault implements Options.Options, Parameter.Input {
validate(
args: HashMap.HashMap>,
config: CliConfig.CliConfig
- ): Effect.Effect {
+ ): Effect.Effect {
return this.options.validate(args, config).pipe(
Effect.catchTag("MissingValue", () => Effect.succeed(this.fallback))
)
@@ -570,7 +573,7 @@ export class KeyValueMap
isValid(
input: string,
config: CliConfig.CliConfig
- ): Effect.Effect> {
+ ): Effect.Effect> {
const identifier = Option.getOrElse(this.identifier, () => "")
const args = input.split(" ")
return this.validate(HashMap.make([identifier, args]), config).pipe(
@@ -637,7 +640,11 @@ export class KeyValueMap
validate(
args: HashMap.HashMap>,
config: CliConfig.CliConfig
- ): Effect.Effect> {
+ ): Effect.Effect<
+ FileSystem.FileSystem,
+ ValidationError.ValidationError,
+ HashMap.HashMap
+ > {
const extractKeyValue = (
keyValue: string
): Effect.Effect => {
@@ -742,7 +749,7 @@ const defaultBooleanOptions = {
/** @internal */
export const boolean = (
name: string,
- options: Options.Options.BooleanOptionConfig = {}
+ options: Options.Options.BooleanOptionsConfig = {}
): Options.Options => {
const { aliases, ifPresent, negationNames } = { ...defaultBooleanOptions, ...options }
const option = new Single(
@@ -785,6 +792,28 @@ export const choiceWithValue = =>
new Single(name, ReadonlyArray.empty(), InternalPrimitive.date)
+/** @internal */
+export const directory = (
+ name: string,
+ config: Options.Options.PathOptionsConfig
+): Options.Options =>
+ new Single(
+ name,
+ ReadonlyArray.empty(),
+ InternalPrimitive.path("directory", config.exists || "either")
+ )
+
+/** @internal */
+export const file = (
+ name: string,
+ config: Options.Options.PathOptionsConfig
+): Options.Options