├── .ci ├── build-platform.yml ├── cross-release.yml ├── esy-build-steps.yml ├── opam-build-steps.yml ├── pipelines-release.js ├── release-platform-setup.yml ├── release-postinstall.js └── utils │ ├── create-docs.yml │ ├── publish-build-cache.yml │ ├── restore-build-cache.yml │ ├── use-esy.yml │ └── use-node.yml ├── .ds-root ├── .gitignore ├── .vscode └── settings.json ├── ESY.md ├── LICENSE ├── MIT-LICENSE ├── ORIGINS.md ├── README.md ├── azure-pipelines.yml ├── bin.opam ├── ds.opam ├── dune-project ├── esy.json ├── esy.lock ├── .gitattributes ├── .gitignore ├── index.json ├── opam │ ├── astring.0.8.3 │ │ └── opam │ ├── atd.2.0.0 │ │ └── opam │ ├── atdgen-runtime.2.0.0 │ │ └── opam │ ├── atdgen.2.0.0 │ │ └── opam │ ├── base-bytes.base │ │ └── opam │ ├── base-threads.base │ │ └── opam │ ├── base-unix.base │ │ └── opam │ ├── biniou.1.2.1 │ │ └── opam │ ├── cmdliner.1.0.4 │ │ └── opam │ ├── conf-m4.1 │ │ └── opam │ ├── cppo.1.6.6 │ │ └── opam │ ├── dune-configurator.1.0.0 │ │ └── opam │ ├── dune.1.11.4 │ │ └── opam │ ├── easy-format.1.3.2 │ │ └── opam │ ├── fpath.0.7.2 │ │ └── opam │ ├── jbuilder.transition │ │ └── opam │ ├── junit.2.0.1 │ │ └── opam │ ├── lwt.4.3.0 │ │ └── opam │ ├── lwt_ppx.1.2.3 │ │ └── opam │ ├── menhir.20190924 │ │ └── opam │ ├── merlin-extend.0.5 │ │ └── opam │ ├── merlin.3.3.3 │ │ └── opam │ ├── mmap.1.1.0 │ │ └── opam │ ├── ocaml-migrate-parsetree.1.5.0 │ │ └── opam │ ├── ocamlbuild.0.14.0 │ │ └── opam │ ├── ocamlfind.1.8.1 │ │ ├── files │ │ │ ├── ocaml-stub │ │ │ └── ocamlfind.install │ │ └── opam │ ├── ocplib-endian.1.0 │ │ └── opam │ ├── odoc.1.4.2 │ │ └── opam │ ├── ppx_derivers.1.2.1 │ │ └── opam │ ├── ppx_tools_versioned.5.2.3 │ │ └── opam │ ├── ptime.0.8.5 │ │ └── opam │ ├── re.1.9.0 │ │ └── opam │ ├── result.1.4 │ │ └── opam │ ├── seq.base │ │ ├── files │ │ │ ├── META.seq │ │ │ └── seq.install │ │ └── opam │ ├── topkg.1.0.1 │ │ └── opam │ ├── tyxml.4.3.0 │ │ └── opam │ ├── uchar.0.0.2 │ │ └── opam │ ├── uutf.1.0.2 │ │ └── opam │ └── yojson.1.7.0 │ │ └── opam └── overrides │ ├── opam__s__ocamlbuild_opam__c__0.14.0_opam_override │ ├── files │ │ └── ocamlbuild-0.14.0.patch │ └── package.json │ ├── opam__s__ocamlfind_opam__c__1.8.1_opam_override │ ├── files │ │ └── findlib-1.8.1.patch │ └── package.json │ └── opam__s__ocplib_endian_opam__c__1.0_opam_override │ ├── files │ ├── esy-fix.patch │ └── ocplib-endian-0.8.patch │ └── package.json ├── example-basic.opam ├── example-lwt.opam ├── rarg.json ├── rarg.opam ├── rarg.png ├── scripts ├── esy-prepublish.js ├── test-ci.cmd └── test-dev.cmd ├── src ├── examples │ ├── example-basic │ │ ├── ExampleBasic.re │ │ ├── cmds │ │ │ ├── ExampleBasicCmds.re │ │ │ └── dune │ │ └── dune │ └── example-lwt │ │ ├── ExampleLwt.re │ │ ├── cmds │ │ ├── ExampleLwtCmds.re │ │ └── dune │ │ └── dune ├── rarg │ ├── Args.re │ ├── Args.rei │ ├── ArgsMap.re │ ├── Argv.re │ ├── Cmd.re │ ├── Cmd.rei │ ├── CmdInternal.re │ ├── Components.re │ ├── ComponentsErrors.re │ ├── ComponentsHelp.re │ ├── ComponentsTips.re │ ├── Rarg.re │ ├── RargAPI.re │ ├── Recommendations.re │ ├── Run.re │ ├── Run.rei │ ├── Suggestions.re │ ├── TerminalTemplates.re │ ├── Type.re │ ├── Type.rei │ ├── ValidateArgs.re │ └── dune └── seed │ ├── Arr.re │ ├── Chan.re │ ├── DataStructures.re │ ├── Env.re │ ├── Fs.re │ ├── Lst.re │ ├── Option.re │ ├── Os.re │ ├── Print.re │ ├── Process.re │ ├── ProjectRoot.re │ ├── Result.re │ ├── Strings.re │ └── dune ├── test ├── tests.opam └── tests ├── TestCi.re ├── TestDev.re ├── TestFramework.re ├── __snapshots__ ├── RargCmd_validate.d5f8c28a.0.snapshot ├── RargComponentsErrors_DuplicateArgs.0b441d9a.0.snapshot ├── RargComponentsErrors_DuplicateArgs.add36454.0.snapshot ├── RargComponentsErrors_InvalidArgNames.17d3d187.0.snapshot ├── RargComponentsErrors_InvalidArgNames.b4b00a55.0.snapshot ├── RargComponentsErrors_UnknownArgs_no_positionals.46a940a4.0.snapshot ├── RargComponentsErrors_UnknownArgs_no_positionals.ada734dc.0.snapshot ├── RargComponentsErrors_UnknownArgs_with_positionals.1bfe4fbf.0.snapshot ├── RargComponentsErrors_UnknownArgs_with_positionals.2a7d66ba.0.snapshot ├── RargComponentsErrors_UnknownArgs_with_positionals.6103095a.0.snapshot ├── RargComponentsErrors_ValidationError_ArgsCount.54516e19.0.snapshot ├── RargComponentsErrors_ValidationError_ArgsCount.713f94b1.0.snapshot ├── RargComponentsErrors_ValidationError_ArgsCount.7825cfbd.0.snapshot ├── RargComponentsErrors_ValidationError_ArgsCount.f1a29b69.0.snapshot ├── RargComponentsHelp_Help.68d44ef8.0.snapshot ├── RargComponentsHelp_Help.6ded9ba6.0.snapshot ├── RargComponentsHelp_Help.c75256d5.0.snapshot ├── RargComponentsHelp_Help.e83467b9.0.snapshot ├── RargComponentsTips_AddPathScript.3666b3eb.0.snapshot ├── RargComponentsTips_AddPathScript.e18b822c.0.snapshot ├── RargComponentsTips_AddPathTip.05dc030a.0.snapshot ├── RargComponentsTips_AddPathTip.2a353412.0.snapshot ├── RargComponentsTips_AddPathTip.37293e9c.0.snapshot ├── RargComponentsTips_AddPathTip.5ecbee85.0.snapshot ├── RargRun_Error.12ef018d.0.snapshot ├── RargRun_Error.8d25981c.0.snapshot ├── RargRun_Error.9aabc109.0.snapshot ├── RargRun_Error.c49fb6ae.0.snapshot ├── RargRun_Error.cd367d91.0.snapshot ├── RargRun_Error.f3640ab1.0.snapshot ├── RargRun_Ok.13df237a.0.snapshot ├── RargRun_Ok.161880a3.0.snapshot ├── RargRun_Ok.237800af.0.snapshot ├── RargRun_Ok.4d153a45.0.snapshot ├── RargRun_Ok.7ddf4c4a.0.snapshot ├── RargRun_Ok.a14be28c.0.snapshot ├── RargRun_Ok.b07dfa75.0.snapshot └── RargRun_Ok.c505ca7a.0.snapshot ├── __tests__ ├── rarg │ ├── Rarg_ArgsMap_test.re │ ├── Rarg_CmdInternal_Validate_test.re │ ├── Rarg_CmdInternal_test.re │ ├── Rarg_Cmd_test.re │ ├── Rarg_ComponentsErrors_test.re │ ├── Rarg_ComponentsHelp_test.re │ ├── Rarg_ComponentsTips_test.re │ ├── Rarg_FakeCommands.re │ ├── Rarg_Recommendations_test.re │ ├── Rarg_Run_test.re │ ├── Rarg_Suggestions_test.re │ └── Rarg_ValidateArgs_test.re └── seed │ ├── Seed_Arr_test.re │ ├── Seed_Lst_test.re │ └── Seed_Strings_test.re └── dune /.ci/build-platform.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | platform: "macOS" 3 | vmImage: "macOS-10.13" 4 | 5 | jobs: 6 | - job: ${{ parameters.platform }} 7 | pool: 8 | vmImage: ${{ parameters.vmImage }} 9 | demands: node.js 10 | timeoutInMinutes: 120 # This is mostly for Windows 11 | steps: 12 | - powershell: $Env:Path 13 | continueOnError: true 14 | condition: and(eq(variables['AGENT.OS'], 'Windows_NT'), and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch'])))) 15 | displayName: "Print env in powershell" 16 | # Needed so that the mingw tar doesn't shadow the system tar. See 17 | # pipelines.yaml. We need windows bsdtar from system32, not the mingw 18 | # one. Note powershell doesn't need escaping of backslashes. 19 | - powershell: Write-Host "##vso[task.setvariable variable=PATH;]C:\Windows\system32;${env:PATH}" 20 | continueOnError: true 21 | condition: eq(variables['AGENT.OS'], 'Windows_NT') 22 | displayName: "Make sure windows/system32 is at front of path if windows" 23 | - powershell: $Env:Path 24 | continueOnError: true 25 | condition: and(eq(variables['AGENT.OS'], 'Windows_NT'), and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch'])))) 26 | displayName: "Print env in powershell" 27 | - powershell: get-command tar 28 | continueOnError: true 29 | condition: and(eq(variables['AGENT.OS'], 'Windows_NT'), and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch'])))) 30 | displayName: "Print where tar is located" 31 | - powershell: tar --help 32 | continueOnError: true 33 | condition: and(eq(variables['AGENT.OS'], 'Windows_NT'), and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch'])))) 34 | displayName: "Print tar help" 35 | - bash: | 36 | # COMPUTE THE ESY INSTALL CACHE LOCATION AHEAD OF TIME 37 | DESIRED_LEN="86" 38 | # Note: This will need to change when upgrading esy version 39 | # that reenables long paths on windows. 40 | if [ "$AGENT_OS" == "Windows_NT" ]; then 41 | DESIRED_LEN="33" 42 | fi 43 | HOME_ESY3="$HOME/.esy/3" 44 | HOME_ESY3_LEN=${#HOME_ESY3} 45 | NUM_UNDERS=$(echo "$(($DESIRED_LEN-$HOME_ESY3_LEN))") 46 | UNDERS=$(printf "%-${NUM_UNDERS}s" "_") 47 | UNDERS="${UNDERS// /_}" 48 | THE_ESY__CACHE_INSTALL_PATH=${HOME_ESY3}${UNDERS}/i 49 | if [ "$AGENT_OS" == "Windows_NT" ]; then 50 | THE_ESY__CACHE_INSTALL_PATH=$( cygpath --mixed --absolute "$THE_ESY__CACHE_INSTALL_PATH") 51 | fi 52 | echo "THE_ESY__CACHE_INSTALL_PATH: $THE_ESY__CACHE_INSTALL_PATH" 53 | # This will be exposed as an env var ESY__CACHE_INSTALL_PATH, or an 54 | # Azure var esy__cache_install_path 55 | echo "##vso[task.setvariable variable=esy__cache_install_path]$THE_ESY__CACHE_INSTALL_PATH" 56 | # - bash: | 57 | # which esy 58 | # echo "$( which esy )" 59 | # echo "##vso[task.setvariable variable=esy_bin_location]$(which esy)" 60 | # displayName: "Find esy binary" 61 | # - bash: echo ${ESY_BIN_LOCATION} 62 | # displayName: "Print esy bin location" 63 | - bash: env 64 | displayName: "Print environment" 65 | - template: utils/use-node.yml 66 | - template: utils/use-esy.yml 67 | - script: "esy install" 68 | displayName: "esy install" 69 | - template: utils/restore-build-cache.yml 70 | - script: "esy build" 71 | displayName: "esy build" 72 | - template: utils/create-docs.yml 73 | - script: "esy test" 74 | displayName: "Test command" 75 | # - script: "esy release" 76 | # displayName: "esy release" 77 | - template: utils/publish-build-cache.yml 78 | # - task: PublishBuildArtifacts@1 79 | # displayName: "Publish Artifact: ${{ parameters.platform }}" 80 | # inputs: 81 | # PathtoPublish: "_release" 82 | # ArtifactName: ${{ parameters.platform }} 83 | -------------------------------------------------------------------------------- /.ci/cross-release.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - template: utils/use-node.yml 3 | 4 | - script: "mkdir _release" 5 | displayName: "Create _release dir" 6 | 7 | - template: release-platform-setup.yml 8 | parameters: 9 | platform: "Linux" 10 | folder: "platform-linux" 11 | 12 | - template: release-platform-setup.yml 13 | parameters: 14 | platform: "macOS" 15 | folder: "platform-darwin" 16 | 17 | - template: release-platform-setup.yml 18 | parameters: 19 | platform: "Windows" 20 | folder: "platform-windows-x64" 21 | 22 | - script: "node .ci/pipelines-release.js" 23 | displayName: "node .ci/pipelines-release.js" 24 | continueOnError: true 25 | 26 | - script: "npm pack ." 27 | displayName: "npm pack" 28 | workingDirectory: "_release" 29 | 30 | - task: PublishBuildArtifacts@1 31 | displayName: "Publish Artifact: Release" 32 | inputs: 33 | PathtoPublish: "_release" 34 | ArtifactName: Release 35 | -------------------------------------------------------------------------------- /.ci/esy-build-steps.yml: -------------------------------------------------------------------------------- 1 | # Cross-platform set of build steps for building esy projects 2 | 3 | steps: 4 | - template: utils/use-node.yml 5 | - template: utils/use-esy.yml 6 | - template: utils/restore-build-cache.yml 7 | - script: esy install 8 | displayName: 'esy install' 9 | - script: esy build 10 | displayName: 'esy build' 11 | - template: utils/publish-build-cache.yml 12 | - script: esy x refmt --version 13 | displayName: 'esy x refmt --version' 14 | # Run tests or any additional steps here 15 | # - script: esy b dune runtest 16 | -------------------------------------------------------------------------------- /.ci/opam-build-steps.yml: -------------------------------------------------------------------------------- 1 | # Shared steps for building OPAM projects 2 | 3 | steps: 4 | - task: NodeTool@0 5 | inputs: 6 | versionSpec: '8.9' 7 | - script: brew install gpatch 8 | displayName: 'brew install gpatch' 9 | - script: brew install opam 10 | displayName: 'brew install opam' 11 | - script: opam --version 12 | displayName: 'Check opam version' 13 | - script: opam init --auto-setup --dot-profile=~/.bash_profile 14 | displayName: 'opam init' 15 | - script: opam remote add ocamlorg https://opam.ocaml.org || true 16 | displayName: 'opam remote add ocamlorg' 17 | - script: opam remove default || true 18 | displayName: 'opam remove default' 19 | - script: opam update 20 | displayName: 'opam update' 21 | - script: opam switch create $(OCAML_VERSION) 22 | displayName: 'Use OCaml version: $(OCAML_VERSION)' 23 | - script: opam install -y dune 24 | displayName: 'opam install -y dune' 25 | - script: opam install -y menhir 26 | displayName: 'opam install -y menhir' 27 | - script: opam install -y utop 28 | displayName: 'opam install -y utop' 29 | - script: make clean-for-ci 30 | displayName: 'make clean-for-ci' 31 | - script: opam pin add -y reason . 32 | displayName: "opam pin add -y reason ." 33 | - script: opam pin add -y rtop . 34 | displayName: "opam pin add -y rtop ." 35 | - script: eval $(opam env); make test-ci 36 | displayName: "make test-ci" 37 | - script: git diff --exit-code 38 | displayName: "Check git is clean" 39 | -------------------------------------------------------------------------------- /.ci/pipelines-release.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | console.log("Creating package.json"); 5 | 6 | // From the project root pwd 7 | const mainPackageJsonPath = 8 | fs.existsSync('esy.json') ? 9 | 'esy.json' : 'package.json'; 10 | 11 | const exists = fs.existsSync(mainPackageJsonPath); 12 | if (!exists) { 13 | console.error("No package.json or esy.json at " + mainPackageJsonPath); 14 | process.exit(1); 15 | } 16 | // Now require from this script's location. 17 | const mainPackageJson = require(path.join('..', mainPackageJsonPath)); 18 | const bins = 19 | Array.isArray(mainPackageJson.esy.release.bin) ? 20 | mainPackageJson.esy.release.bin.reduce( 21 | (acc, curr) => Object.assign({ [curr]: "bin/" + curr }, acc), 22 | {} 23 | ) : 24 | Object.keys(mainPackageJson.esy.release.bin).reduce( 25 | (acc, currKey) => Object.assign({ [currKey]: "bin/" + mainPackageJson.esy.release.bin[currKey] }, acc), 26 | {} 27 | ); 28 | 29 | const rewritePrefix = 30 | mainPackageJson.esy && 31 | mainPackageJson.esy.release && 32 | mainPackageJson.esy.release.rewritePrefix; 33 | 34 | const packageJson = JSON.stringify( 35 | { 36 | name: mainPackageJson.name, 37 | version: mainPackageJson.version, 38 | license: mainPackageJson.license, 39 | description: mainPackageJson.description, 40 | repository: mainPackageJson.repository, 41 | scripts: { 42 | postinstall: 43 | rewritePrefix ? 44 | "ESY_RELEASE_REWRITE_PREFIX=true node ./postinstall.js" : 45 | "node ./postinstall.js" 46 | }, 47 | bin: bins, 48 | files: [ 49 | "_export/", 50 | "bin/", 51 | "postinstall.js", 52 | "esyInstallRelease.js", 53 | "platform-linux/", 54 | "platform-darwin/", 55 | "platform-windows-x64/" 56 | ] 57 | }, 58 | null, 59 | 2 60 | ); 61 | 62 | fs.writeFileSync( 63 | path.join(__dirname, "..", "_release", "package.json"), 64 | packageJson, 65 | { 66 | encoding: "utf8" 67 | } 68 | ); 69 | 70 | try { 71 | console.log("Copying LICENSE"); 72 | fs.copyFileSync( 73 | path.join(__dirname, "..", "LICENSE"), 74 | path.join(__dirname, "..", "_release", "LICENSE") 75 | ); 76 | } catch (e) { 77 | console.warn("No LICENSE found"); 78 | } 79 | 80 | console.log("Copying README.md"); 81 | fs.copyFileSync( 82 | path.join(__dirname, "..", "README.md"), 83 | path.join(__dirname, "..", "_release", "README.md") 84 | ); 85 | 86 | console.log("Copying postinstall.js"); 87 | fs.copyFileSync( 88 | path.join(__dirname, "release-postinstall.js"), 89 | path.join(__dirname, "..", "_release", "postinstall.js") 90 | ); 91 | 92 | console.log("Creating placeholder files"); 93 | const placeholderFile = `:; echo "You need to have postinstall enabled"; exit $? 94 | @ECHO OFF 95 | ECHO You need to have postinstall enabled`; 96 | fs.mkdirSync(path.join(__dirname, "..", "_release", "bin")); 97 | 98 | Object.keys(bins).forEach( 99 | name => { 100 | if(bins[name]) { 101 | const binPath = path.join( 102 | __dirname, 103 | "..", 104 | "_release", 105 | bins[name] 106 | ); 107 | fs.writeFileSync(binPath, placeholderFile); 108 | fs.chmodSync(binPath, 0777); 109 | } else { 110 | console.log("bins[name] name=" + name + " was empty. Weird."); 111 | console.log(bins); 112 | } 113 | } 114 | ); 115 | -------------------------------------------------------------------------------- /.ci/release-platform-setup.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | platform: "macOS" 3 | folder: "platform-darwin" 4 | 5 | steps: 6 | - task: DownloadBuildArtifacts@0 7 | displayName: "Download ${{ parameters.platform }} Artifacts" 8 | inputs: 9 | artifactName: ${{ parameters.platform }} 10 | downloadPath: $(Build.StagingDirectory) 11 | 12 | - script: "mkdir _release/${{ parameters.folder }}" 13 | displayName: "Create _release/${{ parameters.folder }}" 14 | 15 | - script: "cp -r $(Build.StagingDirectory)/${{ parameters.platform }}/ _release/${{ parameters.folder }}" 16 | displayName: "cp ${{ parameters.platform }}" 17 | -------------------------------------------------------------------------------- /.ci/release-postinstall.js: -------------------------------------------------------------------------------- 1 | /** 2 | * release-postinstall.js 3 | * 4 | * XXX: We want to keep this script installable at least with node 4.x. 5 | * 6 | * This script is bundled with the `npm` package and executed on release. 7 | * Since we have a 'fat' NPM package (with all platform binaries bundled), 8 | * this postinstall script extracts them and puts the current platform's 9 | * bits in the right place. 10 | */ 11 | 12 | var path = require("path"); 13 | var cp = require("child_process"); 14 | var fs = require("fs"); 15 | var os = require("os"); 16 | var platform = process.platform; 17 | 18 | var packageJson = require("./package.json"); 19 | var binariesToCopy = Object.keys(packageJson.bin) 20 | .map(function(name) { 21 | return packageJson.bin[name]; 22 | }) 23 | .concat(["esyInstallRelease.js"]); 24 | var foldersToCopy = ["bin", "_export"]; 25 | 26 | function copyRecursive(srcDir, dstDir) { 27 | var results = []; 28 | var list = fs.readdirSync(srcDir); 29 | var src, dst; 30 | list.forEach(function(file) { 31 | src = path.join(srcDir, file); 32 | dst = path.join(dstDir, file); 33 | 34 | var stat = fs.statSync(src); 35 | if (stat && stat.isDirectory()) { 36 | try { 37 | fs.mkdirSync(dst); 38 | } catch (e) { 39 | console.log("directory already exists: " + dst); 40 | console.error(e); 41 | } 42 | results = results.concat(copyRecursive(src, dst)); 43 | } else { 44 | try { 45 | fs.writeFileSync(dst, fs.readFileSync(src)); 46 | } catch (e) { 47 | console.log("could't copy file: " + dst); 48 | console.error(e); 49 | } 50 | results.push(src); 51 | } 52 | }); 53 | return results; 54 | } 55 | 56 | /** 57 | * Since os.arch returns node binary's target arch, not 58 | * the system arch. 59 | * Credits: https://github.com/feross/arch/blob/af080ff61346315559451715c5393d8e86a6d33c/index.js#L10-L58 60 | */ 61 | 62 | function arch() { 63 | /** 64 | * The running binary is 64-bit, so the OS is clearly 64-bit. 65 | */ 66 | if (process.arch === "x64") { 67 | return "x64"; 68 | } 69 | 70 | /** 71 | * All recent versions of Mac OS are 64-bit. 72 | */ 73 | if (process.platform === "darwin") { 74 | return "x64"; 75 | } 76 | 77 | /** 78 | * On Windows, the most reliable way to detect a 64-bit OS from within a 32-bit 79 | * app is based on the presence of a WOW64 file: %SystemRoot%\SysNative. 80 | * See: https://twitter.com/feross/status/776949077208510464 81 | */ 82 | if (process.platform === "win32") { 83 | var useEnv = false; 84 | try { 85 | useEnv = !!( 86 | process.env.SYSTEMROOT && fs.statSync(process.env.SYSTEMROOT) 87 | ); 88 | } catch (err) {} 89 | 90 | var sysRoot = useEnv ? process.env.SYSTEMROOT : "C:\\Windows"; 91 | 92 | // If %SystemRoot%\SysNative exists, we are in a WOW64 FS Redirected application. 93 | var isWOW64 = false; 94 | try { 95 | isWOW64 = !!fs.statSync(path.join(sysRoot, "sysnative")); 96 | } catch (err) {} 97 | 98 | return isWOW64 ? "x64" : "x86"; 99 | } 100 | 101 | /** 102 | * On Linux, use the `getconf` command to get the architecture. 103 | */ 104 | if (process.platform === "linux") { 105 | var output = cp.execSync("getconf LONG_BIT", { encoding: "utf8" }); 106 | return output === "64\n" ? "x64" : "x86"; 107 | } 108 | 109 | /** 110 | * If none of the above, assume the architecture is 32-bit. 111 | */ 112 | return "x86"; 113 | } 114 | 115 | // implementing it b/c we don't want to depend on fs.copyFileSync which appears 116 | // only in node@8.x 117 | function copyFileSync(sourcePath, destPath) { 118 | var data; 119 | try { 120 | data = fs.readFileSync(sourcePath); 121 | } catch (e) { 122 | console.log("Couldn't find " + sourcePath + " trying with .exe"); 123 | data = fs.readFileSync(sourcePath + ".exe"); 124 | sourcePath = sourcePath + ".exe"; 125 | } 126 | var stat = fs.statSync(sourcePath); 127 | fs.writeFileSync(destPath, data); 128 | fs.chmodSync(destPath, stat.mode); 129 | } 130 | 131 | var copyPlatformBinaries = platformPath => { 132 | var platformBuildPath = path.join(__dirname, "platform-" + platformPath); 133 | 134 | foldersToCopy.forEach(folderPath => { 135 | var sourcePath = path.join(platformBuildPath, folderPath); 136 | var destPath = path.join(__dirname, folderPath); 137 | 138 | copyRecursive(sourcePath, destPath); 139 | }); 140 | 141 | binariesToCopy.forEach(binaryPath => { 142 | var sourcePath = path.join(platformBuildPath, binaryPath); 143 | var destPath = path.join(__dirname, binaryPath); 144 | if (fs.existsSync(destPath)) { 145 | fs.unlinkSync(destPath); 146 | } 147 | copyFileSync(sourcePath, destPath); 148 | fs.chmodSync(destPath, 0777); 149 | }); 150 | }; 151 | 152 | try { 153 | fs.mkdirSync("_export"); 154 | } catch (e) { 155 | console.log("Could not create _export folder"); 156 | } 157 | 158 | switch (platform) { 159 | case "win32": 160 | if (arch() !== "x64") { 161 | console.warn("error: x86 is currently not supported on Windows"); 162 | process.exit(1); 163 | } 164 | 165 | copyPlatformBinaries("windows-x64"); 166 | break; 167 | case "linux": 168 | case "darwin": 169 | copyPlatformBinaries(platform); 170 | break; 171 | default: 172 | console.warn("error: no release built for the " + platform + " platform"); 173 | process.exit(1); 174 | } 175 | 176 | require("./esyInstallRelease"); 177 | -------------------------------------------------------------------------------- /.ci/utils/create-docs.yml: -------------------------------------------------------------------------------- 1 | # These steps are only run on Linux 2 | steps: 3 | - script: "esy doc" 4 | displayName: "Build docs" 5 | condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) 6 | 7 | - script: echo '##vso[task.setvariable variable=docsPath]'$(esy echo '#{self.target_dir}/default/_doc/_html') 8 | displayName: "Save docsPath in variable" 9 | condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) 10 | 11 | - task: PublishBuildArtifacts@1 12 | displayName: "Publish Artifact: Docs" 13 | condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) 14 | inputs: 15 | PathtoPublish: $(docsPath) 16 | ArtifactName: Docs 17 | -------------------------------------------------------------------------------- /.ci/utils/publish-build-cache.yml: -------------------------------------------------------------------------------- 1 | # Steps for publishing project cache 2 | 3 | steps: 4 | - bash: 'mkdir -p $(Build.StagingDirectory)' 5 | condition: and(succeeded(), eq(variables['Build.Reason'], 'IndividualCI')) 6 | displayName: '[Cache][Publish] Create cache directory' 7 | 8 | # continueOnError because on windows it has a permission denied error but the 9 | # export succeeds. 10 | - script: "esy export-dependencies" 11 | continueOnError: true 12 | condition: and(succeeded(), eq(variables['Build.Reason'], 'IndividualCI')) 13 | displayName: "esy export-dependencies" 14 | 15 | - bash: pwd && ls _export/* && mv _export '$(Build.StagingDirectory)' && ls '$(Build.StagingDirectory)/_export/' 16 | condition: and(succeeded(), eq(variables['Build.Reason'], 'IndividualCI')) 17 | displayName: '[Cache][Publish] move export to staging dir' 18 | 19 | # - bash: cd $ESY__CACHE_INSTALL_PATH && tar -czf $(Build.StagingDirectory)/esy-cache.tar . 20 | # workingDirectory: '' 21 | # condition: and(succeeded(), eq(variables['Build.Reason'], 'IndividualCI')) 22 | # displayName: '[Cache][Publish] Tar esy cache directory' 23 | 24 | # - bash: 'cd $(ESY__NPM_ROOT) && tar -czf $(Build.StagingDirectory)/npm-cache.tar .' 25 | # condition: and(succeeded(), eq(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch'])) 26 | # displayName: '[Cache][Publish] Tar npm cache directory' 27 | 28 | - task: PublishBuildArtifacts@1 29 | displayName: '[Cache][Publish] Upload tarball' 30 | condition: and(succeeded(), eq(variables['Build.Reason'], 'IndividualCI')) 31 | # TODO: The CI Build caches are pulled down by the last successful buildID 32 | # for the target branch. 33 | inputs: 34 | pathToPublish: '$(Build.StagingDirectory)' 35 | artifactName: 'cache-$(Agent.OS)-install' 36 | parallel: true 37 | parallelCount: 8 38 | -------------------------------------------------------------------------------- /.ci/utils/restore-build-cache.yml: -------------------------------------------------------------------------------- 1 | # Steps for restoring project cache 2 | 3 | steps: 4 | - bash: 'mkdir -p $(Build.StagingDirectory)' 5 | condition: and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch']))) 6 | displayName: '[Cache][Publish] Create cache directory' 7 | 8 | # TODO: This can be done in parallel with installing node, and esy 9 | # (which would save a bunch of time on windows) 10 | - task: Bash@3 11 | condition: and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch']))) 12 | displayName: '[Cache][Restore] Restoring build cache using REST API' 13 | continueOnError: true 14 | inputs: 15 | targetType: 'inline' # Optional. Options: filePath, inline 16 | script: | 17 | # If org name is reasonml then REST_BASE will be: https://dev.azure.com/reasonml/ 18 | REST_BASE="${SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}" 19 | PROJ="$SYSTEM_TEAMPROJECT" 20 | ART_NAME="cache-${AGENT_OS}-install" 21 | fetchLatestBuild() { 22 | PREFIX="branchName=refs%2Fheads%2F" 23 | BRANCH=${PREFIX}${SYSTEM_PULLREQUEST_TARGETBRANCH} 24 | FILTER='deletedFilter=excludeDeleted&statusFilter=completed&resultFilter=succeeded' 25 | LATEST='queryOrder=finishTimeDescending&$top=1' 26 | REST_BUILDS="$REST_BASE/$PROJ/_apis/build/builds?${FILTER}&${BRANCH}&${LATEST}&api-version=4.1" 27 | echo "Rest call for builds: $REST_BUILDS" 28 | REST_BUILDS_RESP=$(curl "$REST_BUILDS") 29 | if [[ $REST_BUILDS_RESP =~ (\"web\":\{\"href\":\")([^\"]*) ]]; then LATEST_BUILD_PAGE="${BASH_REMATCH[2]}"; else LATEST_BUILD_PAGE=""; fi 30 | if [[ $REST_BUILDS_RESP =~ (\"badge\":\{\"href\":\")([^\"]*) ]]; then LATEST_BUILD_BADGE="${BASH_REMATCH[2]}"; else LATEST_BUILD_BADGE=""; fi 31 | if [[ $REST_BUILDS_RESP =~ (\"id\":)([^,]*) ]]; then LATEST_BUILD_ID="${BASH_REMATCH[2]}"; else LATEST_BUILD_ID=""; fi 32 | } 33 | fetchLatestBuild 34 | fetchArtifactURL() { 35 | REST_ART="$REST_BASE/$PROJ/_apis/build/builds/$LATEST_BUILD_ID/artifacts?artifactName=$ART_NAME&api-version=4.1" 36 | echo "Rest call for artifacts: $REST_ART" 37 | if [[ $(curl $REST_ART) =~ (downloadUrl\":\")([^\"]*) ]]; then LATEST_ART_URL="${BASH_REMATCH[2]}"; else LATEST_ART_URL=""; fi 38 | } 39 | downloadArtifactAndContinue() { 40 | if [ -z "$LATEST_ART_URL" ] 41 | then 42 | echo "No latest artifact for merge-target branch found at URL $REST_ART" 43 | else 44 | curl "$LATEST_ART_URL" > "${BUILD_STAGINGDIRECTORY}/$ART_NAME.zip" 45 | PROJECT_DIR=$PWD 46 | cd $BUILD_STAGINGDIRECTORY 47 | unzip "$ART_NAME.zip" 48 | echo "Using Dependency cache for buildID: $LATEST_BUILD_ID" 49 | echo "Build log for build that produced the cache: $LATEST_BUILD_PAGE" 50 | echo "Build badge for build that produced the cache: $LATEST_BUILD_BADGE" 51 | echo "Build artifact from build that produced the cache: $LATEST_ART_URL" 52 | echo "Restoring build cache into:" 53 | mkdir -p $ESY__CACHE_INSTALL_PATH 54 | echo $ESY__CACHE_INSTALL_PATH 55 | echo "##vso[task.setvariable variable=esy_export_dir_to_import]${BUILD_STAGINGDIRECTORY}/${ART_NAME}/_export" 56 | if [[ -d ${ART_NAME}/_export ]]; then 57 | echo "Cached builds to import from ${ART_NAME}/_export in next CI step" 58 | ls ${ART_NAME}/_export 59 | mv ${ART_NAME}/_export ${PROJECT_DIR}/_export 60 | else 61 | echo "No _export directory to import from build cache" 62 | echo "Here's the contents of build cache:" 63 | find ${BUILD_STAGINGDIRECTORY} 64 | fi 65 | fi 66 | } 67 | fetchArtifactURL 68 | downloadArtifactAndContinue 69 | 70 | - powershell: esy.cmd import-dependencies 71 | continueOnError: true 72 | condition: and(eq(variables['AGENT.OS'], 'Windows_NT'), and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch'])))) 73 | displayName: "esy import-dependencies if windows (build cache from CI cache)" 74 | 75 | - bash: esy import-dependencies 76 | continueOnError: true 77 | condition: and(ne(variables['AGENT.OS'], 'Windows_NT'), and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch'])))) 78 | displayName: "esy import-dependencies if not windows (build cache from CI cache)" 79 | 80 | # Remove as soon as https://github.com/esy/esy/pull/969 is resolved. 81 | # For now, windows won't use build cache for problematic package. 82 | - task: Bash@3 83 | continueOnError: true 84 | condition: and(eq(variables['AGENT.OS'], 'Windows_NT'), and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch'])))) 85 | displayName: 'Remove ocamlfind prebuilts if on Windows' 86 | inputs: 87 | targetType: 'inline' # Optional. Options: filePath, inline 88 | script: | 89 | ls ${ESY__CACHE_INSTALL_PATH} 90 | rm -r ${ESY__CACHE_INSTALL_PATH}/opam__s__ocamlfind* 91 | 92 | - bash: 'rm -rf _import' 93 | continueOnError: true 94 | condition: and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch']))) 95 | displayName: 'Remove import directory' 96 | 97 | - bash: 'rm -rf *' 98 | continueOnError: true 99 | workingDirectory: '$(Build.StagingDirectory)' 100 | condition: and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch']))) 101 | displayName: '[Cache][Restore] Clean up staging dir' 102 | -------------------------------------------------------------------------------- /.ci/utils/use-esy.yml: -------------------------------------------------------------------------------- 1 | # steps to install esy globally 2 | 3 | steps: 4 | - script: "npm install -g esy@0.5.6" 5 | displayName: "install esy" 6 | -------------------------------------------------------------------------------- /.ci/utils/use-node.yml: -------------------------------------------------------------------------------- 1 | # steps to use node on agent 2 | 3 | steps: 4 | - task: NodeTool@0 5 | displayName: "Use Node 8.x" 6 | inputs: 7 | versionSpec: 8.x 8 | -------------------------------------------------------------------------------- /.ds-root: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonstefanov/rarg/0deeeb986f9b3a1b0e1513f98969197ca21ebab6/.ds-root -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | _build 4 | *.byte 5 | *.native 6 | *.install 7 | # gitignored, but not npmignored. Published by `npm run prepublish` 8 | _esybuild 9 | _esyinstall 10 | .merlin 11 | _release 12 | _esy 13 | junit.xml 14 | *.orig 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "saveAndRun": { 3 | "commands": [ 4 | { 5 | "cmd": "clear && esy test", 6 | "match": ".re" 7 | } 8 | ] 9 | }, 10 | "code-runner.executorMapByFileExtension": { 11 | ".re": "esy x qw" 12 | }, 13 | "search.exclude": { 14 | "**/node_modules": true, 15 | "**/esy.lock": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ESY.md: -------------------------------------------------------------------------------- 1 | [odoc](http://caml.inria.fr/pub/docs/manual-ocaml/ocamldoc.html#sec352) 2 | 3 | [![Build Status](https://dev.azure.com/esy-ocaml/esy-ocaml/_apis/build/status/esy-ocaml.rarg?branchName=master)](https://dev.azure.com/esy-ocaml/esy-ocaml/_build/latest?definitionId=1?branchName=master) 4 | 5 | A project which demonstrates a Reason workflow with [Esy][]. 6 | 7 | [esy]: https://github.com/esy-ocaml/esy 8 | 9 | ## Usage 10 | 11 | You need Esy, you can install the beta using [npm](https://npmjs.com): 12 | 13 | % npm install -g esy@latest 14 | 15 | > NOTE: Make sure `esy --version` returns at least `0.5.4` for this project to build. 16 | 17 | Then run the `esy` command from this project root to install and build depenencies. 18 | 19 | % esy 20 | 21 | Now you can run your editor within the environment (which also includes merlin): 22 | 23 | % esy $EDITOR 24 | % esy vim 25 | 26 | Alternatively you can try [vim-reasonml](https://github.com/jordwalke/vim-reasonml) 27 | which loads esy project environments automatically. 28 | 29 | After you make some changes to source code, you can re-run project's build 30 | again with the same simple `esy` command. 31 | 32 | % esy 33 | 34 | And test compiled executable (runs `scripts.tests` specified in 35 | `package.json`): 36 | 37 | % esy test 38 | 39 | Documentation for the libraries in the project can be generated with: 40 | 41 | % esy doc 42 | % esy open '#{self.target_dir}/default/_doc/_html/index.html' 43 | 44 | Shell into environment: 45 | 46 | % esy shell 47 | 48 | ## Create Prebuilt Release: 49 | 50 | `esy` allows creating prebuilt binary packages for your current platform, with 51 | no dependencies. 52 | 53 | % esy npm-release 54 | % cd _release 55 | % npm publish 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Anton Stefanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016-2018 Various Authors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /ORIGINS.md: -------------------------------------------------------------------------------- 1 | Project template, application platform copied from: 2 | [esy](https://github.com/esy/esy): BSD-2 3 | 4 | Collections (JS Api), tests setup modified from 5 | [reason-native](https://github.com/facebookexperimental/reason-native): MIT 6 | 7 | Levenshtein modified from 8 | [Rosetta Stone](http://rosettacode.org/wiki/Levenshtein_distance#OCaml): GNU Free Documentation License 9 | 10 | Shell autocomplete templates modified from 11 | [yargs](https://github.com/yargs/yargs): MIT 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![rarg logo](./rarg.png) 2 | 3 | A simple, focused and expressive library for building command line applications. 4 | Optimised for native ReasonML/OCaml. 5 | 6 | [![Build Status](https://antonstefanov.visualstudio.com/rarg/_apis/build/status/antonstefanov.rarg?branchName=master)](https://antonstefanov.visualstudio.com/rarg/_build/latest?definitionId=2&branchName=master) 7 | 8 | ## Features 9 | 10 | - **autocompletion** - fast and comprehensive autocompletion of commands, arguments and values 11 | - **sync** and **async** commands support 12 | - **sub commands** - you can easily define a whole tree of commands and compose them 13 | - **auto configuration validation** - you can validate your whole commands tree configuration with a single function call in your tests 14 | - **auto help generation** 15 | - **autocorrection** 16 | 17 | ## [API Reference](https://rarg.z13.web.core.windows.net/rarg/Rarg/index.html) 18 | 19 | > Note that the only external modules are: 20 | > 21 | > - [Type](https://rarg.z13.web.core.windows.net/rarg/RargInternal/Type/index.html) 22 | > - [Args](https://rarg.z13.web.core.windows.net/rarg/RargInternal/Args/index.html) 23 | > - [Cmd](https://rarg.z13.web.core.windows.net/rarg/RargInternal/Cmd/index.html) 24 | > - [Run](https://rarg.z13.web.core.windows.net/rarg/RargInternal/Run/index.html) 25 | 26 | ## Usage 27 | 28 | 0. To add to your [esy](https://esy.sh) project simply use: 29 | 30 | ```sh 31 | esy add rarg 32 | ``` 33 | 34 | 1. Define command arguments with [Args](https://rarg.z13.web.core.windows.net/rarg/RargInternal/Args/index.html). 35 | 36 | ```reason 37 | module Args = Rarg.Args; 38 | module Type = Rarg.Type; 39 | 40 | module MyCmd = { 41 | let args = [] 42 | let (args, getCopy) = Args.One.boolFlag( 43 | ~args, 44 | ~name="--copy", 45 | ~doc="Whether to copy", 46 | Type.bool, 47 | ); 48 | let (args, getColor) = 49 | Args.One.default( 50 | ~args, 51 | ~name="--color", 52 | ~doc="Paint color", 53 | ~default="green", 54 | Type.string, 55 | ); 56 | 57 | // ... 58 | }; 59 | ``` 60 | 61 | For the [Type](https://rarg.z13.web.core.windows.net/rarg/RargInternal/Type/index.html) argument you can either choose one of the [predefined argument types](https://rarg.z13.web.core.windows.net/rarg/RargInternal/Type/index.html#type_predefined) or define custom argument types, for example: 62 | 63 | ```reason 64 | // ... 65 | 66 | type fruit = | Apple | Banana; 67 | let fruit: Type.t(fruit) = { 68 | name: "fruit", 69 | parse: 70 | fun 71 | | "apple" => Ok(Apple) 72 | | "banana" => Ok(Banana) 73 | | x => Error(Some(x ++ " is not a fruit.")), 74 | stringify: 75 | fun 76 | | Apple => "apple" 77 | | Banana => "banana", 78 | choices: Some(HelpAndSuggestions([Apple, Banana])), 79 | }; 80 | let (args, getFruits) = Args.Many.req(~args, ~name="--fruits", ~doc="Fruits", fruit); 81 | 82 | // ... 83 | ``` 84 | 85 | 2. Define the command with [Cmd](https://rarg.z13.web.core.windows.net/rarg/RargInternal/Cmd/index.html): 86 | 87 | ```reason 88 | // ... 89 | 90 | // Define the function that you want to execute 91 | let handle = (~fruits: list(fruit), ~copy: bool, ~color: string) => (); 92 | 93 | // Define a mapping function that will use the getters returned from `Args` 94 | // and pass the provided user arguments. 95 | // It allows you to use labeled arguments as opposed to relying on arg positions. 96 | let run = m => handle(~fruits=getFruits(m), ~copy=getCopy(m), ~color=getColor(m)); 97 | 98 | // Define a command record that you can use to run your command, 99 | // pass it as a child to other commands or test it 100 | let cmd: Cmd.t(unit) = Cmd.make(~name="My Command", ~version="1.0", ~args, ~run, ()); 101 | } // module MyCmd close 102 | ``` 103 | 104 | You can also easily define sub commands: 105 | 106 | ```reason 107 | module AnotherCmd = { 108 | // ... 109 | 110 | let cmd: Cmd.t(unit) = 111 | Cmd.make( 112 | ~name="Another Command", 113 | ~version="1.0", 114 | ~args, 115 | ~run, 116 | ~children=[("my-cmd", MyCmd.cmd)], 117 | (), 118 | ); 119 | }; 120 | ``` 121 | 122 | > In `rarg` every command/subcommand is a complete unit of work, that can exist on its own, has no dependencies of its parents. That's why every command has its own version. 123 | 124 | 3. And finally you can run your command with [Run](https://rarg.z13.web.core.windows.net/rarg/RargInternal/Run/index.html) 125 | 126 | ```reason 127 | let main = { 128 | switch (Run.autorun(MyCmd.cmd)) { 129 | | Ok(_) => exit(0) 130 | | Error(_) => exit(1) 131 | }; 132 | }; 133 | ``` 134 | 135 | ## System arguments (auto-included) 136 | 137 | - `--help` - display command help 138 | - `--version` - display command version 139 | - `--rarg-suggestions-script` - displays a script with instsructions how to install it to enable shell autocompletions 140 | - `--rarg-add-path` - displays a script with instructions how to add the app executable to the user's path (helpful during development) 141 | 142 | ## Examples 143 | 144 | You can check the local [examples](https://github.com/antonstefanov/rarg/tree/master/src/examples) or the repo [rarg-examples](https://github.com/antonstefanov/rarg-examples) for more complete examples. 145 | 146 | ## Comparison with [cmdliner](https://github.com/dbuenzli/cmdliner) 147 | 148 | > This was the most requested comparison and is added for completeness, but the 2 are very different. 149 | 150 | - `cmdliner` is hosted on `opam` | `rarg` on `npm` 151 | - it's likely that you would be more familiar with `cmdliner`'s API if you have an `OCaml` background and with `rarg`'s API if you are coming from other languages (including `JS`) 152 | - `cmdliner` is very mature and has a large ecosystem behind it 153 | - `rarg` has autocompletions, smaller API footprint, validation, composable commands and is simpler and less abstract in nature 154 | 155 | ## Notes 156 | 157 | All commands must follow the following structure: 158 | 159 | ```sh 160 | command [..sub-commands] [..positionals] [..options] 161 | ``` 162 | 163 | The main `command`, optionally followed by `sub-commands`, then optional `positionals` and finally `options` (like `--foo`). 164 | Options always come last and cannot be between subcommands and positionals. 165 | This consistent structure allows for more relevant autocomplete functionality and predictable options value parsing. 166 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: Build npm release 2 | 3 | trigger: 4 | - master 5 | - global 6 | - release-* 7 | - releases-* 8 | - feature-* 9 | 10 | jobs: 11 | - template: .ci/build-platform.yml 12 | parameters: 13 | platform: Linux 14 | vmImage: ubuntu-16.04 15 | 16 | - template: .ci/build-platform.yml 17 | parameters: 18 | platform: macOS 19 | vmImage: macOS-10.13 20 | # Need windows-2019 to do esy import/export-dependencies 21 | # which assumes you have bsdtar (tar.exe) in your system 22 | # otherwise it will end up using the esy-bash tar which doesn't 23 | # understand drives like D:/ (thinks it's an scp path). 24 | # - template: .ci/build-platform.yml 25 | # parameters: 26 | # platform: Windows 27 | # vmImage: windows-2019 28 | # This job is kept here as we want to have the platform names in the same file 29 | # - job: Release 30 | # displayName: Release 31 | # dependsOn: 32 | # - Linux 33 | # - macOS 34 | # - Windows 35 | # pool: 36 | # vmImage: macOS-10.13 37 | # demands: node.js 38 | # steps: 39 | # - template: .ci/cross-release.yml 40 | -------------------------------------------------------------------------------- /bin.opam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonstefanov/rarg/0deeeb986f9b3a1b0e1513f98969197ca21ebab6/bin.opam -------------------------------------------------------------------------------- /ds.opam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonstefanov/rarg/0deeeb986f9b3a1b0e1513f98969197ca21ebab6/ds.opam -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.6) 2 | -------------------------------------------------------------------------------- /esy.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rarg-dev", 3 | "version": "0.6.3", 4 | "description": "A simple, focused and expressive library for building command line applications.", 5 | "license": "MIT", 6 | "author": { "name": "Anton Stefanov", "email": "anton.stefanov@live.com" }, 7 | "esy": { 8 | "build": "refmterr dune build --profile=release", 9 | "NOTE": "Optional release Section. Customizes result of `esy release`", 10 | "install": [ 11 | "esy-installer rarg.install", 12 | "esy-installer tests.install", 13 | "esy-installer example-basic.install", 14 | "esy-installer example-lwt.install" 15 | ], 16 | "buildEnv": { "ODOC_SYNTAX": "re" } 17 | }, 18 | "scripts": { 19 | "test": "esy x bash -cx ./scripts/test-dev.cmd", 20 | "test-ci": "esy x bash -cx ./scripts/test-ci.cmd", 21 | "example": "esy x example-basic", 22 | "example-lwt": "esy x example-lwt", 23 | "doc": "esy dune build @doc", 24 | "opendoc": "esy open '#{self.target_dir}/default/_doc/_html/rarg/Rarg/index.html'" 25 | }, 26 | "repository": "https://github.com/antonstefanov/rarg", 27 | "homepage": "https://github.com/antonstefanov/rarg", 28 | "bugs": "https://github.com/antonstefanov/rarg/issues", 29 | "dependencies": { 30 | "@opam/dune": "^1.10.0", 31 | "@opam/lwt": "4.3.0", 32 | "@opam/lwt_ppx": "1.2.3", 33 | "@reason-native/console": "*", 34 | "@reason-native/pastel": "0.2.1", 35 | "@esy-ocaml/reason": ">= 3.5.0 < 3.6.0", 36 | "refmterr": "*", 37 | "ocaml": "~4.8.1000", 38 | "@reason-native/rely": "*" 39 | }, 40 | "devDependencies": { 41 | "@opam/merlin": "*", 42 | "ocaml": "~4.8.1000", 43 | "@opam/odoc": "*" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /esy.lock/.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | # Set eol to LF so files aren't converted to CRLF-eol on Windows. 3 | * text eol=lf 4 | -------------------------------------------------------------------------------- /esy.lock/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Reset any possible .gitignore, we want all esy.lock to be un-ignored. 3 | !* 4 | -------------------------------------------------------------------------------- /esy.lock/opam/astring.0.8.3/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Daniel Bünzli " 3 | authors: ["Daniel Bünzli "] 4 | homepage: "http://erratique.ch/software/astring" 5 | doc: "http://erratique.ch/software/astring/doc" 6 | dev-repo: "git+http://erratique.ch/repos/astring.git" 7 | bug-reports: "https://github.com/dbuenzli/astring/issues" 8 | tags: [ "string" "org:erratique" ] 9 | license: "ISC" 10 | depends: [ 11 | "ocaml" {>= "4.01.0"} 12 | "ocamlfind" {build} 13 | "ocamlbuild" {build} 14 | "topkg" {build} 15 | "base-bytes" 16 | ] 17 | build: [[ 18 | "ocaml" "pkg/pkg.ml" "build" 19 | "--pinned" "%{pinned}%" ]] 20 | synopsis: "Alternative String module for OCaml" 21 | description: """ 22 | Astring exposes an alternative `String` module for OCaml. This module 23 | tries to balance minimality and expressiveness for basic, index-free, 24 | string processing and provides types and functions for substrings, 25 | string sets and string maps. 26 | 27 | Remaining compatible with the OCaml `String` module is a non-goal. The 28 | `String` module exposed by Astring has exception safe functions, 29 | removes deprecated and rarely used functions, alters some signatures 30 | and names, adds a few missing functions and fully exploits OCaml's 31 | newfound string immutability. 32 | 33 | Astring depends only on the OCaml standard library. It is distributed 34 | under the ISC license.""" 35 | url { 36 | src: "http://erratique.ch/software/astring/releases/astring-0.8.3.tbz" 37 | checksum: "md5=c5bf6352b9ac27fbeab342740f4fa870" 38 | } 39 | -------------------------------------------------------------------------------- /esy.lock/opam/atd.2.0.0/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "martin@mjambon.com" 3 | authors: ["Martin Jambon"] 4 | 5 | homepage: "https://github.com/mjambon/atd" 6 | bug-reports: "https://github.com/mjambon/atd/issues" 7 | dev-repo: "git://github.com/mjambon/atd.git" 8 | 9 | build: [ 10 | ["jbuilder" "subst" "-p" name] {pinned} 11 | ["jbuilder" "build" "-p" name "-j" jobs] 12 | ] 13 | 14 | # Restore when https://github.com/mjambon/atd/issues/121 is resolved. 15 | # build-test: [ 16 | # ["jbuilder" "runtest" "-p" name] 17 | # ] 18 | 19 | depends: [ 20 | "ocaml" {>= "4.03.0"} 21 | "jbuilder" 22 | "menhir" {build} 23 | "easy-format" 24 | ] 25 | synopsis: "Parser for the ATD data format description language" 26 | description: """ 27 | ATD is the OCaml library providing a parser for the ATD language and 28 | various utilities. ATD stands for Adjustable Type Definitions in 29 | reference to its main property of supporting annotations that allow a 30 | good fit with a variety of data formats.""" 31 | url { 32 | src: "https://github.com/mjambon/atd/releases/download/2.0.0/atd-2.0.0.tbz" 33 | checksum: "md5=14e47609397c524ea0eae7c3f14f7ccf" 34 | } 35 | -------------------------------------------------------------------------------- /esy.lock/opam/atdgen-runtime.2.0.0/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "martin@mjambon.com" 3 | authors: ["Martin Jambon"] 4 | 5 | homepage: "https://github.com/mjambon/atd" 6 | bug-reports: "https://github.com/mjambon/atd/issues" 7 | dev-repo: "git://github.com/mjambon/atd.git" 8 | 9 | build: [ 10 | ["jbuilder" "subst" "-p" name] {pinned} 11 | ["jbuilder" "build" "-p" name "-j" jobs] 12 | ] 13 | 14 | # Restore when https://github.com/mjambon/atd/issues/121 is resolved. 15 | # build-test: [ 16 | # ["jbuilder" "runtest" "-p" name] 17 | # ] 18 | 19 | depends: [ 20 | "ocaml" {>= "4.02.3"} 21 | "jbuilder" 22 | "biniou" {>= "1.0.6"} 23 | "yojson" {>= "1.2.1"} 24 | ] 25 | synopsis: "Runtime library for code generated by atdgen." 26 | url { 27 | src: "https://github.com/mjambon/atd/releases/download/2.0.0/atd-2.0.0.tbz" 28 | checksum: "md5=14e47609397c524ea0eae7c3f14f7ccf" 29 | } 30 | -------------------------------------------------------------------------------- /esy.lock/opam/atdgen.2.0.0/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "martin@mjambon.com" 3 | authors: ["Martin Jambon"] 4 | 5 | homepage: "https://github.com/mjambon/atd" 6 | bug-reports: "https://github.com/mjambon/atd/issues" 7 | dev-repo: "git://github.com/mjambon/atd.git" 8 | 9 | build: [ 10 | ["jbuilder" "subst" "-p" name] {pinned} 11 | ["jbuilder" "build" "-p" name "-j" jobs] 12 | ] 13 | 14 | # Restore when https://github.com/mjambon/atd/issues/121 is resolved. 15 | # build-test: [ 16 | # ["jbuilder" "runtest" "-p" name] 17 | # ] 18 | 19 | depends: [ 20 | "ocaml" {>= "4.03.0"} 21 | "jbuilder" 22 | "atd" {>= "2.0.0"} 23 | "atdgen-runtime" {>= "2.0.0"} 24 | "biniou" {>= "1.0.6"} 25 | "yojson" {>= "1.2.1"} 26 | ] 27 | synopsis: 28 | "Generates efficient JSON serializers, deserializers and validators" 29 | description: """ 30 | Atdgen is a command-line program that takes as input type definitions in the 31 | ATD syntax and produces OCaml code suitable for data serialization and 32 | deserialization. 33 | 34 | Two data formats are currently supported, these are biniou and JSON. 35 | Atdgen-biniou and Atdgen-json will refer to Atdgen used in one context or the 36 | other. 37 | 38 | Atdgen was designed with efficiency and durability in mind. Software authors 39 | are encouraged to use Atdgen directly and to write tools that may reuse part of 40 | Atdgen’s source code.""" 41 | url { 42 | src: "https://github.com/mjambon/atd/releases/download/2.0.0/atd-2.0.0.tbz" 43 | checksum: "md5=14e47609397c524ea0eae7c3f14f7ccf" 44 | } 45 | -------------------------------------------------------------------------------- /esy.lock/opam/base-bytes.base/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: " " 3 | authors: " " 4 | homepage: " " 5 | depends: [ 6 | "ocaml" {>= "4.02.0"} 7 | "ocamlfind" {>= "1.5.3"} 8 | ] 9 | synopsis: "Bytes library distributed with the OCaml compiler" 10 | -------------------------------------------------------------------------------- /esy.lock/opam/base-threads.base/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "https://github.com/ocaml/opam-repository/issues" 3 | description: """ 4 | Threads library distributed with the OCaml compiler 5 | """ 6 | 7 | -------------------------------------------------------------------------------- /esy.lock/opam/base-unix.base/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "https://github.com/ocaml/opam-repository/issues" 3 | description: """ 4 | Unix library distributed with the OCaml compiler 5 | """ 6 | 7 | -------------------------------------------------------------------------------- /esy.lock/opam/biniou.1.2.1/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | build: [ 3 | ["dune" "subst"] {pinned} 4 | ["dune" "build" "-p" name "-j" jobs] 5 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 6 | ["dune" "build" "-p" name "@doc"] {with-doc} 7 | ] 8 | maintainer: ["martin@mjambon.com"] 9 | authors: ["Martin Jambon"] 10 | bug-reports: "https://github.com/mjambon/biniou/issues" 11 | homepage: "https://github.com/mjambon/biniou" 12 | doc: "https://mjambon.github.io/biniou/" 13 | license: "BSD-3-Clause" 14 | dev-repo: "git+https://github.com/mjambon/biniou.git" 15 | synopsis: 16 | "Binary data format designed for speed, safety, ease of use and backward compatibility as protocols evolve" 17 | description: """ 18 | 19 | Biniou (pronounced "be new") is a binary data format designed for speed, safety, 20 | ease of use and backward compatibility as protocols evolve. Biniou is vastly 21 | equivalent to JSON in terms of functionality but allows implementations several 22 | times faster (4 times faster than yojson), with 25-35% space savings. 23 | 24 | Biniou data can be decoded into human-readable form without knowledge of type 25 | definitions except for field and variant names which are represented by 31-bit 26 | hashes. A program named bdump is provided for routine visualization of biniou 27 | data files. 28 | 29 | The program atdgen is used to derive OCaml-Biniou serializers and deserializers 30 | from type definitions. 31 | 32 | Biniou format specification: mjambon.github.io/atdgen-doc/biniou-format.txt""" 33 | depends: [ 34 | "easy-format" 35 | "dune" {>= "1.10"} 36 | "ocaml" {>= "4.02.3"} 37 | ] 38 | url { 39 | src: 40 | "https://github.com/mjambon/biniou/releases/download/1.2.1/biniou-1.2.1.tbz" 41 | checksum: [ 42 | "sha256=35546c68b1929a8e6d27a3b39ecd17b38303a0d47e65eb9d1480c2061ea84335" 43 | "sha512=82670cc77bf3e869ee26e5fbe5a5affa45a22bc8b6c4bd7e85473912780e0111baca59b34a2c14feae3543ce6e239d7fddaeab24b686a65bfe642cdb91d27ebf" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /esy.lock/opam/cmdliner.1.0.4/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Daniel Bünzli " 3 | authors: ["Daniel Bünzli "] 4 | homepage: "http://erratique.ch/software/cmdliner" 5 | doc: "http://erratique.ch/software/cmdliner/doc/Cmdliner" 6 | dev-repo: "git+http://erratique.ch/repos/cmdliner.git" 7 | bug-reports: "https://github.com/dbuenzli/cmdliner/issues" 8 | tags: [ "cli" "system" "declarative" "org:erratique" ] 9 | license: "ISC" 10 | depends:[ "ocaml" {>= "4.03.0"} ] 11 | build: [[ make "all" "PREFIX=%{prefix}%" ]] 12 | install: 13 | [[make "install" "LIBDIR=%{_:lib}%" "DOCDIR=%{_:doc}%" ] 14 | [make "install-doc" "LIBDIR=%{_:lib}%" "DOCDIR=%{_:doc}%" ]] 15 | 16 | synopsis: """Declarative definition of command line interfaces for OCaml""" 17 | description: """\ 18 | 19 | Cmdliner allows the declarative definition of command line interfaces 20 | for OCaml. 21 | 22 | It provides a simple and compositional mechanism to convert command 23 | line arguments to OCaml values and pass them to your functions. The 24 | module automatically handles syntax errors, help messages and UNIX man 25 | page generation. It supports programs with single or multiple commands 26 | and respects most of the [POSIX][1] and [GNU][2] conventions. 27 | 28 | Cmdliner has no dependencies and is distributed under the ISC license. 29 | 30 | [1]: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html 31 | [2]: http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html 32 | """ 33 | url { 34 | archive: "http://erratique.ch/software/cmdliner/releases/cmdliner-1.0.4.tbz" 35 | checksum: "fe2213d0bc63b1e10a2d0aa66d2fc8d9" 36 | } 37 | -------------------------------------------------------------------------------- /esy.lock/opam/conf-m4.1/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "tim@gfxmonk.net" 3 | homepage: "http://www.gnu.org/software/m4/m4.html" 4 | bug-reports: "https://github.com/ocaml/opam-repository/issues" 5 | authors: "GNU Project" 6 | license: "GPL-3.0-only" 7 | build: [["sh" "-exc" "echo | m4"]] 8 | depexts: [ 9 | ["m4"] {os-family = "debian"} 10 | ["m4"] {os-distribution = "fedora"} 11 | ["m4"] {os-distribution = "rhel"} 12 | ["m4"] {os-distribution = "centos"} 13 | ["m4"] {os-distribution = "alpine"} 14 | ["m4"] {os-distribution = "nixos"} 15 | ["m4"] {os-family = "suse"} 16 | ["m4"] {os-distribution = "ol"} 17 | ["m4"] {os-distribution = "arch"} 18 | ] 19 | synopsis: "Virtual package relying on m4" 20 | description: 21 | "This package can only install if the m4 binary is installed on the system." 22 | flags: conf 23 | -------------------------------------------------------------------------------- /esy.lock/opam/cppo.1.6.6/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "martin@mjambon.com" 3 | authors: "Martin Jambon" 4 | license: "BSD-3-Clause" 5 | homepage: "http://mjambon.com/cppo.html" 6 | doc: "https://ocaml-community.github.io/cppo/" 7 | bug-reports: "https://github.com/ocaml-community/cppo/issues" 8 | depends: [ 9 | "ocaml" {>= "4.03"} 10 | "dune" {>= "1.0"} 11 | "base-unix" 12 | ] 13 | build: [ 14 | ["dune" "subst"] {pinned} 15 | ["dune" "build" "-p" name "-j" jobs] 16 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 17 | ] 18 | dev-repo: "git+https://github.com/ocaml-community/cppo.git" 19 | synopsis: "Code preprocessor like cpp for OCaml" 20 | description: """ 21 | Cppo is an equivalent of the C preprocessor for OCaml programs. 22 | It allows the definition of simple macros and file inclusion. 23 | 24 | Cppo is: 25 | 26 | * more OCaml-friendly than cpp 27 | * easy to learn without consulting a manual 28 | * reasonably fast 29 | * simple to install and to maintain 30 | """ 31 | url { 32 | src: "https://github.com/ocaml-community/cppo/releases/download/v1.6.6/cppo-v1.6.6.tbz" 33 | checksum: [ 34 | "sha256=e7272996a7789175b87bb998efd079794a8db6625aae990d73f7b4484a07b8a0" 35 | "sha512=44ecf9d225d9e45490a2feac0bde04865ca398dba6c3579e3370fcd1ea255707b8883590852af8b2df87123801062b9f3acce2455c092deabf431f9c4fb8d8eb" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /esy.lock/opam/dune-configurator.1.0.0/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | authors: ["Jérémie Dimino"] 3 | homepage: "https://github.com/ocaml/dune" 4 | bug-reports: "https://github.com/ocaml/dune/issues" 5 | maintainer: "Jérémie Dimino" 6 | description: """ 7 | dune.configurator library distributed with Dune 1.x 8 | """ 9 | depends: ["dune" {<"2.0.0"}] 10 | -------------------------------------------------------------------------------- /esy.lock/opam/dune.1.11.4/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | synopsis: "Fast, portable and opinionated build system" 3 | description: """ 4 | 5 | dune is a build system that was designed to simplify the release of 6 | Jane Street packages. It reads metadata from "dune" files following a 7 | very simple s-expression syntax. 8 | 9 | dune is fast, it has very low-overhead and support parallel builds on 10 | all platforms. It has no system dependencies, all you need to build 11 | dune and packages using dune is OCaml. You don't need or make or bash 12 | as long as the packages themselves don't use bash explicitly. 13 | 14 | dune supports multi-package development by simply dropping multiple 15 | repositories into the same directory. 16 | 17 | It also supports multi-context builds, such as building against 18 | several opam roots/switches simultaneously. This helps maintaining 19 | packages across several versions of OCaml and gives cross-compilation 20 | for free. 21 | """ 22 | maintainer: ["Jane Street Group, LLC "] 23 | authors: ["Jane Street Group, LLC "] 24 | license: "MIT" 25 | homepage: "https://github.com/ocaml/dune" 26 | doc: "https://dune.readthedocs.io/" 27 | bug-reports: "https://github.com/ocaml/dune/issues" 28 | depends: [ 29 | "ocaml" {>= "4.02"} 30 | "base-unix" 31 | "base-threads" 32 | ] 33 | conflicts: [ 34 | "jbuilder" {!= "transition"} 35 | "odoc" {< "1.3.0"} 36 | "dune-release" {< "1.3.0"} 37 | ] 38 | dev-repo: "git+https://github.com/ocaml/dune.git" 39 | build: [ 40 | # opam 2 sets OPAM_SWITCH_PREFIX, so we don't need a hardcoded path 41 | ["ocaml" "configure.ml" "--libdir" lib] {opam-version < "2"} 42 | ["ocaml" "bootstrap.ml"] 43 | ["./boot.exe" "--release" "--subst"] {pinned} 44 | ["./boot.exe" "--release" "-j" jobs] 45 | ] 46 | url { 47 | src: 48 | "https://github.com/ocaml/dune/releases/download/1.11.4/dune-build-info-1.11.4.tbz" 49 | checksum: [ 50 | "sha256=77cb5f483221b266ded2b85fc84173ae0089a25134a086be922e82c131456ce6" 51 | "sha512=02f00fd872aa49b832fc8c1e928409f23c79ddf84a53009a58875f222cca36fbb92c905e12c539caec9cbad723f195a8aa24218382dca35a903b3f52b11f06f2" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /esy.lock/opam/easy-format.1.3.2/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | build: [ 3 | ["dune" "subst"] {pinned} 4 | ["dune" "build" "-p" name "-j" jobs] 5 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 6 | ["dune" "build" "-p" name "@doc"] {with-doc} 7 | ] 8 | maintainer: ["martin@mjambon.com" "rudi.grinberg@gmail.com"] 9 | authors: ["Martin Jambon"] 10 | bug-reports: "https://github.com/mjambon/easy-format/issues" 11 | homepage: "https://github.com/mjambon/easy-format" 12 | doc: "https://mjambon.github.io/easy-format/" 13 | license: "BSD-3-Clause" 14 | dev-repo: "git+https://github.com/mjambon/easy-format.git" 15 | synopsis: 16 | "High-level and functional interface to the Format module of the OCaml standard library" 17 | description: """ 18 | 19 | This module offers a high-level and functional interface to the Format module of 20 | the OCaml standard library. It is a pretty-printing facility, i.e. it takes as 21 | input some code represented as a tree and formats this code into the most 22 | visually satisfying result, breaking and indenting lines of code where 23 | appropriate. 24 | 25 | Input data must be first modelled and converted into a tree using 3 kinds of 26 | nodes: 27 | 28 | * atoms 29 | * lists 30 | * labelled nodes 31 | 32 | Atoms represent any text that is guaranteed to be printed as-is. Lists can model 33 | any sequence of items such as arrays of data or lists of definitions that are 34 | labelled with something like "int main", "let x =" or "x:".""" 35 | depends: [ 36 | "dune" {>= "1.10"} 37 | "ocaml" {>= "4.02.3"} 38 | ] 39 | url { 40 | src: 41 | "https://github.com/mjambon/easy-format/releases/download/1.3.2/easy-format-1.3.2.tbz" 42 | checksum: [ 43 | "sha256=3440c2b882d537ae5e9011eb06abb53f5667e651ea4bb3b460ea8230fa8c1926" 44 | "sha512=e39377a2ff020ceb9ac29e8515a89d9bdbc91dfcfa871c4e3baafa56753fac2896768e5d9822a050dc1e2ade43c8967afb69391a386c0a8ecd4e1f774e236135" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /esy.lock/opam/fpath.0.7.2/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Daniel Bünzli " 3 | authors: ["Daniel Bünzli "] 4 | homepage: "http://erratique.ch/software/fpath" 5 | doc: "http://erratique.ch/software/fpath/doc" 6 | dev-repo: "git+http://erratique.ch/repos/fpath.git" 7 | bug-reports: "https://github.com/dbuenzli/fpath/issues" 8 | tags: [ "file" "system" "path" "org:erratique" ] 9 | license: "ISC" 10 | depends: [ 11 | "ocaml" {>= "4.01.0"} 12 | "ocamlfind" {build} 13 | "ocamlbuild" {build} 14 | "topkg" {build & >= "0.9.0"} 15 | "result" 16 | "astring" 17 | ] 18 | build: [[ 19 | "ocaml" "pkg/pkg.ml" "build" 20 | "--dev-pkg" "%{pinned}%" ]] 21 | synopsis: "File system paths for OCaml" 22 | description: """ 23 | Fpath is an OCaml module for handling file system paths with POSIX or 24 | Windows conventions. Fpath processes paths without accessing the file 25 | system and is independent from any system library. 26 | 27 | Fpath depends on [Astring][astring] and is distributed under the ISC 28 | license. 29 | 30 | [astring]: http://erratique.ch/software/astring""" 31 | url { 32 | src: "http://erratique.ch/software/fpath/releases/fpath-0.7.2.tbz" 33 | checksum: "md5=52c7ecb0bf180088336f3c645875fa41" 34 | } 35 | -------------------------------------------------------------------------------- /esy.lock/opam/jbuilder.transition/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "opensource@janestreet.com" 3 | authors: ["Jane Street Group, LLC "] 4 | homepage: "https://github.com/ocaml/dune" 5 | bug-reports: "https://github.com/ocaml/dune/issues" 6 | dev-repo: "git+https://github.com/ocaml/dune.git" 7 | license: "MIT" 8 | depends: [ 9 | "ocaml" 10 | "dune" {< "2.0"} 11 | ] 12 | post-messages: [ 13 | "Jbuilder has been renamed and the jbuilder package is now a transition \ 14 | package. Use the dune package instead." 15 | ] 16 | synopsis: 17 | "This is a transition package, jbuilder is now named dune. Use the dune" 18 | description: "package instead." 19 | -------------------------------------------------------------------------------- /esy.lock/opam/junit.2.0.1/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Louis Roché " 3 | authors: "Louis Roché " 4 | homepage: "https://github.com/Khady/ocaml-junit" 5 | bug-reports: "https://github.com/Khady/ocaml-junit/issues" 6 | license: "LGPL-3.0-or-later with OCaml-LGPL-linking-exception" 7 | dev-repo: "git+https://github.com/Khady/ocaml-junit.git" 8 | doc: "https://khady.github.io/ocaml-junit/" 9 | tags: ["junit" "jenkins"] 10 | depends: [ 11 | "dune" {>= "1.0"} 12 | "ptime" 13 | "tyxml" {>= "4.0.0"} 14 | "odoc" {with-doc & >= "1.1.1"} 15 | ] 16 | build: [ 17 | ["dune" "subst"] {pinned} 18 | ["dune" "build" "-p" name "-j" jobs] 19 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 20 | ["dune" "build" "-p" name "-j" jobs] {with-doc} 21 | ] 22 | name: "junit" 23 | synopsis: "JUnit XML reports generation library" 24 | description: "JUnit XML reports generation library" 25 | url { 26 | src: 27 | "https://github.com/Khady/ocaml-junit/releases/download/2.0.1/junit-2.0.1.tbz" 28 | checksum: "md5=40224fb3d4f5e47dc5ff4605587d383b" 29 | } 30 | -------------------------------------------------------------------------------- /esy.lock/opam/lwt.4.3.0/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | 3 | synopsis: "Promises and event-driven I/O" 4 | 5 | version: "4.3.0" 6 | license: "MIT" 7 | homepage: "https://github.com/ocsigen/lwt" 8 | doc: "https://ocsigen.org/lwt/manual/" 9 | bug-reports: "https://github.com/ocsigen/lwt/issues" 10 | 11 | authors: [ 12 | "Jérôme Vouillon" 13 | "Jérémie Dimino" 14 | ] 15 | maintainer: [ 16 | "Anton Bachin " 17 | ] 18 | dev-repo: "git+https://github.com/ocsigen/lwt.git" 19 | 20 | depends: [ 21 | "cppo" {build & >= "1.1.0"} 22 | "dune" {>= "1.7.0"} 23 | "dune-configurator" 24 | "mmap" {>= "1.1.0"} # mmap is needed as long as Lwt supports OCaml < 4.06.0. 25 | "ocaml" {>= "4.02.0"} 26 | "ocplib-endian" 27 | "result" # result is needed as long as Lwt supports OCaml 4.02. 28 | "seq" # seq is needed as long as Lwt supports OCaml < 4.07.0. 29 | 30 | "bisect_ppx" {dev & >= "1.3.0"} 31 | "ocamlfind" {dev & >= "1.7.3-1"} 32 | ] 33 | 34 | depopts: [ 35 | "base-threads" 36 | "base-unix" 37 | "conf-libev" 38 | ] 39 | 40 | conflicts: [ 41 | "ocaml-variants" {= "4.02.1+BER"} 42 | ] 43 | 44 | post-messages: [ 45 | "Lwt 5.0.0 will make some breaking changes in November 2019. See 46 | https://github.com/ocsigen/lwt/issues/584" 47 | ] 48 | 49 | build: [ 50 | ["dune" "build" "-p" name "-j" jobs] 51 | ] 52 | 53 | description: "A promise is a value that may become determined in the future. 54 | 55 | Lwt provides typed, composable promises. Promises that are resolved by I/O are 56 | resolved by Lwt in parallel. 57 | 58 | Meanwhile, OCaml code, including code creating and waiting on promises, runs in 59 | a single thread by default. This reduces the need for locks or other 60 | synchronization primitives. Code can be run in parallel on an opt-in basis." 61 | 62 | url { 63 | src: "https://github.com/ocsigen/lwt/archive/4.3.0.tar.gz" 64 | checksum: "md5=1a72b5ae4245707c12656632a25fc18c" 65 | } 66 | -------------------------------------------------------------------------------- /esy.lock/opam/lwt_ppx.1.2.3/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | 3 | synopsis: "PPX syntax for Lwt, providing something similar to async/await from JavaScript" 4 | 5 | version: "1.2.3" 6 | license: "MIT" 7 | homepage: "https://github.com/ocsigen/lwt" 8 | doc: "https://ocsigen.org/lwt/api/Ppx_lwt" 9 | bug-reports: "https://github.com/ocsigen/lwt/issues" 10 | 11 | authors: [ 12 | "Gabriel Radanne" 13 | ] 14 | maintainer: [ 15 | "Anton Bachin " 16 | ] 17 | dev-repo: "git+https://github.com/ocsigen/lwt.git" 18 | 19 | depends: [ 20 | "dune" 21 | "lwt" 22 | "ocaml" {>= "4.02.0"} 23 | "ocaml-migrate-parsetree" {>= "1.3.0"} 24 | "ppx_tools_versioned" {>= "5.2.3"} 25 | ] 26 | 27 | build: [ 28 | ["dune" "build" "-p" name "-j" jobs] 29 | ] 30 | 31 | url { 32 | src: "https://github.com/ocsigen/lwt/archive/4.3.0.tar.gz" 33 | checksum: "md5=1a72b5ae4245707c12656632a25fc18c" 34 | } 35 | -------------------------------------------------------------------------------- /esy.lock/opam/menhir.20190924/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "francois.pottier@inria.fr" 3 | authors: [ 4 | "François Pottier " 5 | "Yann Régis-Gianas " 6 | ] 7 | homepage: "http://gitlab.inria.fr/fpottier/menhir" 8 | dev-repo: "git+https://gitlab.inria.fr/fpottier/menhir.git" 9 | bug-reports: "menhir@inria.fr" 10 | build: [ 11 | [make "-f" "Makefile" "PREFIX=%{prefix}%" "USE_OCAMLFIND=true" "docdir=%{doc}%/menhir" "libdir=%{lib}%/menhir" "mandir=%{man}%/man1"] 12 | ] 13 | install: [ 14 | [make "-f" "Makefile" "install" "PREFIX=%{prefix}%" "docdir=%{doc}%/menhir" "libdir=%{lib}%/menhir" "mandir=%{man}%/man1"] 15 | ] 16 | depends: [ 17 | "ocaml" {>= "4.02"} 18 | "ocamlfind" {build} 19 | "ocamlbuild" {build} 20 | ] 21 | synopsis: "An LR(1) parser generator" 22 | url { 23 | src: 24 | "https://gitlab.inria.fr/fpottier/menhir/repository/20190924/archive.tar.gz" 25 | checksum: [ 26 | "md5=677f1997fb73177d5a00fa1b8d61c3ef" 27 | "sha512=ea8a9a6d773529cf6ac05e4c6c4532770fbb8e574c9b646efcefe90d9f24544741e3e8cfd94c8afea0447e34059a8c79c2829b46764ce3a3d6dcb3e7f75980fc" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /esy.lock/opam/merlin-extend.0.5/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Frederic Bour " 3 | authors: "Frederic Bour " 4 | homepage: "https://github.com/let-def/merlin-extend" 5 | bug-reports: "https://github.com/let-def/merlin-extend" 6 | license: "MIT" 7 | dev-repo: "git+https://github.com/let-def/merlin-extend.git" 8 | build: [ 9 | ["dune" "subst"] {pinned} 10 | ["dune" "build" "-p" name "-j" jobs] 11 | ] 12 | depends: [ 13 | "dune" {>= "1.0"} 14 | "cppo" {build} 15 | "ocaml" {>= "4.02.3"} 16 | ] 17 | synopsis: "A protocol to provide custom frontend to Merlin" 18 | description: """ 19 | This protocol allows to replace the OCaml frontend of Merlin. 20 | It extends what used to be done with the `-pp' flag to handle a few more cases.""" 21 | doc: "https://let-def.github.io/merlin-extend" 22 | url { 23 | src: 24 | "https://github.com/let-def/merlin-extend/releases/download/v0.5/merlin-extend-v0.5.tbz" 25 | checksum: [ 26 | "sha256=ca3a38c360c7d4827eb4789abf7a6aa4b6e3b4e3c3ef69a5be64dce4601ec227" 27 | "sha512=55c5a3637337abb8ca8db679128a81ca8ccce567bc214d55b2e6444dc0e905b74c64d629bdea2457d0fe4be5306414feefcdbc4d4761fdafd59aa107550936b6" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /esy.lock/opam/merlin.3.3.3/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "defree@gmail.com" 3 | authors: "The Merlin team" 4 | homepage: "https://github.com/ocaml/merlin" 5 | bug-reports: "https://github.com/ocaml/merlin/issues" 6 | dev-repo: "git+https://github.com/ocaml/merlin.git" 7 | build: [ 8 | ["dune" "subst"] {pinned} 9 | ["dune" "build" "-p" name "-j" jobs] 10 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 11 | ] 12 | depends: [ 13 | "ocaml" {>= "4.02.1" & < "4.10"} 14 | "dune" {>= "1.8.0"} 15 | "ocamlfind" {>= "1.5.2"} 16 | "yojson" {>= "1.6.0"} 17 | "mdx" {with-test & >= "1.3.0"} 18 | "conf-jq" {with-test} 19 | ] 20 | synopsis: 21 | "Editor helper, provides completion, typing and source browsing in Vim and Emacs" 22 | description: 23 | "Merlin is an assistant for editing OCaml code. It aims to provide the features available in modern IDEs: error reporting, auto completion, source browsing and much more." 24 | post-messages: [ 25 | "merlin installed. 26 | 27 | Quick setup for VIM 28 | ------------------- 29 | Append this to your .vimrc to add merlin to vim's runtime-path: 30 | let g:opamshare = substitute(system('opam config var share'),'\\n$','','''') 31 | execute \"set rtp+=\" . g:opamshare . \"/merlin/vim\" 32 | 33 | Also run the following line in vim to index the documentation: 34 | :execute \"helptags \" . g:opamshare . \"/merlin/vim/doc\" 35 | 36 | Quick setup for EMACS 37 | ------------------- 38 | Add opam emacs directory to your load-path by appending this to your .emacs: 39 | (let ((opam-share (ignore-errors (car (process-lines \"opam\" \"config\" \"var\" \"share\"))))) 40 | (when (and opam-share (file-directory-p opam-share)) 41 | ;; Register Merlin 42 | (add-to-list 'load-path (expand-file-name \"emacs/site-lisp\" opam-share)) 43 | (autoload 'merlin-mode \"merlin\" nil t nil) 44 | ;; Automatically start it in OCaml buffers 45 | (add-hook 'tuareg-mode-hook 'merlin-mode t) 46 | (add-hook 'caml-mode-hook 'merlin-mode t) 47 | ;; Use opam switch to lookup ocamlmerlin binary 48 | (setq merlin-command 'opam))) 49 | 50 | Take a look at https://github.com/ocaml/merlin for more information 51 | 52 | Quick setup with opam-user-setup 53 | -------------------------------- 54 | 55 | Opam-user-setup support Merlin. 56 | 57 | $ opam user-setup install 58 | 59 | should take care of basic setup. 60 | See https://github.com/OCamlPro/opam-user-setup 61 | " 62 | {success & !user-setup:installed} 63 | ] 64 | url { 65 | src: 66 | "https://github.com/ocaml/merlin/releases/download/v3.3.3/merlin-v3.3.3.tbz" 67 | checksum: [ 68 | "sha256=72909ef47eea1f6fca13b4109a34dccf8fe3923a3c026f1ed1db9eb5ee9aae15" 69 | "sha512=2a5f39d966be56c1322982effc05bc98fd5f66cd12f1f76953f8daa9eca74a58c92a186854f4e601e2f0bb038720691446e7591b4613982accded3e579fedb23" 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /esy.lock/opam/mmap.1.1.0/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "jeremie@dimino.org" 3 | authors: ["Jérémie Dimino " "Anton Bachin" ] 4 | homepage: "https://github.com/mirage/mmap" 5 | bug-reports: "https://github.com/mirage/mmap/issues" 6 | doc: "https://mirage.github.io/mmap/" 7 | dev-repo: "git+https://github.com/mirage/mmap.git" 8 | license: "LGPL-2.1-only with OCaml-LGPL-linking-exception" 9 | build: [ 10 | ["dune" "build" "-p" name "-j" jobs] 11 | ] 12 | depends: [ 13 | "ocaml" 14 | "dune" {>= "1.6"} 15 | ] 16 | synopsis: "File mapping functionality" 17 | description: """ 18 | This project provides a Mmap.map_file functions for mapping files in memory. 19 | """ 20 | url { 21 | src: 22 | "https://github.com/mirage/mmap/releases/download/v1.1.0/mmap-v1.1.0.tbz" 23 | checksum: "md5=8c5d5fbc537296dc525867535fb878ba" 24 | } 25 | -------------------------------------------------------------------------------- /esy.lock/opam/ocaml-migrate-parsetree.1.5.0/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "frederic.bour@lakaban.net" 3 | authors: [ 4 | "Frédéric Bour " 5 | "Jérémie Dimino " 6 | ] 7 | license: "LGPL-2.1 with OCaml linking exception" 8 | homepage: "https://github.com/ocaml-ppx/ocaml-migrate-parsetree" 9 | bug-reports: "https://github.com/ocaml-ppx/ocaml-migrate-parsetree/issues" 10 | dev-repo: "git+https://github.com/ocaml-ppx/ocaml-migrate-parsetree.git" 11 | doc: "https://ocaml-ppx.github.io/ocaml-migrate-parsetree/" 12 | tags: [ "syntax" "org:ocamllabs" ] 13 | build: [ 14 | ["dune" "build" "-p" name "-j" jobs] 15 | ] 16 | depends: [ 17 | "result" 18 | "ppx_derivers" 19 | "dune" {>= "1.9.0"} 20 | "ocaml" {>= "4.02.3"} 21 | ] 22 | synopsis: "Convert OCaml parsetrees between different versions" 23 | description: """ 24 | Convert OCaml parsetrees between different versions 25 | 26 | This library converts parsetrees, outcometree and ast mappers between 27 | different OCaml versions. High-level functions help making PPX 28 | rewriters independent of a compiler version. 29 | """ 30 | url { 31 | src: 32 | "https://github.com/ocaml-ppx/ocaml-migrate-parsetree/releases/download/v1.5.0/ocaml-migrate-parsetree-v1.5.0.tbz" 33 | checksum: [ 34 | "sha256=7f56679c9561552762666de5b6b81c8e4cc2e9fd92272e2269878a2eb534e3c0" 35 | "sha512=87fdccafae83b0437f1ccd4f3cfbc49e699bc0804596480e0df88510ba33410f31d48c7f677fe72800ed3f442a3a586d82d86aee1d12a964f79892833847b16a" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /esy.lock/opam/ocamlbuild.0.14.0/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Gabriel Scherer " 3 | authors: ["Nicolas Pouillard" "Berke Durak"] 4 | homepage: "https://github.com/ocaml/ocamlbuild/" 5 | bug-reports: "https://github.com/ocaml/ocamlbuild/issues" 6 | license: "LGPL-2.1-only with OCaml-LGPL-linking-exception" 7 | doc: "https://github.com/ocaml/ocamlbuild/blob/master/manual/manual.adoc" 8 | dev-repo: "git+https://github.com/ocaml/ocamlbuild.git" 9 | build: [ 10 | [ 11 | make 12 | "-f" 13 | "configure.make" 14 | "all" 15 | "OCAMLBUILD_PREFIX=%{prefix}%" 16 | "OCAMLBUILD_BINDIR=%{bin}%" 17 | "OCAMLBUILD_LIBDIR=%{lib}%" 18 | "OCAMLBUILD_MANDIR=%{man}%" 19 | "OCAML_NATIVE=%{ocaml:native}%" 20 | "OCAML_NATIVE_TOOLS=%{ocaml:native}%" 21 | ] 22 | [make "check-if-preinstalled" "all" "opam-install"] 23 | ] 24 | conflicts: [ 25 | "base-ocamlbuild" 26 | "ocamlfind" {< "1.6.2"} 27 | ] 28 | synopsis: 29 | "OCamlbuild is a build system with builtin rules to easily build most OCaml projects." 30 | depends: [ 31 | "ocaml" {>= "4.03"} 32 | ] 33 | url { 34 | src: "https://github.com/ocaml/ocamlbuild/archive/0.14.0.tar.gz" 35 | checksum: "sha256=87b29ce96958096c0a1a8eeafeb6268077b2d11e1bf2b3de0f5ebc9cf8d42e78" 36 | } 37 | -------------------------------------------------------------------------------- /esy.lock/opam/ocamlfind.1.8.1/files/ocaml-stub: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | BINDIR=$(dirname "$(command -v ocamlc)") 4 | "$BINDIR/ocaml" -I "$OCAML_TOPLEVEL_PATH" "$@" 5 | -------------------------------------------------------------------------------- /esy.lock/opam/ocamlfind.1.8.1/files/ocamlfind.install: -------------------------------------------------------------------------------- 1 | bin: [ 2 | "src/findlib/ocamlfind" {"ocamlfind"} 3 | "?src/findlib/ocamlfind_opt" {"ocamlfind"} 4 | "?tools/safe_camlp4" 5 | ] 6 | toplevel: ["src/findlib/topfind"] 7 | -------------------------------------------------------------------------------- /esy.lock/opam/ocamlfind.1.8.1/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | synopsis: "A library manager for OCaml" 3 | maintainer: "Thomas Gazagnaire " 4 | authors: "Gerd Stolpmann " 5 | homepage: "http://projects.camlcity.org/projects/findlib.html" 6 | bug-reports: "https://gitlab.camlcity.org/gerd/lib-findlib/issues" 7 | dev-repo: "git+https://gitlab.camlcity.org/gerd/lib-findlib.git" 8 | description: """ 9 | Findlib is a library manager for OCaml. It provides a convention how 10 | to store libraries, and a file format ("META") to describe the 11 | properties of libraries. There is also a tool (ocamlfind) for 12 | interpreting the META files, so that it is very easy to use libraries 13 | in programs and scripts. 14 | """ 15 | build: [ 16 | [ 17 | "./configure" 18 | "-bindir" 19 | bin 20 | "-sitelib" 21 | lib 22 | "-mandir" 23 | man 24 | "-config" 25 | "%{lib}%/findlib.conf" 26 | "-no-custom" 27 | "-no-camlp4" {!ocaml:preinstalled & ocaml:version >= "4.02.0"} 28 | "-no-topfind" {ocaml:preinstalled} 29 | ] 30 | [make "all"] 31 | [make "opt"] {ocaml:native} 32 | ] 33 | install: [ 34 | [make "install"] 35 | ["install" "-m" "0755" "ocaml-stub" "%{bin}%/ocaml"] {ocaml:preinstalled} 36 | ] 37 | depends: [ 38 | "ocaml" {>= "4.00.0"} 39 | "conf-m4" {build} 40 | ] 41 | extra-files: [ 42 | ["ocamlfind.install" "md5=06f2c282ab52d93aa6adeeadd82a2543"] 43 | ["ocaml-stub" "md5=181f259c9e0bad9ef523e7d4abfdf87a"] 44 | ] 45 | url { 46 | src: "http://download.camlcity.org/download/findlib-1.8.1.tar.gz" 47 | checksum: "md5=18ca650982c15536616dea0e422cbd8c" 48 | mirrors: "http://download2.camlcity.org/download/findlib-1.8.1.tar.gz" 49 | } 50 | depopts: ["graphics"] 51 | -------------------------------------------------------------------------------- /esy.lock/opam/ocplib-endian.1.0/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | authors: "Pierre Chambart" 3 | maintainer: "pierre.chambart@ocamlpro.com" 4 | homepage: "https://github.com/OCamlPro/ocplib-endian" 5 | build: [ 6 | ["ocaml" "setup.ml" "-configure" "--disable-debug" "--prefix" prefix] 7 | ["ocaml" "setup.ml" "-build"] 8 | ] 9 | install: [ 10 | ["ocaml" "setup.ml" "-install"] 11 | ] 12 | remove: ["ocamlfind" "remove" "ocplib-endian"] 13 | depends: [ 14 | "ocaml" 15 | "base-bytes" 16 | "ocamlfind" 17 | "cppo" {>= "1.1.0"} 18 | "ocamlbuild" {build} 19 | ] 20 | dev-repo: "git+https://github.com/OCamlPro/ocplib-endian.git" 21 | bug-reports: "https://github.com/OCamlPro/ocplib-endian/issues" 22 | synopsis: 23 | "Optimised functions to read and write int16/32/64 from strings and bigarrays, based on new primitives added in version 4.01." 24 | description: """ 25 | The library implements three modules: 26 | * [EndianString](https://github.com/OCamlPro/ocplib-endian/blob/master/src/endianString.cppo.mli) works directly on strings, and provides submodules BigEndian and LittleEndian, with their unsafe counter-parts; 27 | * [EndianBytes](https://github.com/OCamlPro/ocplib-endian/blob/master/src/endianBytes.cppo.mli) works directly on bytes, and provides submodules BigEndian and LittleEndian, with their unsafe counter-parts; 28 | * [EndianBigstring](https://github.com/OCamlPro/ocplib-endian/blob/master/src/endianBigstring.cppo.mli) works on bigstrings (Bigarrays of chars), and provides submodules BigEndian and LittleEndian, with their unsafe counter-parts;""" 29 | flags: light-uninstall 30 | url { 31 | src: "https://github.com/OCamlPro/ocplib-endian/archive/1.0.tar.gz" 32 | checksum: "md5=74b45ba33e189283170a748c2a3ed477" 33 | } 34 | -------------------------------------------------------------------------------- /esy.lock/opam/odoc.1.4.2/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | 3 | version: "1.4.2" 4 | homepage: "http://github.com/ocaml/odoc" 5 | doc: "https://github.com/ocaml/odoc#readme" 6 | bug-reports: "https://github.com/ocaml/odoc/issues" 7 | license: "ISC" 8 | 9 | authors: [ 10 | "Thomas Refis " 11 | "David Sheets " 12 | "Leo White " 13 | ] 14 | maintainer: "Anton Bachin " 15 | dev-repo: "git+https://github.com/ocaml/odoc.git" 16 | 17 | synopsis: "OCaml documentation generator" 18 | 19 | depends: [ 20 | "astring" {build} 21 | "cmdliner" {build & >= "1.0.0"} 22 | "cppo" {build} 23 | "dune" 24 | "fpath" {build} 25 | "ocaml" {>= "4.02.0"} 26 | "result" {build} 27 | "tyxml" {build & >= "4.3.0"} 28 | 29 | "alcotest" {dev & >= "0.8.3"} 30 | "markup" {dev & >= "0.8.0"} 31 | "ocamlfind" {dev} 32 | "sexplib" {dev & >= "113.33.00" & < "v0.13"} 33 | 34 | "bisect_ppx" {with-test & >= "1.3.0"} 35 | ] 36 | 37 | build: [ 38 | ["dune" "subst"] {pinned} 39 | ["dune" "build" "-p" name "-j" jobs] 40 | ] 41 | 42 | url { 43 | src: "https://github.com/ocaml/odoc/archive/1.4.2.tar.gz" 44 | checksum: "md5=d75ce63539040cd199d22203d46fc5f3" 45 | } 46 | -------------------------------------------------------------------------------- /esy.lock/opam/ppx_derivers.1.2.1/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "jeremie@dimino.org" 3 | authors: ["Jérémie Dimino"] 4 | license: "BSD-3-Clause" 5 | homepage: "https://github.com/ocaml-ppx/ppx_derivers" 6 | bug-reports: "https://github.com/ocaml-ppx/ppx_derivers/issues" 7 | dev-repo: "git://github.com/ocaml-ppx/ppx_derivers.git" 8 | build: [ 9 | ["dune" "build" "-p" name "-j" jobs] 10 | ] 11 | depends: [ 12 | "ocaml" 13 | "dune" 14 | ] 15 | synopsis: "Shared [@@deriving] plugin registry" 16 | description: """ 17 | Ppx_derivers is a tiny package whose sole purpose is to allow 18 | ppx_deriving and ppx_type_conv to inter-operate gracefully when linked 19 | as part of the same ocaml-migrate-parsetree driver.""" 20 | url { 21 | src: "https://github.com/ocaml-ppx/ppx_derivers/archive/1.2.1.tar.gz" 22 | checksum: "md5=5dc2bf130c1db3c731fe0fffc5648b41" 23 | } 24 | -------------------------------------------------------------------------------- /esy.lock/opam/ppx_tools_versioned.5.2.3/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "frederic.bour@lakaban.net" 3 | authors: [ 4 | "Frédéric Bour " 5 | "Alain Frisch " 6 | ] 7 | license: "MIT" 8 | homepage: "https://github.com/ocaml-ppx/ppx_tools_versioned" 9 | bug-reports: "https://github.com/ocaml-ppx/ppx_tools_versioned/issues" 10 | dev-repo: "git://github.com/ocaml-ppx/ppx_tools_versioned.git" 11 | tags: [ "syntax" ] 12 | build: [ 13 | ["dune" "subst"] {pinned} 14 | ["dune" "build" "-p" name "-j" jobs] 15 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 16 | ] 17 | depends: [ 18 | "ocaml" {>= "4.02.0"} 19 | "dune" {>= "1.0"} 20 | "ocaml-migrate-parsetree" {>= "1.4.0"} 21 | ] 22 | synopsis: "A variant of ppx_tools based on ocaml-migrate-parsetree" 23 | url { 24 | src: 25 | "https://github.com/ocaml-ppx/ppx_tools_versioned/archive/5.2.3.tar.gz" 26 | checksum: [ 27 | "md5=b1455e5a4a1bcd9ddbfcf712ccbd4262" 28 | "sha512=af20aa0031b9c638537bcdb52c75de95f316ae8fd455a38672a60da5c7c6895cca9dbecd5d56a88c3c40979c6a673a047d986b5b41e1e84b528b7df5d905b9b1" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /esy.lock/opam/ptime.0.8.5/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Daniel Bünzli " 3 | authors: ["The ptime programmers"] 4 | homepage: "https://erratique.ch/software/ptime" 5 | doc: "https://erratique.ch/software/ptime/doc" 6 | dev-repo: "git+http://erratique.ch/repos/ptime.git" 7 | bug-reports: "https://github.com/dbuenzli/ptime/issues" 8 | tags: [ "time" "posix" "system" "org:erratique" ] 9 | license: "ISC" 10 | depends: [ 11 | "ocaml" {>= "4.01.0"} 12 | "ocamlfind" {build} 13 | "ocamlbuild" {build} 14 | "topkg" {build} 15 | "result" 16 | ] 17 | depopts: [ "js_of_ocaml" ] 18 | conflicts: [ "js_of_ocaml" { < "3.3.0" } ] 19 | build:[[ 20 | "ocaml" "pkg/pkg.ml" "build" 21 | "--pinned" "%{pinned}%" 22 | "--with-js_of_ocaml" "%{js_of_ocaml:installed}%" ]] 23 | 24 | synopsis: """POSIX time for OCaml""" 25 | description: """\ 26 | 27 | Ptime has platform independent POSIX time support in pure OCaml. It 28 | provides a type to represent a well-defined range of POSIX timestamps 29 | with picosecond precision, conversion with date-time values, 30 | conversion with [RFC 3339 timestamps][rfc3339] and pretty printing to a 31 | human-readable, locale-independent representation. 32 | 33 | The additional Ptime_clock library provides access to a system POSIX 34 | clock and to the system's current time zone offset. 35 | 36 | Ptime is not a calendar library. 37 | 38 | Ptime depends on the `result` compatibility package. Ptime_clock 39 | depends on your system library. Ptime_clock's optional JavaScript 40 | support depends on [js_of_ocaml][jsoo]. Ptime and its libraries are 41 | distributed under the ISC license. 42 | 43 | [rfc3339]: http://tools.ietf.org/html/rfc3339 44 | [jsoo]: http://ocsigen.org/js_of_ocaml/ 45 | """ 46 | url { 47 | archive: "https://erratique.ch/software/ptime/releases/ptime-0.8.5.tbz" 48 | checksum: "4d48055d623ecf2db792439b3e96a520" 49 | } 50 | -------------------------------------------------------------------------------- /esy.lock/opam/re.1.9.0/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | 3 | maintainer: "rudi.grinberg@gmail.com" 4 | authors: [ 5 | "Jerome Vouillon" 6 | "Thomas Gazagnaire" 7 | "Anil Madhavapeddy" 8 | "Rudi Grinberg" 9 | "Gabriel Radanne" 10 | ] 11 | license: "LGPL-2.0-only with OCaml-LGPL-linking-exception" 12 | homepage: "https://github.com/ocaml/ocaml-re" 13 | bug-reports: "https://github.com/ocaml/ocaml-re/issues" 14 | dev-repo: "git+https://github.com/ocaml/ocaml-re.git" 15 | 16 | build: [ 17 | ["dune" "subst"] {pinned} 18 | ["dune" "build" "-p" name "-j" jobs] 19 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 20 | ] 21 | 22 | depends: [ 23 | "ocaml" {>= "4.02"} 24 | "dune" 25 | "ounit" {with-test} 26 | "seq" 27 | ] 28 | 29 | synopsis: "RE is a regular expression library for OCaml" 30 | description: """ 31 | Pure OCaml regular expressions with: 32 | * Perl-style regular expressions (module Re.Perl) 33 | * Posix extended regular expressions (module Re.Posix) 34 | * Emacs-style regular expressions (module Re.Emacs) 35 | * Shell-style file globbing (module Re.Glob) 36 | * Compatibility layer for OCaml's built-in Str module (module Re.Str) 37 | """ 38 | url { 39 | src: 40 | "https://github.com/ocaml/ocaml-re/releases/download/1.9.0/re-1.9.0.tbz" 41 | checksum: "md5=bddaed4f386a22cace7850c9c7dac296" 42 | } 43 | -------------------------------------------------------------------------------- /esy.lock/opam/result.1.4/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "opensource@janestreet.com" 3 | authors: ["Jane Street Group, LLC "] 4 | homepage: "https://github.com/janestreet/result" 5 | dev-repo: "git+https://github.com/janestreet/result.git" 6 | bug-reports: "https://github.com/janestreet/result/issues" 7 | license: "BSD-3-Clause" 8 | build: [["dune" "build" "-p" name "-j" jobs]] 9 | depends: [ 10 | "ocaml" 11 | "dune" {>= "1.0"} 12 | ] 13 | synopsis: "Compatibility Result module" 14 | description: """ 15 | Projects that want to use the new result type defined in OCaml >= 4.03 16 | while staying compatible with older version of OCaml should use the 17 | Result module defined in this library.""" 18 | url { 19 | src: 20 | "https://github.com/janestreet/result/archive/1.4.tar.gz" 21 | checksum: "md5=d3162dbc501a2af65c8c71e0866541da" 22 | } 23 | -------------------------------------------------------------------------------- /esy.lock/opam/seq.base/files/META.seq: -------------------------------------------------------------------------------- 1 | name="seq" 2 | version="[distributed with OCaml 4.07 or above]" 3 | description="dummy backward-compatibility package for iterators" 4 | requires="" 5 | -------------------------------------------------------------------------------- /esy.lock/opam/seq.base/files/seq.install: -------------------------------------------------------------------------------- 1 | lib:[ 2 | "META.seq" {"META"} 3 | ] 4 | -------------------------------------------------------------------------------- /esy.lock/opam/seq.base/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: " " 3 | authors: " " 4 | homepage: " " 5 | depends: [ 6 | "ocaml" {>= "4.07.0"} 7 | ] 8 | dev-repo: "git+https://github.com/ocaml/ocaml.git" 9 | bug-reports: "https://caml.inria.fr/mantis/main_page.php" 10 | synopsis: 11 | "Compatibility package for OCaml's standard iterator type starting from 4.07." 12 | extra-files: [ 13 | ["seq.install" "md5=026b31e1df290373198373d5aaa26e42"] 14 | ["META.seq" "md5=b33c8a1a6c7ed797816ce27df4855107"] 15 | ] 16 | -------------------------------------------------------------------------------- /esy.lock/opam/topkg.1.0.1/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Daniel Bünzli " 3 | authors: ["Daniel Bünzli "] 4 | homepage: "http://erratique.ch/software/topkg" 5 | doc: "http://erratique.ch/software/topkg/doc" 6 | license: "ISC" 7 | dev-repo: "git+http://erratique.ch/repos/topkg.git" 8 | bug-reports: "https://github.com/dbuenzli/topkg/issues" 9 | tags: ["packaging" "ocamlbuild" "org:erratique"] 10 | depends: [ 11 | "ocaml" {>= "4.03.0"} 12 | "ocamlfind" {build & >= "1.6.1"} 13 | "ocamlbuild" ] 14 | build: [[ 15 | "ocaml" "pkg/pkg.ml" "build" 16 | "--pkg-name" name 17 | "--dev-pkg" "%{pinned}%" ]] 18 | synopsis: """The transitory OCaml software packager""" 19 | description: """\ 20 | 21 | Topkg is a packager for distributing OCaml software. It provides an 22 | API to describe the files a package installs in a given build 23 | configuration and to specify information about the package's 24 | distribution, creation and publication procedures. 25 | 26 | The optional topkg-care package provides the `topkg` command line tool 27 | which helps with various aspects of a package's life cycle: creating 28 | and linting a distribution, releasing it on the WWW, publish its 29 | documentation, add it to the OCaml opam repository, etc. 30 | 31 | Topkg is distributed under the ISC license and has **no** 32 | dependencies. This is what your packages will need as a *build* 33 | dependency. 34 | 35 | Topkg-care is distributed under the ISC license it depends on 36 | [fmt][fmt], [logs][logs], [bos][bos], [cmdliner][cmdliner], 37 | [webbrowser][webbrowser] and `opam-format`. 38 | 39 | [fmt]: http://erratique.ch/software/fmt 40 | [logs]: http://erratique.ch/software/logs 41 | [bos]: http://erratique.ch/software/bos 42 | [cmdliner]: http://erratique.ch/software/cmdliner 43 | [webbrowser]: http://erratique.ch/software/webbrowser 44 | """ 45 | url { 46 | archive: "http://erratique.ch/software/topkg/releases/topkg-1.0.1.tbz" 47 | checksum: "16b90e066d8972a5ef59655e7c28b3e9" 48 | } 49 | -------------------------------------------------------------------------------- /esy.lock/opam/tyxml.4.3.0/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "dev@ocsigen.org" 3 | homepage: "https://github.com/ocsigen/tyxml/" 4 | bug-reports: "https://github.com/ocsigen/tyxml/issues" 5 | doc: "https://ocsigen.org/tyxml/manual/" 6 | dev-repo: "git+https://github.com/ocsigen/tyxml.git" 7 | license: "LGPL-2.1-only with OCaml-LGPL-linking-exception" 8 | 9 | build: [ 10 | ["dune" "subst"] {pinned} 11 | ["dune" "build" "-p" name "-j" jobs] 12 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 13 | ] 14 | 15 | depends: [ 16 | "ocaml" {>= "4.02"} 17 | "re" {>= "1.5.0"} 18 | ("ocaml" {>= "4.07"} | "re" {>= "1.8.0"}) 19 | "dune" 20 | "alcotest" {with-test} 21 | "seq" 22 | "uutf" {>= "1.0.0"} 23 | ] 24 | 25 | synopsis:"TyXML is a library for building correct HTML and SVG documents" 26 | description:""" 27 | TyXML provides a set of convenient combinators that uses the OCaml 28 | type system to ensure the validity of the generated documents. TyXML 29 | can be used with any representation of HTML and SVG: the textual one, 30 | provided directly by this package, or DOM trees (`js_of_ocaml-tyxml`) 31 | virtual DOM (`virtual-dom`) and reactive or replicated trees 32 | (`eliom`). You can also create your own representation and use it to 33 | instantiate a new set of combinators. 34 | 35 | ```ocaml 36 | open Tyxml 37 | let to_ocaml = Html.(a ~a:[a_href "ocaml.org"] [txt "OCaml!"]) 38 | ``` 39 | """ 40 | authors: "The ocsigen team" 41 | url { 42 | src: 43 | "https://github.com/ocsigen/tyxml/releases/download/4.3.0/tyxml-4.3.0.tbz" 44 | checksum: "md5=fd834a567f813bf447cab5f4c3a723e2" 45 | } 46 | -------------------------------------------------------------------------------- /esy.lock/opam/uchar.0.0.2/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Daniel Bünzli " 3 | authors: ["Daniel Bünzli "] 4 | homepage: "http://ocaml.org" 5 | doc: "https://ocaml.github.io/uchar/" 6 | dev-repo: "git+https://github.com/ocaml/uchar.git" 7 | bug-reports: "https://github.com/ocaml/uchar/issues" 8 | tags: [ "text" "character" "unicode" "compatibility" "org:ocaml.org" ] 9 | license: "typeof OCaml system" 10 | depends: [ 11 | "ocaml" {>= "3.12.0"} 12 | "ocamlbuild" {build} 13 | ] 14 | build: [ 15 | ["ocaml" "pkg/git.ml"] 16 | [ 17 | "ocaml" 18 | "pkg/build.ml" 19 | "native=%{ocaml:native}%" 20 | "native-dynlink=%{ocaml:native-dynlink}%" 21 | ] 22 | ] 23 | synopsis: "Compatibility library for OCaml's Uchar module" 24 | description: """ 25 | The `uchar` package provides a compatibility library for the 26 | [`Uchar`][1] module introduced in OCaml 4.03. 27 | 28 | The `uchar` package is distributed under the license of the OCaml 29 | compiler. See [LICENSE](LICENSE) for details. 30 | 31 | [1]: http://caml.inria.fr/pub/docs/manual-ocaml/libref/Uchar.html""" 32 | url { 33 | src: 34 | "https://github.com/ocaml/uchar/releases/download/v0.0.2/uchar-0.0.2.tbz" 35 | checksum: "md5=c9ba2c738d264c420c642f7bb1cf4a36" 36 | } 37 | -------------------------------------------------------------------------------- /esy.lock/opam/uutf.1.0.2/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Daniel Bünzli " 3 | authors: ["Daniel Bünzli "] 4 | homepage: "http://erratique.ch/software/uutf" 5 | doc: "http://erratique.ch/software/uutf/doc/Uutf" 6 | dev-repo: "git+http://erratique.ch/repos/uutf.git" 7 | bug-reports: "https://github.com/dbuenzli/uutf/issues" 8 | tags: [ "unicode" "text" "utf-8" "utf-16" "codec" "org:erratique" ] 9 | license: "ISC" 10 | depends: [ 11 | "ocaml" {>= "4.01.0"} 12 | "ocamlfind" {build} 13 | "ocamlbuild" {build} 14 | "topkg" {build} 15 | "uchar" 16 | ] 17 | depopts: ["cmdliner"] 18 | conflicts: ["cmdliner" { < "0.9.6"} ] 19 | build: [[ 20 | "ocaml" "pkg/pkg.ml" "build" 21 | "--pinned" "%{pinned}%" 22 | "--with-cmdliner" "%{cmdliner:installed}%" ]] 23 | synopsis: """Non-blocking streaming Unicode codec for OCaml""" 24 | description: """\ 25 | 26 | Uutf is a non-blocking streaming codec to decode and encode the UTF-8, 27 | UTF-16, UTF-16LE and UTF-16BE encoding schemes. It can efficiently 28 | work character by character without blocking on IO. Decoders perform 29 | character position tracking and support newline normalization. 30 | 31 | Functions are also provided to fold over the characters of UTF encoded 32 | OCaml string values and to directly encode characters in OCaml 33 | Buffer.t values. 34 | 35 | Uutf has no dependency and is distributed under the ISC license. 36 | """ 37 | url { 38 | archive: "http://erratique.ch/software/uutf/releases/uutf-1.0.2.tbz" 39 | checksum: "a7c542405a39630c689a82bd7ef2292c" 40 | } 41 | -------------------------------------------------------------------------------- /esy.lock/opam/yojson.1.7.0/opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "martin@mjambon.com" 3 | authors: ["Martin Jambon"] 4 | homepage: "https://github.com/ocaml-community/yojson" 5 | bug-reports: "https://github.com/ocaml-community/yojson/issues" 6 | dev-repo: "git+https://github.com/ocaml-community/yojson.git" 7 | doc: "https://ocaml-community.github.io/yojson/" 8 | build: [ 9 | ["dune" "subst"] {pinned} 10 | ["dune" "build" "-p" name "-j" jobs] 11 | ] 12 | run-test: [["dune" "runtest" "-p" name "-j" jobs]] 13 | depends: [ 14 | "ocaml" {>= "4.02.3"} 15 | "dune" 16 | "cppo" {build} 17 | "easy-format" 18 | "biniou" {>= "1.2.0"} 19 | "alcotest" {with-test & >= "0.8.5"} 20 | ] 21 | synopsis: 22 | "Yojson is an optimized parsing and printing library for the JSON format" 23 | description: """ 24 | Yojson is an optimized parsing and printing library for the JSON format. 25 | 26 | It addresses a few shortcomings of json-wheel including 2x speedup, 27 | polymorphic variants and optional syntax for tuples and variants. 28 | 29 | ydump is a pretty-printing command-line program provided with the 30 | yojson package. 31 | 32 | The program atdgen can be used to derive OCaml-JSON serializers and 33 | deserializers from type definitions.""" 34 | url { 35 | src: 36 | "https://github.com/ocaml-community/yojson/releases/download/1.7.0/yojson-1.7.0.tbz" 37 | checksum: "md5=b89d39ca3f8c532abe5f547ad3b8f84d" 38 | } 39 | -------------------------------------------------------------------------------- /esy.lock/overrides/opam__s__ocamlbuild_opam__c__0.14.0_opam_override/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": [ 3 | [ 4 | "bash", 5 | "-c", 6 | "#{os == 'windows' ? 'patch -p1 < ocamlbuild-0.14.0.patch' : 'true'}" 7 | ], 8 | [ 9 | "make", 10 | "-f", 11 | "configure.make", 12 | "all", 13 | "OCAMLBUILD_PREFIX=#{self.install}", 14 | "OCAMLBUILD_BINDIR=#{self.bin}", 15 | "OCAMLBUILD_LIBDIR=#{self.lib}", 16 | "OCAMLBUILD_MANDIR=#{self.man}", 17 | "OCAMLBUILD_NATIVE=true", 18 | "OCAMLBUILD_NATIVE_TOOLS=true" 19 | ], 20 | [ 21 | "make", 22 | "check-if-preinstalled", 23 | "all", 24 | "#{os == 'windows' ? 'install' : 'opam-install'}" 25 | ] 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /esy.lock/overrides/opam__s__ocamlfind_opam__c__1.8.1_opam_override/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": [ 3 | [ 4 | "bash", 5 | "-c", 6 | "#{os == 'windows' ? 'patch -p1 < findlib-1.8.1.patch' : 'true'}" 7 | ], 8 | [ 9 | "./configure", 10 | "-bindir", 11 | "#{self.bin}", 12 | "-sitelib", 13 | "#{self.lib}", 14 | "-mandir", 15 | "#{self.man}", 16 | "-config", 17 | "#{self.lib}/findlib.conf", 18 | "-no-custom", 19 | "-no-topfind" 20 | ], 21 | [ 22 | "make", 23 | "all" 24 | ], 25 | [ 26 | "make", 27 | "opt" 28 | ] 29 | ], 30 | "install": [ 31 | [ 32 | "make", 33 | "install" 34 | ], 35 | [ 36 | "install", 37 | "-m", 38 | "0755", 39 | "ocaml-stub", 40 | "#{self.bin}/ocaml" 41 | ], 42 | [ 43 | "mkdir", 44 | "-p", 45 | "#{self.toplevel}" 46 | ], 47 | [ 48 | "install", 49 | "-m", 50 | "0644", 51 | "src/findlib/topfind", 52 | "#{self.toplevel}/topfind" 53 | ] 54 | ], 55 | "exportedEnv": { 56 | "OCAML_TOPLEVEL_PATH": { 57 | "val": "#{self.toplevel}", 58 | "scope": "global" 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /esy.lock/overrides/opam__s__ocplib_endian_opam__c__1.0_opam_override/files/esy-fix.patch: -------------------------------------------------------------------------------- 1 | --- ./setup.ml 2 | +++ ./setup.ml 3 | @@ -6331,9 +6331,7 @@ 4 | [ 5 | "-classic-display"; 6 | "-no-log"; 7 | - "-no-links"; 8 | - "-install-lib-dir"; 9 | - (Filename.concat (standard_library ()) "ocamlbuild") 10 | + "-no-links" 11 | ] 12 | else 13 | []; 14 | -------------------------------------------------------------------------------- /esy.lock/overrides/opam__s__ocplib_endian_opam__c__1.0_opam_override/files/ocplib-endian-0.8.patch: -------------------------------------------------------------------------------- 1 | --- ./myocamlbuild.ml 2 | +++ ./myocamlbuild.ml 3 | @@ -573,6 +573,24 @@ 4 | Add a dependency after dropping support for 4.01 and earlier. *) 5 | let dispatch_cppo = function 6 | | After_rules -> begin 7 | + let is_directory s = 8 | + let slen = String.length s in 9 | + let s = 10 | + if Sys.os_type <> "Win32" || slen < 2 then 11 | + s 12 | + else 13 | + match s.[slen-1] with 14 | + | '\\' | '/' -> 15 | + if slen <> 3 || s.[1] <> ':' then 16 | + String.sub s 0 (slen -1) 17 | + else 18 | + (match s.[0] with 19 | + | 'A' .. 'Z' | 'a' .. 'z' -> s 20 | + | _ -> String.sub s 0 (slen -1)) 21 | + | _ -> s 22 | + in 23 | + Pathname.is_directory s 24 | + in 25 | let cppo_rules ext = 26 | let dep = "%(name).cppo"-.-ext 27 | and prod1 = "%(name: <*> and not <*.cppo>)"-.-ext 28 | @@ -591,11 +609,11 @@ 29 | pflag ["cppo"] "cppo_D" (fun s -> S [A "-D"; A s]) ; 30 | pflag ["cppo"] "cppo_U" (fun s -> S [A "-U"; A s]) ; 31 | pflag ["cppo"] "cppo_I" (fun s -> 32 | - if Pathname.is_directory s then S [A "-I"; P s] 33 | + if is_directory s then S [A "-I"; P s] 34 | else S [A "-I"; P (Pathname.dirname s)] 35 | ) ; 36 | pdep ["cppo"] "cppo_I" (fun s -> 37 | - if Pathname.is_directory s then [] else [s]) ; 38 | + if is_directory s then [] else [s]) ; 39 | flag ["cppo"; "cppo_q"] (A "-q") ; 40 | flag ["cppo"; "cppo_s"] (A "-s") ; 41 | flag ["cppo"; "cppo_n"] (A "-n") ; 42 | -------------------------------------------------------------------------------- /esy.lock/overrides/opam__s__ocplib_endian_opam__c__1.0_opam_override/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": [ 3 | [ 4 | "bash", 5 | "-c", 6 | "#{os == 'windows' ? 'patch -p1 < ocplib-endian-0.8.patch' : 'true'}" 7 | ], 8 | [ 9 | "bash", 10 | "-c", 11 | "#{os == 'windows' ? 'patch -p1 < esy-fix.patch' : 'true'}" 12 | ], 13 | [ 14 | "ocaml", 15 | "setup.ml", 16 | "-configure", 17 | "--disable-debug", 18 | "--prefix", 19 | "#{self.install}" 20 | ], 21 | [ 22 | "ocaml", 23 | "setup.ml", 24 | "-build" 25 | ] 26 | ], 27 | "install": [ 28 | [ 29 | "ocaml", 30 | "setup.ml", 31 | "-install" 32 | ] 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /example-basic.opam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonstefanov/rarg/0deeeb986f9b3a1b0e1513f98969197ca21ebab6/example-basic.opam -------------------------------------------------------------------------------- /example-lwt.opam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonstefanov/rarg/0deeeb986f9b3a1b0e1513f98969197ca21ebab6/example-lwt.opam -------------------------------------------------------------------------------- /rarg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rarg", 3 | "version": "0.6.3", 4 | "description": "", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/antonstefanov/rarg/tree/master/src/rarg" 8 | }, 9 | "author": { 10 | "name": "Anton Stefanov", 11 | "email": "anton.stefanov@live.com" 12 | }, 13 | "license": "MIT", 14 | "keywords": ["cli", "args", "reasonml", "reason", "ocaml", "esy"], 15 | "esy": { 16 | "build": "refmterr dune build -p rarg", 17 | "install": "esy-installer rarg.install" 18 | }, 19 | "scripts": { 20 | "release": "node ./scripts/esy-prepublish.js rarg.json" 21 | }, 22 | "homepage": "https://github.com/antonstefanov/rarg", 23 | "bugs": "https://github.com/antonstefanov/rarg/issues", 24 | "dependencies": { 25 | "@opam/dune": "^1.10.0", 26 | "@opam/lwt": "4.3.0", 27 | "@opam/lwt_ppx": "1.2.3", 28 | "@reason-native/console": "*", 29 | "@reason-native/pastel": "0.2.1", 30 | "@esy-ocaml/reason": ">= 3.4.0 < 3.6.0", 31 | "refmterr": "*", 32 | "ocaml": "~4.8.1000", 33 | "@reason-native/rely": "*" 34 | }, 35 | "devDependencies": { 36 | "@opam/merlin": "*", 37 | "ocaml": "~4.8.1000", 38 | "@opam/odoc": "*" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /rarg.opam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonstefanov/rarg/0deeeb986f9b3a1b0e1513f98969197ca21ebab6/rarg.opam -------------------------------------------------------------------------------- /rarg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonstefanov/rarg/0deeeb986f9b3a1b0e1513f98969197ca21ebab6/rarg.png -------------------------------------------------------------------------------- /scripts/test-ci.cmd: -------------------------------------------------------------------------------- 1 | :; set -e 2 | 3 | :; # This will be the location of this script. 4 | :; DIR="$(cd "$(dirname "$0")" && pwd)" 5 | 6 | :; # Run RargTests.exe with correct root set. 7 | :; RARG_ROOT="$DIR/../" esy x "TestCi.exe" "$@" 8 | -------------------------------------------------------------------------------- /scripts/test-dev.cmd: -------------------------------------------------------------------------------- 1 | :; set -e 2 | 3 | :; # This will be the location of this script. 4 | :; DIR="$(cd "$(dirname "$0")" && pwd)" 5 | 6 | :; # Run RargTests.exe with correct root set. 7 | :; RARG_ROOT="$DIR/../" esy x "TestDev.exe" "$@" 8 | -------------------------------------------------------------------------------- /src/examples/example-basic/ExampleBasic.re: -------------------------------------------------------------------------------- 1 | let main = { 2 | switch (Rarg.Run.autorun(Cmds.ExampleBasicCmds.CmdStart.cmd)) { 3 | | Ok(_) => exit(0) 4 | | Error(_) => exit(1) 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /src/examples/example-basic/cmds/ExampleBasicCmds.re: -------------------------------------------------------------------------------- 1 | module T = Rarg.Type; 2 | module Cmd = Rarg.Cmd; 3 | module Args = Rarg.Args; 4 | 5 | module CmdFruits = { 6 | type fruit = 7 | | Apple 8 | | Banana; 9 | let fruit: T.t(fruit) = { 10 | name: "fruit", 11 | parse: 12 | fun 13 | | "apple" => Ok(Apple) 14 | | "banana" => Ok(Banana) 15 | | x => Error(Some(x ++ " is not a fruit.")), 16 | stringify: 17 | fun 18 | | Apple => "apple" 19 | | Banana => "banana", 20 | choices: Some(HelpAndSuggestions([Apple, Banana])), 21 | }; 22 | 23 | let args = []; 24 | let (args, getFruits) = 25 | Args.Many.req(~args, ~name="--fruits", ~doc="Fruits to cook", fruit); 26 | let (args, getBake) = 27 | Args.One.boolFlag( 28 | ~args, 29 | ~name="--bake", 30 | ~doc="Whether to bake the fruit", 31 | T.bool, 32 | ); 33 | let (args, getCut) = 34 | Args.One.boolFlag( 35 | ~args, 36 | ~name="--cut", 37 | ~doc="Whether to cut the fruit", 38 | T.bool, 39 | ); 40 | 41 | let handle = (~fruits: list(fruit), ~bake: bool, ~cut: bool) => (); 42 | let run = m => 43 | handle(~fruits=getFruits(m), ~bake=getBake(m), ~cut=getCut(m)); 44 | let cmd: Cmd.t(unit) = 45 | Cmd.make(~name="Fruits", ~version="1.1", ~args, ~run, ()); 46 | }; 47 | 48 | module CmdCar = { 49 | let car = T.withChoices(T.string, T.Choices.Suggestions(["alfa", "bmw"])); 50 | 51 | let args = []; 52 | let (args, getCar) = 53 | Args.One.req(~args, ~name="--car", ~alias="-c", ~doc="Car to build", car); 54 | let (args, getCat) = Args.One.req(~args, ~name="--cat", ~doc="Cat", car); 55 | let (args, getColor) = 56 | Args.One.default( 57 | ~args, 58 | ~name="--color", 59 | ~doc="Paint color", 60 | ~default="green", 61 | T.string, 62 | ); 63 | let model = 64 | T.withChoices( 65 | T.string, 66 | T.Choices.Dynamic( 67 | (argsMap, _) => 68 | switch (getCar(argsMap)) { 69 | | car => 70 | switch (car) { 71 | | "alfa" => ["4C"] 72 | | "bmw" => ["3-series", "5-series"] 73 | | _ => [] 74 | } 75 | // suggestions are called before input validation -> getCar is not safe to call 76 | | exception e => [] 77 | }, 78 | ), 79 | ); 80 | let (args, getModel) = 81 | Args.One.opt(~args, ~name="--model", ~doc="The car model", model); 82 | 83 | let handle = 84 | (~car: string, ~cat: string, ~color: string, ~model: option(string)) => 85 | (); 86 | let run = map => 87 | handle( 88 | ~car=getCar(map), 89 | ~cat=getCat(map), 90 | ~color=getColor(map), 91 | ~model=getModel(map), 92 | ); 93 | let cmd: Cmd.t(unit) = 94 | Cmd.make( 95 | ~name="Build a car", 96 | ~version="1.2", 97 | ~doc= 98 | "Allows you to build amazing " 99 | ++ "cars" 100 | ++ " and more", 101 | ~args, 102 | ~run, 103 | ~children=[("fruit", CmdFruits.cmd)], 104 | (), 105 | ); 106 | }; 107 | 108 | module CmdFind = { 109 | let args = []; 110 | let (args, getPatterns) = 111 | Args.Positional.Many.req( 112 | ~args, 113 | ~name="patterns", 114 | ~doc="Patterns to find", 115 | T.string, 116 | ); 117 | let (args, getCopy) = 118 | Args.One.boolFlag( 119 | ~args, 120 | ~name="--copy", 121 | ~doc="Whether to copy the results", 122 | T.bool, 123 | ); 124 | let (args, getBranch) = 125 | Args.One.opt(~args, ~name="--branch", ~doc="Branch to find", T.branch); 126 | 127 | let handle = 128 | (~patterns: list(string), ~branch: option(string), ~copy: bool) => { 129 | print_endline("______________________"); 130 | print_endline("patterns"); 131 | print_endline(patterns |> String.concat(" | ", _)); 132 | print_endline("______________________"); 133 | print_endline("branch"); 134 | print_endline(branch |> Seed.Option.getDefault(_, ~default="__None__")); 135 | print_endline("______________________"); 136 | print_endline("copy"); 137 | print_endline(copy |> string_of_bool); 138 | print_endline("______________________"); 139 | }; 140 | let run = m => 141 | handle( 142 | ~patterns=getPatterns(m), 143 | ~branch=getBranch(m), 144 | ~copy=getCopy(m), 145 | ); 146 | 147 | let cmd: Cmd.t(unit) = 148 | Cmd.make(~name="Find", ~version="1.3", ~args, ~run, ()); 149 | }; 150 | 151 | module CmdStart = { 152 | let args = []; 153 | let run = m => 154 | print_endline("Forgot to add a subcommand, enter --help for help."); 155 | let cmd: Cmd.t(unit) = 156 | Cmd.make( 157 | ~name="Start", 158 | ~version="1.0", 159 | ~args, 160 | ~run, 161 | ~children=[ 162 | ("fruits", CmdFruits.cmd), 163 | ("car", CmdCar.cmd), 164 | ("find", CmdFind.cmd), 165 | ], 166 | (), 167 | ); 168 | }; 169 | -------------------------------------------------------------------------------- /src/examples/example-basic/cmds/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name Cmds) 3 | (libraries rarg pastel.lib)) 4 | -------------------------------------------------------------------------------- /src/examples/example-basic/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name ExampleBasic) 3 | (public_name example-basic) 4 | (package example-basic) 5 | (libraries rarg Cmds)) 6 | -------------------------------------------------------------------------------- /src/examples/example-lwt/ExampleLwt.re: -------------------------------------------------------------------------------- 1 | /** 2 | You can define a logo to be displayed in --help 3 | */ 4 | let logo = 5 | 6 | {| 7 | ██████╗ █████╗ ██████╗ ██████╗ 8 | ██╔══██╗██╔══██╗██╔══██╗██╔════╝ 9 | ██████╔╝███████║██████╔╝██║ ███╗ 10 | ██╔══██╗██╔══██║██╔══██╗██║ ██║ 11 | ██║ ██║██║ ██║██║ ██║╚██████╔╝ 12 | ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ 13 | |} 14 | ; 15 | // esy x example-lwt --help 16 | let main = 17 | Lwt_main.run( 18 | { 19 | switch (Rarg.Run.autorun(~logo, LwtCmds.ExampleLwtCmds.CmdStart.cmd)) { 20 | | Error(e) => 21 | switch (e) { 22 | | ConfigError => exit(100) 23 | | UserError => exit(10) 24 | | UnknownError => exit(1) 25 | } 26 | | Ok(ok) => 27 | switch (ok) { 28 | | Handled => Lwt.return() 29 | | Run(promise) => 30 | let%lwt result = promise; 31 | switch (result) { 32 | | Ok(ok) => 33 | switch (ok) { 34 | // normally there shouldn't be a need to return ok or error variants 35 | // since these outcomes can be handled directly in the cmd handler 36 | // however for illustration purposes - you can propagate to the top any error or success 37 | | FoundFiles => Lwt_io.printl("ExampleLwt.FoundFiles") 38 | | CutFruits(fruits) => 39 | let%lwt () = Lwt_io.printl("Successfully cut fruits:"); 40 | fruits 41 | |> List.map(LwtCmds.ExampleLwtCmds.CmdCut.fruit.stringify) 42 | |> String.concat(", ", _) 43 | |> Lwt_io.printl(_); 44 | } 45 | | Error(e) => 46 | let%lwt () = Lwt_io.printl(e); 47 | exit(1); 48 | }; 49 | } 50 | }; 51 | }, 52 | ); 53 | -------------------------------------------------------------------------------- /src/examples/example-lwt/cmds/ExampleLwtCmds.re: -------------------------------------------------------------------------------- 1 | module T = Rarg.Type; 2 | module Cmd = Rarg.Cmd; 3 | module Args = Rarg.Args; 4 | 5 | type fruit = 6 | | Apple 7 | | Banana; 8 | type ok = 9 | | FoundFiles 10 | | CutFruits(list(fruit)); 11 | type cmdResult = result(ok, string); 12 | 13 | module CmdFind = { 14 | let args = []; 15 | let (args, getPatterns) = 16 | Args.Positional.Many.req( 17 | ~args, 18 | ~name="patterns", 19 | ~doc="Patterns to find", 20 | T.string, 21 | ); 22 | let handle = (~patterns: list(string)): Lwt.t(cmdResult) => { 23 | let%lwt () = Lwt_unix.sleep(0.5); 24 | ( 25 | switch (patterns) { 26 | | [] => Error("No files provided") 27 | | [_x] => Ok(FoundFiles) 28 | | _ => Error("Could not find any files") 29 | } 30 | ) 31 | |> Lwt.return; 32 | }; 33 | let run = m => handle(~patterns=getPatterns(m)); 34 | let cmd: Cmd.t(Lwt.t(cmdResult)) = 35 | Cmd.make(~name="Find", ~version="1.1", ~args, ~run, ()); 36 | }; 37 | 38 | module CmdCut = { 39 | let fruit: T.t(fruit) = { 40 | name: "fruit", 41 | parse: 42 | fun 43 | | "apple" => Ok(Apple) 44 | | "banana" => Ok(Banana) 45 | | x => Error(Some(x ++ " is not a fruit.")), 46 | stringify: 47 | fun 48 | | Apple => "apple" 49 | | Banana => "banana", 50 | choices: Some(T.Choices.HelpAndSuggestions([Apple, Banana])), 51 | }; 52 | let args = []; 53 | let (args, getFruits) = 54 | Args.Positional.Many.req( 55 | ~args, 56 | ~name="fruits", 57 | ~doc="Fruits to cut", 58 | fruit, 59 | ); 60 | 61 | let handle = (~fruits: list(fruit)): Lwt.t(cmdResult) => { 62 | let%lwt () = Lwt_unix.sleep(0.5); 63 | Ok(CutFruits(fruits)) |> Lwt.return; 64 | }; 65 | let run = m => handle(~fruits=getFruits(m)); 66 | let cmd: Cmd.t(Lwt.t(cmdResult)) = 67 | Cmd.make(~name="Cut fruits", ~version="1.2", ~args, ~run, ()); 68 | }; 69 | 70 | module CmdStart = { 71 | let args = []; 72 | /** 73 | The whole commands tree needs to return the same type -> we use `Lwt.return`. 74 | Alternatively you could define [type res = | Sync(unit) | Async(Lwt.t(cmdResult))] and support both sync and async 75 | */ 76 | let run = _m => 77 | Error("Forgot to add a subcommand, enter --help for help.") 78 | |> Lwt.return(_); 79 | let cmd: Cmd.t(Lwt.t(cmdResult)) = 80 | Cmd.make( 81 | ~name="Start", 82 | ~version="1.0", 83 | ~args, 84 | ~run, 85 | ~children=[("find", CmdFind.cmd), ("cut", CmdCut.cmd)], 86 | (), 87 | ); 88 | }; 89 | -------------------------------------------------------------------------------- /src/examples/example-lwt/cmds/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name LwtCmds) 3 | (libraries rarg lwt lwt.unix) 4 | (preprocess (pps lwt_ppx))) 5 | -------------------------------------------------------------------------------- /src/examples/example-lwt/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name ExampleLwt) 3 | (public_name example-lwt) 4 | (package example-lwt) 5 | (libraries rarg lwt lwt.unix LwtCmds) 6 | (preprocess (pps lwt_ppx))) 7 | -------------------------------------------------------------------------------- /src/rarg/Argv.re: -------------------------------------------------------------------------------- 1 | type t = { 2 | appName: string, 3 | appPath: string, 4 | args: array(string), 5 | argsMap: ArgsMap.t, 6 | }; 7 | let getExeAndArgs = (argv: array(string)): (string, array(string)) => ( 8 | argv[0], 9 | Seed.Arr.slice(argv, ~starti=1, ()), 10 | ); 11 | let getBasename = (path: string): string => Filename.basename(path); 12 | let removeExtension = (filename: string) => 13 | try (Filename.chop_extension(filename)) { 14 | | _ => filename 15 | }; 16 | let make = (argv: array(string)): t => { 17 | let (appPath, args) = getExeAndArgs(argv); 18 | { 19 | args, 20 | appPath, 21 | appName: 22 | appPath 23 | |> getBasename(_) 24 | |> removeExtension(_) 25 | |> String.lowercase_ascii(_), 26 | argsMap: ArgsMap.ofArray(args), 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/rarg/Cmd.re: -------------------------------------------------------------------------------- 1 | type argsMap = ArgsMap.t; 2 | type run('a) = argsMap => 'a; 3 | 4 | type t('a) = CmdInternal.t('a); 5 | 6 | module StringMap = CmdInternal.StringMap; 7 | module Action = CmdInternal.Action; 8 | module Err = CmdInternal.Err; 9 | 10 | type cmd_t('a) = t('a); 11 | 12 | /** 13 | An error that results from the validation of a command 14 | */ 15 | type validateErr('a) = list((t('a), Err.Config.t)); 16 | 17 | /** follows the sub commands tree and returns whether there are any configuration errors */ 18 | let validate = (cmd: t('a)): result(unit, validateErr('a)) => { 19 | let rec aux = (~cmds: list(t('a)), ~acc) => { 20 | switch (cmds) { 21 | | [] => acc 22 | | [cmd, ...restCmds] => 23 | let nextAcc = 24 | switch (CmdInternal.Validate.definedArgs(cmd.args)) { 25 | | Ok(_) => acc 26 | | Error(err) => [(cmd, err), ...acc] 27 | }; 28 | switch (cmd.children) { 29 | | None => aux(~cmds=restCmds, ~acc=nextAcc) 30 | | Some(children) => 31 | aux(~cmds=restCmds @ StringMap.values(children), ~acc=nextAcc) 32 | }; 33 | }; 34 | }; 35 | 36 | switch (aux(~cmds=[cmd], ~acc=[])) { 37 | | [] => Ok() 38 | | errors => Error(errors) 39 | }; 40 | }; 41 | 42 | let make = 43 | ( 44 | ~name: string, 45 | ~version: string, 46 | ~doc: option(string)=?, 47 | ~args: list((Args.t, Args.validate)), 48 | ~run: CmdInternal.run('a), 49 | ~children: option(list((StringMap.key, t('a))))=?, 50 | (), 51 | ) 52 | : t('a) => { 53 | name, 54 | version, 55 | doc, 56 | args, 57 | run, 58 | children: Seed.Option.map(children, CmdInternal.Sub.ofList), 59 | }; 60 | -------------------------------------------------------------------------------- /src/rarg/Cmd.rei: -------------------------------------------------------------------------------- 1 | type argsMap = ArgsMap.t; 2 | type run('a) = argsMap => 'a; 3 | 4 | type t('a) = CmdInternal.t('a); 5 | 6 | module Action = CmdInternal.Action; 7 | module Err = CmdInternal.Err; 8 | 9 | /** 10 | An error that results from the validation of a command 11 | */ 12 | type validateErr('a) = list((t('a), Err.Config.t)); 13 | 14 | /** 15 | Follows the sub commands tree and returns whether there are any configuration errors. 16 | You probably want to call this function with your top level command in your tests. 17 | This would ensure that you don't ship any configuration errors to users. 18 | */ 19 | let validate: t('a) => result(unit, list((t('a), Err.Config.t))); 20 | 21 | /** 22 | Create a command. 23 | All commands from a tree need to return the same result from their [run] function. 24 | If you need to differentiate the results from different commands you can define a variant 25 | and use it as a return type. 26 | For example: 27 | {[ 28 | type fruit = | Apple | Banana; 29 | type ok = | PeeledFruit(fruit) | CutFruits(list(fruit)); 30 | type cmdResult = result(ok, string); 31 | let run = (..): Lwt.t(cmdResult) => .. 32 | ]} 33 | 34 | You can check the local {{:https://github.com/antonstefanov/rarg/tree/master/src/examples} examples} 35 | or the repo {{:https://github.com/antonstefanov/rarg-examples} rarg-examples} for more complete examples. 36 | */ 37 | let make: 38 | ( 39 | ~name: string, 40 | ~version: string, 41 | ~doc: string=?, 42 | ~args: list((Args.t, Args.validate)), 43 | ~run: CmdInternal.run('a), 44 | ~children: list((string, t('a)))=?, 45 | unit 46 | ) => 47 | t('a); 48 | -------------------------------------------------------------------------------- /src/rarg/Components.re: -------------------------------------------------------------------------------- 1 | module Span = 2 | Pastel.Make({}); 3 | 4 | let isSome = Seed.Option.isSome; 5 | let getMaxLength = items => 6 | List.fold_left( 7 | (acc, text) => 8 | switch (String.length(text)) { 9 | | len when len > acc => len 10 | | _ => acc 11 | }, 12 | 0, 13 | items, 14 | ); 15 | let leftRightLines = 16 | (items: list((string, string))): list((string, int, string)) => { 17 | let maxLength = getMaxLength(List.map(((left, _)) => left, items)); 18 | List.map( 19 | ((left, right)) => (left, maxLength - String.length(left), right), 20 | items, 21 | ); 22 | }; 23 | let getLeftRightLine = (~startSpace, ~left, ~midSpace, ~right) => { 24 | let startSpace = Seed.Strings.repeat(" ", ~times=startSpace); 25 | let midSpace = Seed.Strings.repeat(" ", ~times=midSpace); 26 | 27 | startSpace 28 | left 29 | midSpace 30 | right 31 | ; 32 | }; 33 | let spacedLeftRightLines = (items: list((string, int, string))) => { 34 | List.map( 35 | ((left, midSpace, right)) => 36 | getLeftRightLine(~startSpace=2, ~left, ~midSpace=midSpace + 2, ~right), 37 | items, 38 | ); 39 | }; 40 | let tupleLines = (lines: list((string, string))): list(string) => 41 | lines |> leftRightLines(_) |> spacedLeftRightLines(_); 42 | 43 | let maybeIndent = (items: list(string), indent: int) => 44 | indent == 0 45 | ? items 46 | : List.map( 47 | item => Seed.Strings.repeat(" ", ~times=indent) ++ item, 48 | items, 49 | ); 50 | 51 | let renderIfSome = (maybe: option('a), render: 'a => string) => 52 | switch (maybe) { 53 | | None => "" 54 | | Some(some) => render(some) 55 | }; 56 | let renderIfTrue = (cond: bool, render: unit => string) => 57 | cond ? render() : ""; 58 | 59 | module Lines = { 60 | let createElement = 61 | (~children: list(string), ~indent=0, ~marginBottom=0, ()) => 62 | String.concat("\n", maybeIndent(children, indent)) 63 | ++ Seed.Strings.repeat("\n", ~times=marginBottom); 64 | }; 65 | module Line = { 66 | let createElement = 67 | (~children: list(string), ~indent=0, ~marginBottom=0, ()) => 68 | String.concat("", maybeIndent(children, indent)) 69 | ++ Seed.Strings.repeat("\n", ~times=marginBottom); 70 | }; 71 | module Maybe = { 72 | let createElement = (~cond: bool, ~children, ()) => 73 | switch (cond) { 74 | | false => "" 75 | | true => ...children 76 | }; 77 | }; 78 | module MaybeText = { 79 | let createElement = (~text: option(string), ~children as _, ()) => 80 | switch (text) { 81 | | None => "" 82 | | Some(text) => text 83 | }; 84 | }; 85 | module NewLine = { 86 | let createElement = () => "\n"; 87 | }; 88 | -------------------------------------------------------------------------------- /src/rarg/ComponentsTips.re: -------------------------------------------------------------------------------- 1 | open Components; 2 | 3 | module AddPathTip = { 4 | let installTip = 5 | ( 6 | ~appPath, 7 | ~shell: option(Seed.Process.Shell.t), 8 | ~platform: option(Seed.Os.Platform.t)=?, 9 | (), 10 | ) => { 11 | let shellLocation = 12 | Seed.Process.Shell.getConfigLocation(~shell?, ~platform?, ()); 13 | // {{app_path}} --rarg-add-path >> {{bashrc_location}} 14 | String.concat(" ", [appPath, ArgsMap.addPathKey, ">>", shellLocation]); 15 | }; 16 | let createElement = 17 | ( 18 | ~appPath, 19 | ~shell: option(Seed.Process.Shell.t)=?, 20 | ~platform: option(Seed.Os.Platform.t)=?, 21 | ~children as _, 22 | (), 23 | ) => { 24 | let tip = installTip(~appPath, ~shell, ~platform?, ()); 25 | 26 | 27 | "To append to your shell config run:" 28 | 29 | tip 30 | 31 | "Afterwards restart your terminal" 32 | 33 | ; 34 | }; 35 | }; 36 | 37 | module AddPathScript = { 38 | let createElement = (~appName, ~appPath, ~children as _, ()) => { 39 | let appAlias = 40 | String.concat("", ["alias ", appName, "=", "'", appPath, "'"]); 41 | 42 | "# " appName appAlias ; 43 | }; 44 | }; 45 | 46 | module AutocompleteTip = { 47 | let installTip = 48 | ( 49 | ~appName, 50 | ~shell: option(Seed.Process.Shell.t), 51 | ~platform: option(Seed.Os.Platform.t)=?, 52 | (), 53 | ) => { 54 | let shellLocation = 55 | Seed.Process.Shell.getConfigLocation(~shell?, ~platform?, ()); 56 | // {{app_path}} --rarg-add-path >> {{bashrc_location}} 57 | String.concat( 58 | " ", 59 | [appName, ArgsMap.suggestionsScriptKey, ">>", shellLocation], 60 | ); 61 | }; 62 | let createElement = 63 | ( 64 | ~appName, 65 | ~shell: option(Seed.Process.Shell.t)=?, 66 | ~platform: option(Seed.Os.Platform.t)=?, 67 | ~children as _, 68 | (), 69 | ) => { 70 | let tip = installTip(~appName, ~shell, ~platform?, ()); 71 | 72 | 73 | "To append to your shell config run:" 74 | 75 | tip 76 | 77 | "Afterwards restart your terminal" 78 | 79 | ; 80 | }; 81 | }; 82 | 83 | module AutocompleteScript = { 84 | let createElement = 85 | ( 86 | ~appName, 87 | ~appPath, 88 | ~shell: option(Seed.Process.Shell.t)=?, 89 | ~children as _, 90 | (), 91 | ) => { 92 | 93 | {TerminalTemplates.Autocomplete.replace(~appName, ~appPath, ~shell?, ())} 94 | ; 95 | }; 96 | }; 97 | -------------------------------------------------------------------------------- /src/rarg/Rarg.re: -------------------------------------------------------------------------------- 1 | include RargInternal.RargAPI; 2 | -------------------------------------------------------------------------------- /src/rarg/RargAPI.re: -------------------------------------------------------------------------------- 1 | /** 2 | {!module:RargInternal.Args} - define your command arguments, including their name, documentation and {!module:RargInternal.Type}. 3 | */ 4 | module Args = Args; 5 | 6 | /** 7 | {!module:RargInternal.Type} - predefined type parsers and utilities for building your own. 8 | For example [string] [bool] and etc. 9 | Used to parse the input arguments to usable types. 10 | */ 11 | module Type = Type; 12 | 13 | /** 14 | {!module:RargInternal.Cmd} - define your commands, including what arguments and sub commands tree. 15 | */ 16 | module Cmd = { 17 | include Cmd; 18 | 19 | /** 20 | Convert validation errors to string 21 | */ 22 | let validateErrToString = (errors: validateErr('a)): list(string) => 23 | List.map( 24 | ((cmd: t('a), err: Cmd.Err.Config.t)) => 25 | 26 | 27 | {cmd.name} 28 | 29 | 30 | {switch (err) { 31 | | DuplicateArgs(duplicates) => 32 | 33 | | InvalidArgNames(invalidArgs) => 34 | 35 | }} 36 | 37 | , 38 | errors, 39 | ); 40 | }; 41 | 42 | /** 43 | {!module:RargInternal.Run} - run your commands 44 | */ 45 | module Run = Run; 46 | -------------------------------------------------------------------------------- /src/rarg/Recommendations.re: -------------------------------------------------------------------------------- 1 | /* 2 | Translated to reasonml from http://rosettacode.org/wiki/Levenshtein_distance#OCaml 3 | */ 4 | let minimum = (a, b, c) => min(a, min(b, c)); 5 | 6 | let levenshteinDistance = (s, t) => { 7 | let m = String.length(s) 8 | and n = String.length(t); 9 | /* for all i and j, d.(i).(j) will hold the Levenshtein distance between 10 | the first i characters of s and the first j characters of t */ 11 | let d = Array.make_matrix(m + 1, n + 1, 0); 12 | 13 | for (i in 0 to m) { 14 | d[i][0] = i; 15 | }; /* the distance of any first string to an empty second string */ 16 | for (j in 0 to n) { 17 | d[0][j] = j; 18 | }; /* the distance of any second string to an empty first string */ 19 | 20 | for (j in 1 to n) { 21 | for (i in 1 to m) 22 | { 23 | if (s.[i - 1] == t.[j - 1]) { 24 | d[i][j] = d[i - 1][j - 1]; 25 | } else { 26 | /* no operation required */ 27 | d[i][j] = 28 | minimum( 29 | d[i - 1][j] + 1, /* a deletion */ 30 | d[i][j - 1] + 1, /* an insertion */ 31 | d[i - 1][j - 1] + 1, 32 | ); 33 | }; 34 | }; /* a substitution */ 35 | }; 36 | 37 | d[m][n]; 38 | }; 39 | 40 | module S = Seed.DataStructures.StringMap; 41 | 42 | module Internal = { 43 | let get = 44 | (str, ~threshold=3, ~candidates: list('a), ~getValue: 'a => string, ()) 45 | : list(('a, int)) => { 46 | let add = (map, ~candidate: 'a, ~value: string, ~distance: int) => { 47 | switch (S.getOpt(value, map)) { 48 | | None => S.set(value, (distance, candidate), map) 49 | | Some((existingDistance, _)) when distance < existingDistance => 50 | S.set(value, (distance, candidate), map) 51 | | Some(_) => map 52 | }; 53 | }; 54 | 55 | let entries = 56 | List.fold_left( 57 | (acc, candidate) => { 58 | let value = getValue(candidate); 59 | switch (levenshteinDistance(str, value)) { 60 | | distance when distance <= threshold => 61 | add(acc, ~candidate, ~value, ~distance) 62 | | _ => acc 63 | }; 64 | }, 65 | S.empty, 66 | candidates, 67 | ) 68 | |> S.entries(_); 69 | List.map(((_, (d, v))) => (v, d), entries); 70 | }; 71 | let sort = (~compare, recommendations: list(('a, int))): list(('a, int)) => 72 | List.sort( 73 | ((v1, distance1), (v2, distance2)) => 74 | switch (distance1 - distance2) { 75 | | 0 => compare(v1, v2) 76 | | d => d 77 | }, 78 | recommendations, 79 | ); 80 | }; 81 | // String.compare(name1, name2) 82 | let get = 83 | ( 84 | str, 85 | ~threshold=3, 86 | ~top=5, 87 | ~candidates: list('a), 88 | ~compare: ('a, 'a) => int, 89 | ~getValue: 'a => string, 90 | (), 91 | ) 92 | : list('a) => { 93 | let rec getTop = (recommendations, ~maxi, ~acc, ~i) => 94 | i >= maxi 95 | ? acc 96 | : ( 97 | switch (recommendations) { 98 | | [] => acc 99 | | [(x, _), ...xs] => getTop(xs, ~maxi, ~acc=[x, ...acc], ~i=i + 1) 100 | } 101 | ); 102 | Internal.get(str, ~threshold, ~candidates, ~getValue, ()) 103 | |> Internal.sort(_, ~compare) 104 | |> getTop(_, ~maxi=top, ~acc=[], ~i=0) 105 | |> List.rev(_); 106 | }; 107 | 108 | let forArgValues = 109 | ( 110 | arg: Args.t, 111 | ~argsMap, 112 | ~currentArgKey: string, 113 | ~currentArgValues: array(string), 114 | ) 115 | : list((string, string)) => { 116 | switch ( 117 | Suggestions.getChoicesForSuggestions( 118 | arg.choices, 119 | ~argsMap, 120 | ~currentArg=(arg.name, currentArgValues), 121 | ) 122 | ) { 123 | | None => [] 124 | | Some(suggestions) => 125 | get( 126 | currentArgKey, 127 | ~threshold=4, 128 | ~candidates=List.map(c => (c, ""), suggestions), 129 | ~getValue=Suggestions.getValue, 130 | ~compare=((name1, _), (name2, _)) => String.compare(name1, name2), 131 | (), 132 | ) 133 | }; 134 | }; 135 | 136 | let forArgName = 137 | ( 138 | ~definedArgs: list((Args.t, 'a)), 139 | ~argsMap, 140 | ~currentArg as (current, values): (string, array(string)), 141 | ) => { 142 | let suggestions = 143 | Suggestions.getValuesSuggestions( 144 | ~definedArgs, 145 | ~argsMap, 146 | ~currentArg=(current, values), 147 | ); 148 | get( 149 | current, 150 | ~threshold=3, 151 | ~candidates=suggestions, 152 | ~getValue=Suggestions.getValue, 153 | ~compare=((name1, _), (name2, _)) => String.compare(name1, name2), 154 | (), 155 | ); 156 | }; 157 | -------------------------------------------------------------------------------- /src/rarg/Run.re: -------------------------------------------------------------------------------- 1 | open ComponentsErrors; 2 | 3 | type runAction('a) = ( 4 | result(Cmd.Action.t, Cmd.Err.t), 5 | Cmd.t('a), 6 | Cmd.argsMap, 7 | ); 8 | 9 | /** follows the sub commands tree and returns the action that should be taken */ 10 | let getRunAction = (~cmd: Cmd.t('a), ~argsMap: Cmd.argsMap): runAction('a) => { 11 | let (cmd, positionals) = 12 | CmdInternal.findCommandAndPositionals(cmd, ~argsMap); 13 | let argsMap = 14 | switch (positionals) { 15 | | [] => ArgsMap.deletePositionals(argsMap) 16 | | _ => ArgsMap.setPositionals(argsMap, ~value=Array.of_list(positionals)) 17 | }; 18 | (CmdInternal.getCmdAction(cmd, ~argsMap), cmd, argsMap); 19 | }; 20 | 21 | module RunResult = { 22 | type ok('a) = 23 | | Run('a) 24 | | Help(string) 25 | | Version(string) 26 | | Suggest(string) 27 | | AutoCompleteScript(string, string) 28 | | AddPath(string, string) 29 | | RemovePath; 30 | type err = 31 | | ConfigError(string) 32 | | UserError(string) 33 | | UnknownError(string); 34 | type t('a) = result(ok('a), err); 35 | let configError = err => Error(ConfigError(err)); 36 | let userError = err => Error(UserError(err)); 37 | }; 38 | 39 | let simplify = 40 | ( 41 | ~runAction: runAction('a), 42 | ~shell: option(Seed.Process.Shell.t)=?, 43 | ~platform: option(Seed.Os.Platform.t)=?, 44 | ~args: array(string), 45 | ~appName: string, 46 | ~appPath: string, 47 | (), 48 | ) 49 | : RunResult.t('a) => { 50 | switch (runAction) { 51 | | (Error(err), cmd, argsMap) => 52 | switch (err) { 53 | | ConfigError(err) => 54 | ( 55 | switch (err) { 56 | | DuplicateArgs(duplicates) => 57 | | InvalidArgNames(invalidArgs) => 58 | } 59 | ) 60 | |> RunResult.configError(_) 61 | | UserError(err) => 62 | ( 63 | switch (err) { 64 | | InvalidArgValues(validationErrors) => 65 | 66 | | UnknownArgs(unknownArgs) => 67 | 75 | } 76 | ) 77 | |> RunResult.userError(_) 78 | } 79 | | (Ok(action), cmd, argsMap) => 80 | switch (action) { 81 | | Run => Ok(RunResult.Run(cmd.run(argsMap))) 82 | | Help => Ok(RunResult.Help()) 83 | | Version => Ok(Version(cmd.version)) 84 | | Suggest(shell) => 85 | let suggestions = Suggestions.getSuggestions(~args, ~cmd, ~argsMap); 86 | let lines = Suggestions.suggestionsForShell(shell, suggestions); 87 | Ok(RunResult.Suggest(String.concat("\n", lines))); 88 | | AddPath => 89 | Ok( 90 | RunResult.AddPath( 91 | , 92 | , 93 | ), 94 | ) 95 | | RemovePath => 96 | Error( 97 | UnknownError( 98 | "Currently it's not possible to automatically remove path", 99 | ), 100 | ) 101 | | AutoCompleteScript => 102 | Ok( 103 | RunResult.AutoCompleteScript( 104 | , 105 | , 106 | ), 107 | ) 108 | } 109 | }; 110 | }; 111 | 112 | module AutorunResult = { 113 | type ok('a) = 114 | | Run('a) 115 | | Handled; 116 | type err = 117 | | ConfigError 118 | | UserError 119 | | UnknownError; 120 | type t('a) = result(ok('a), err); 121 | }; 122 | 123 | let autorun = 124 | (~logo: option(string)=?, cmd: Cmd.t('a)): AutorunResult.t('a) => { 125 | let {args, appName, appPath, argsMap}: Argv.t = Argv.make(Sys.argv); 126 | switch ( 127 | simplify( 128 | ~runAction=getRunAction(~cmd, ~argsMap), 129 | ~appName, 130 | ~appPath, 131 | ~args, 132 | (), 133 | ) 134 | ) { 135 | | Error(e) => 136 | switch (e) { 137 | | ConfigError(e) => 138 | prerr_endline(e); 139 | Error(ConfigError); 140 | | UserError(e) => 141 | prerr_endline(e); 142 | Error(UserError); 143 | | UnknownError(e) => 144 | prerr_endline(e); 145 | Error(UnknownError); 146 | } 147 | | Ok(ok) => 148 | switch (ok) { 149 | | Run(a) => Ok(Run(a)) 150 | | Help(str) => 151 | switch (logo) { 152 | | None => () 153 | | Some(logo) => print_endline(logo) 154 | }; 155 | print_endline(str); 156 | Ok(Handled); 157 | | Version(version) => 158 | print_endline(version); 159 | Ok(Handled); 160 | | Suggest(str) => 161 | print_endline(str); 162 | Ok(Handled); 163 | | AutoCompleteScript(instruction, script) 164 | | AddPath(instruction, script) => 165 | // use stdout to more easily support >> ~/.zshrc 166 | prerr_endline(instruction); 167 | print_endline(script); 168 | Ok(Handled); 169 | | RemovePath => 170 | prerr_endline( 171 | "Currently it's not possible to automatically remove path", 172 | ); 173 | Ok(Handled); 174 | } 175 | }; 176 | }; 177 | -------------------------------------------------------------------------------- /src/rarg/Run.rei: -------------------------------------------------------------------------------- 1 | type runAction('a) = ( 2 | result(Cmd.Action.t, Cmd.Err.t), 3 | Cmd.t('a), 4 | Cmd.argsMap, 5 | ); 6 | 7 | /** 8 | Follow the sub commands tree and return the action that should be taken 9 | */ 10 | let getRunAction: (~cmd: Cmd.t('a), ~argsMap: Cmd.argsMap) => runAction('a); 11 | 12 | /** 13 | A simplified run result. It contains the ready to print strings for help, suggest and etc. 14 | */ 15 | module RunResult: { 16 | type ok('a) = 17 | | /** The arguments are valid and the command can be executed */ 18 | Run('a) 19 | | /** Help was requested */ 20 | Help(string) 21 | | /** Version was requested */ 22 | Version(string) 23 | | /** Suggestions for autocompletion were requested */ 24 | Suggest(string) 25 | | /** A script for autocompletion to be added to the bash config was requested */ 26 | AutoCompleteScript( 27 | string, 28 | string, 29 | ) 30 | | /** A script for installation to be added to the bash config was requested */ 31 | AddPath( 32 | string, 33 | string, 34 | ) 35 | | RemovePath; 36 | type err = 37 | | /** There was a configuration error, this can be validated before distribution of the application */ 38 | ConfigError( 39 | string, 40 | ) 41 | | /** The user has provided some invalid input */ 42 | UserError(string) 43 | | /** It's unclear if the error is a developer or user mistake */ 44 | UnknownError( 45 | string, 46 | ); 47 | type t('a) = result(ok('a), err); 48 | }; 49 | 50 | /** 51 | Simplifies a run action by returning the concrete strings to be printed for help, suggestions and etc. 52 | */ 53 | let simplify: 54 | ( 55 | ~runAction: runAction('a), 56 | ~shell: Seed.Process.Shell.t=?, 57 | ~platform: Seed.Os.Platform.t=?, 58 | ~args: array(string), 59 | ~appName: string, 60 | ~appPath: string, 61 | unit 62 | ) => 63 | RunResult.t('a); 64 | 65 | /** 66 | The result of an [autorun] 67 | */ 68 | module AutorunResult: { 69 | type ok('a) = 70 | | /** The arguments are valid and the command can be executed */ 71 | Run('a) 72 | | /** The arguments are valid and the action has already been handled */ 73 | Handled; 74 | type err = 75 | | /** There was a configuration error, this can be validated before distribution of the application */ 76 | ConfigError 77 | | /** The user has provided some invalid input */ 78 | UserError 79 | | /** It's unclear if the error is a developer or user mistake */ 80 | UnknownError; 81 | type t('a) = result(ok('a), err); 82 | }; 83 | 84 | /** 85 | Autorun checks the requested action and handles all actions (for example prints help) except [run], 86 | which needs to be handled by the user code. 87 | This makes it trivial to use custom return types, use your own exit codes, or use an async library like [lwt]. 88 | */ 89 | let autorun: (~logo: string=?, Cmd.t('a)) => AutorunResult.t('a); 90 | -------------------------------------------------------------------------------- /src/rarg/Suggestions.re: -------------------------------------------------------------------------------- 1 | module LastArg = { 2 | type lastArg = (string, array(string)); 3 | type t = 4 | | Positionals(lastArg) 5 | | Short(lastArg) 6 | | Long(lastArg) 7 | | Dash(lastArg); 8 | 9 | let ofArray = (args: array(string)): t => { 10 | let endi = Array.length(args) - 1; 11 | switch (ArgsMap.revTakeUntilOption(args, ~n=endi)) { 12 | | (_, None) => 13 | Positionals(( 14 | ArgsMap.positionalsKey, 15 | Seed.Arr.slice(args, ~starti=0, ~endi=endi + 1, ()), 16 | )) 17 | | (optionIndex, Some(argType)) => 18 | switch (argType) { 19 | | Short(opt) => 20 | Short(( 21 | opt, 22 | Seed.Arr.slice(args, ~starti=optionIndex + 1, ~endi=endi + 1, ()), 23 | )) 24 | | Long(opt) => 25 | Long(( 26 | opt, 27 | Seed.Arr.slice(args, ~starti=optionIndex + 1, ~endi=endi + 1, ()), 28 | )) 29 | | Dash => 30 | Dash(( 31 | ArgsMap.dashKey, 32 | Seed.Arr.slice(args, ~starti=optionIndex + 1, ()), 33 | )) 34 | } 35 | }; 36 | }; 37 | }; 38 | 39 | let getChoicesForSuggestions = 40 | ( 41 | choices: option(Type.Choices.t('a)), 42 | ~argsMap: ArgsMap.t, 43 | ~currentArg: (string, array(string)), 44 | ) 45 | : option(list('a)) => 46 | Seed.Option.flatMap(choices, ~fn=choices => 47 | switch (choices) { 48 | | HelpAndSuggestions(xs) 49 | | Suggestions(xs) => Some(xs) 50 | | Dynamic(predicate) => Some(predicate(argsMap, currentArg)) 51 | } 52 | ); 53 | 54 | // only show arg name suggestions if arg not already provided 55 | // or if many can be provided 56 | let showArgNameSuggestions = (arg: Args.t, ~argsMap) => { 57 | switch (arg.possibleValues) { 58 | | ZeroOrOne 59 | | One => 60 | switch (ArgsMap.getEither(argsMap, ~name=arg.name, ~alias=arg.alias)) { 61 | | None => true 62 | | Some(_) => false 63 | } 64 | | Many => true 65 | }; 66 | }; 67 | 68 | let getValuesSuggestions = 69 | ( 70 | ~definedArgs: list((Args.t, 'a)), 71 | ~argsMap, 72 | ~currentArg: (string, array(string)), 73 | ) 74 | : list((string, string)) => { 75 | let suggestions = ref([]); 76 | let (currentArgName, _currentArgValues) = currentArg; 77 | List.iter( 78 | ((arg, _): (Args.t, 'a)) => 79 | switch (arg.name) { 80 | | argName when argName == currentArgName => 81 | if (showArgNameSuggestions(arg, ~argsMap)) { 82 | suggestions := [(arg.name, arg.doc), ...suggestions^]; 83 | }; 84 | switch (getChoicesForSuggestions(arg.choices, ~argsMap, ~currentArg)) { 85 | | None => () 86 | | Some(choices) => 87 | suggestions := List.map(c => (c, arg.doc), choices) @ suggestions^ 88 | }; 89 | | _ => 90 | arg.name == ArgsMap.positionalsKey 91 | ? () : suggestions := [(arg.name, arg.doc), ...suggestions^] 92 | }, 93 | definedArgs, 94 | ); 95 | 96 | suggestions^; 97 | }; 98 | 99 | let getPositionalSuggestions = 100 | ( 101 | ~choices, 102 | ~children: option(list((string, string))), 103 | ~argsMap: ArgsMap.t, 104 | ~currentValues, 105 | ) 106 | : list((string, string)) => { 107 | let suggestions = 108 | switch ( 109 | getChoicesForSuggestions( 110 | choices, 111 | ~argsMap, 112 | ~currentArg=(ArgsMap.positionalsKey, currentValues), 113 | ) 114 | ) { 115 | | None => [] 116 | | Some(valueSuggestions) => 117 | module S = Seed.DataStructures.StringSet; 118 | let currentValues = S.fromArray(currentValues); 119 | List.filter(v => !S.has(v, currentValues), valueSuggestions) 120 | |> List.map(c => (c, "")); 121 | }; 122 | switch (children) { 123 | | None => suggestions 124 | // sub commands are already relevant to whatever command is active 125 | // -> should not be excluded from result even if they are duplicated 126 | | Some(children) => children @ suggestions 127 | }; 128 | }; 129 | 130 | let getArgChoices = 131 | (args: list((Args.t, 'a)), ~key): option(Type.Choices.t(string)) => 132 | switch (key) { 133 | | "" => None 134 | | _ => 135 | switch ( 136 | List.find_opt( 137 | ((arg, _): (Args.t, 'a)) => 138 | arg.name == key || Seed.Option.eq(arg.alias, ~v=key), 139 | args, 140 | ) 141 | ) { 142 | | None => None 143 | | Some((arg, _)) => arg.choices 144 | } 145 | }; 146 | 147 | let getSuggestions = 148 | (~args: array(string), ~cmd: Cmd.t('a), ~argsMap: ArgsMap.t) 149 | : list((string, string)) => { 150 | // -2 to exclude the --suggestions arg and shell 151 | let last = 152 | Seed.Arr.slice(args, ~starti=0, ~endi=-2, ()) |> LastArg.ofArray(_); 153 | switch (last) { 154 | | Short(currentArg) 155 | | Long(currentArg) 156 | | Dash(currentArg) => 157 | getValuesSuggestions(~definedArgs=cmd.args, ~argsMap, ~currentArg) 158 | | Positionals((key, values)) => 159 | getPositionalSuggestions( 160 | ~choices=getArgChoices(cmd.args, ~key), 161 | ~argsMap, 162 | ~currentValues=values, 163 | ~children= 164 | Seed.Option.map( 165 | cmd.children, 166 | ~fn=children => { 167 | let kvs = Seed.DataStructures.StringMap.entries(children); 168 | List.map( 169 | ((k, v: CmdInternal.t('a))) => 170 | (k, Seed.Option.getDefault(v.doc, ~default="")), 171 | kvs, 172 | ); 173 | }, 174 | ), 175 | ) 176 | }; 177 | }; 178 | 179 | let getValue = ((value, _)) => value; 180 | let values = vs => List.map(getValue, vs); 181 | 182 | let suggestionsForShell = (shell: Seed.Process.Shell.t, suggestions) => { 183 | switch (shell) { 184 | | Bash => List.map(((s, _)) => s, suggestions) 185 | | Zsh => 186 | List.map( 187 | ((s, d)) => { 188 | let fl = switch(Seed.Strings.splitAtChar(d, ~char='\n')) { 189 | | None => d 190 | | Some((firstLine, _)) => firstLine 191 | }; 192 | s ++ ":" ++ fl; 193 | }, 194 | suggestions, 195 | ) 196 | }; 197 | }; 198 | -------------------------------------------------------------------------------- /src/rarg/TerminalTemplates.re: -------------------------------------------------------------------------------- 1 | module type TipTemplateConfig = { 2 | let templateName: string; 3 | let bashTemplate: string; 4 | let zshTemplate: string; 5 | }; 6 | 7 | let installTip = 8 | ( 9 | ~appPath, 10 | ~shell: option(Seed.Process.Shell.t), 11 | ~platform: option(Seed.Os.Platform.t)=?, 12 | (), 13 | ) => { 14 | let shellLocation = 15 | Seed.Process.Shell.getConfigLocation(~shell?, ~platform?, ()); 16 | // {{app_path}} --rarg-add-path >> {{bashrc_location}} 17 | String.concat(" ", [appPath, ArgsMap.addPathKey, ">>", shellLocation]); 18 | }; 19 | 20 | module type PathTemplateConfig = { 21 | let templateName: string; 22 | let bashTemplate: string; 23 | let zshTemplate: string; 24 | }; 25 | module PathTemplate = (Templates: PathTemplateConfig) => { 26 | let replaceAppName = (template, ~appName) => 27 | Str.global_replace(Str.regexp("{{app_name}}"), appName, template); 28 | let replaceAppPath = (template, ~appPath) => 29 | Str.global_replace(Str.regexp("{{app_path}}"), appPath, template); 30 | 31 | let beginTemplate = {|###-begin-{{app_name}}-completions-###|}; 32 | let doNotEdit = {|# Autogenerated, do not edit manually 33 | ########################################|}; 34 | let endTemplate = {|###-end-{{app_name}}-completions-###|}; 35 | 36 | let wrap = template => 37 | String.concat("\n", [beginTemplate, doNotEdit, template, endTemplate]); 38 | 39 | let replace = 40 | (~shell: option(Seed.Process.Shell.t)=?, ~appName, ~appPath, ()) => { 41 | let template = 42 | switch ( 43 | Seed.Option.getDefaultLazy(shell, ~default=() => 44 | Seed.Process.Shell.readFromEnv() 45 | ) 46 | ) { 47 | | Bash => Templates.bashTemplate 48 | | Zsh => Templates.zshTemplate 49 | }; 50 | wrap(template) 51 | |> replaceAppName(_, ~appName) 52 | |> replaceAppPath(_, ~appPath); 53 | }; 54 | }; 55 | 56 | module Autocomplete = 57 | PathTemplate({ 58 | let templateName = "completions"; 59 | /** 60 | Autocompletion templates based on yargs: 61 | https://github.com/yargs/yargs/blob/master/lib/completion-templates.js 62 | */ 63 | // "${args[@]:1}" is used to exclude the command name from the list 64 | let bashTemplate = {|_rarg_{{app_name}}_completions() 65 | { 66 | local cur_word args type_list 67 | 68 | cur_word="${COMP_WORDS[COMP_CWORD]}" 69 | args=("${COMP_WORDS[@]}") 70 | 71 | # ask rarg to generate completions. 72 | type_list=$({{app_name}} "${args[@]:1}" --rarg-suggestions-request bash) 73 | 74 | COMPREPLY=( $(compgen -W "${type_list}" -- ${cur_word}) ) 75 | 76 | # if no match was found, fall back to filename completion 77 | if [ ${#COMPREPLY[@]} -eq 0 ]; then 78 | COMPREPLY=() 79 | fi 80 | 81 | return 0 82 | } 83 | complete -o default -F _rarg_{{app_name}}_completions {{app_name}}|}; 84 | 85 | let zshTemplate = {|_rarg_{{app_name}}_completions() 86 | { 87 | local reply 88 | local si=$IFS 89 | IFS=$'\n' reply=($(COMP_CWORD="$((CURRENT-1))" COMP_LINE="$BUFFER" COMP_POINT="$CURSOR" {{app_name}} "${words[@]:1}" --rarg-suggestions-request zsh)) 90 | IFS=$si 91 | _describe 'values' reply 92 | } 93 | compdef _rarg_{{app_name}}_completions {{app_name}}|}; 94 | }); 95 | -------------------------------------------------------------------------------- /src/rarg/Type.re: -------------------------------------------------------------------------------- 1 | module Choices = { 2 | type argsMap = ArgsMap.t; 3 | type t('a) = 4 | | HelpAndSuggestions(list('a)) 5 | | Suggestions(list('a)) 6 | | Dynamic((argsMap, (string, array(string))) => list('a)); 7 | 8 | let map = (choices: t('a), toB: 'a => 'b): t('b) => { 9 | switch (choices) { 10 | | HelpAndSuggestions(xs) => HelpAndSuggestions(List.map(toB, xs)) 11 | | Suggestions(xs) => Suggestions(List.map(toB, xs)) 12 | | Dynamic(predicate) => 13 | Dynamic((map, current) => List.map(toB, predicate(map, current))) 14 | }; 15 | }; 16 | }; 17 | 18 | /** result contains the parsed value or an optional actionable user text */ 19 | type parse('a) = string => result('a, option(string)); 20 | type t('a) = { 21 | name: string, 22 | parse: parse('a), 23 | stringify: 'a => string, 24 | choices: option(Choices.t('a)), 25 | }; 26 | /** 27 | * Create a parser 28 | */ 29 | let make = (~name, ~parse, ~stringify, ~choices=?, ()): t('a) => { 30 | name, 31 | parse, 32 | stringify, 33 | choices, 34 | }; 35 | 36 | let char: t(char) = { 37 | name: "char", 38 | parse: x => 39 | switch (String.length(x)) { 40 | | 1 => Ok(x.[0]) 41 | | _ => Error(None) 42 | }, 43 | stringify: x => String.make(1, x), 44 | choices: None, 45 | }; 46 | 47 | let string: t(string) = { 48 | name: "string", 49 | parse: x => Ok(x), 50 | stringify: x => x, 51 | choices: None, 52 | }; 53 | 54 | let int: t(int) = { 55 | name: "int", 56 | parse: x => { 57 | switch (int_of_string(x)) { 58 | | num => Ok(num) 59 | | exception _e => Error(None) 60 | }; 61 | }, 62 | stringify: x => string_of_int(x), 63 | choices: None, 64 | }; 65 | 66 | let float: t(float) = { 67 | name: "float", 68 | parse: x => { 69 | switch (float_of_string(x)) { 70 | | num => Ok(num) 71 | | exception _e => Error(None) 72 | }; 73 | }, 74 | stringify: x => string_of_float(x), 75 | choices: None, 76 | }; 77 | 78 | let bool: t(bool) = { 79 | name: "bool", 80 | parse: 81 | fun 82 | | "true" => Ok(true) 83 | | "false" => Ok(false) 84 | | _ => Error(None), 85 | stringify: x => x ? "true" : "false", 86 | choices: Some(Suggestions([true, false])), 87 | }; 88 | 89 | let flag: t(bool) = { 90 | name: "flag", 91 | parse: 92 | fun 93 | | "1" 94 | | "true" 95 | | "on" 96 | | "y" 97 | | "yes" => Ok(true) 98 | | "0" 99 | | "false" 100 | | "off" 101 | | "n" 102 | | "no" => Ok(false) 103 | | _ => Error(None), 104 | stringify: x => x ? "true" : "false", 105 | choices: Some(Suggestions([true, false])), 106 | }; 107 | 108 | let shell: t(Seed.Process.Shell.t) = { 109 | name: "shell", 110 | parse: 111 | fun 112 | | "bash" => Ok(Bash) 113 | | "zsh" => Ok(Zsh) 114 | | s => Error(Some("Unknown shell: " ++ s)), 115 | stringify: 116 | fun 117 | | Bash => "bash" 118 | | Zsh => "zsh", 119 | choices: Some(HelpAndSuggestions([Bash, Zsh])), 120 | }; 121 | 122 | let withChoices = (t: t('a), choices: Choices.t('a)): t('a) => { 123 | ...t, 124 | choices: Some(choices), 125 | }; 126 | let with_choices = withChoices; 127 | 128 | let branchFilterPattern = branch => 129 | String.concat( 130 | " ", 131 | [ 132 | "refs/remotes/" ++ branch ++ "*", 133 | "refs/remotes/" ++ branch ++ "*/**", 134 | "refs/heads/" ++ branch ++ "*", 135 | "refs/heads/" ++ branch ++ "*/**", 136 | "refs/tags/" ++ branch ++ "*", 137 | "refs/tags/" ++ branch ++ "*/**", 138 | ], 139 | ); 140 | let branch = 141 | make( 142 | ~name="branch", 143 | ~parse=x => Ok(x), 144 | ~stringify=x => x, 145 | ~choices= 146 | Dynamic( 147 | (_argsMap, (_key, values)) => { 148 | let pattern = 149 | switch (values) { 150 | | [||] => "" 151 | | _ => branchFilterPattern(values[Array.length(values) - 1]) 152 | }; 153 | Seed.Chan.readMany( 154 | {|git for-each-ref --count 30 --format="%(refname:strip=2)" |} 155 | ++ pattern, 156 | ); 157 | }, 158 | ), 159 | (), 160 | ); 161 | -------------------------------------------------------------------------------- /src/rarg/Type.rei: -------------------------------------------------------------------------------- 1 | /** 2 | For defining suggested values in a parser. 3 | These values appear in autocompletions and the command help. 4 | They are not used for validation which should be part of the parsing. 5 | For example you can define static suggestions: [ Choices.HelpAndSuggestions([Apple, Banana]) ] 6 | */ 7 | /** 8 | Or you can define dynamic autocomplete suggestions that depend on any of the provided arguments so far: 9 | {[ 10 | let carModel = 11 | withChoices( 12 | string, 13 | Dynamic( 14 | (argsMap, _) => 15 | switch (getCar(argsMap)) { 16 | | car => 17 | switch (car) { 18 | | "alfa" => ["4C"] 19 | | "bmw" => ["3-series", "5-series"] 20 | | _ => [] 21 | } 22 | // suggestions are called before input validation 23 | // -> getCar is not safe to call, so we return an empty list 24 | // when suggestions are requested, but we don't have a valid car yet 25 | | exception _e => [] 26 | }, 27 | ), 28 | ); 29 | ]} 30 | */ 31 | module Choices: { 32 | type argsMap = ArgsMap.t; 33 | type t('a) = 34 | | /** The values will appear in both help text and suggestions */ 35 | HelpAndSuggestions( 36 | list('a), 37 | ) 38 | | /** The values will appear only in suggestions */ 39 | Suggestions(list('a)) 40 | | /** The values will appear only in suggestions */ 41 | Dynamic( 42 | (argsMap, (string, array(string))) => list('a), 43 | ); 44 | 45 | let map: (t('a), 'a => 'b) => t('b); 46 | }; 47 | 48 | /** 49 | The parse result contains (the parsed value, and an optional actionable user text) 50 | */; 51 | type parse('a) = string => result('a, option(string)); 52 | 53 | /** 54 | Type record used for parsing types from and to [string] 55 | */; 56 | type t('a) = { 57 | /** name of the type that will appear in help */ 58 | name: string, 59 | /** string to type */ 60 | parse: parse('a), 61 | /** type to string */ 62 | stringify: 'a => string, 63 | /** values to be used in autocompletion and help */ 64 | choices: option(Choices.t('a)), 65 | }; 66 | 67 | /** 68 | Creates a reusable [Type] parser with optional autocomplete suggestions, 69 | that you can pass when adding new {!module:Args}. 70 | For example to create a type-safe fruits variant type: 71 | */ 72 | /** 73 | {[ 74 | type fruit = | Apple | Banana; 75 | let fruit: Type.t(fruit) = { 76 | name: "fruit", 77 | parse: 78 | fun 79 | | "apple" => Ok(Apple) 80 | | "banana" => Ok(Banana) 81 | | x => Error(Some(x ++ " is not a fruit.")), 82 | stringify: 83 | fun 84 | | Apple => "apple" 85 | | Banana => "banana", 86 | choices: Some(HelpAndSuggestions([Apple, Banana])), 87 | }; 88 | ]} 89 | */ 90 | let make: 91 | ( 92 | ~name: string, 93 | ~parse: parse('a), 94 | ~stringify: 'a => string, 95 | ~choices: Choices.t('a)=?, 96 | unit 97 | ) => 98 | t('a); 99 | 100 | /** 101 | Extends an existing type parser with a list of possible choices. 102 | For example you can extend the [string] type to autocomplete the value [origin/master] with: 103 | {[ 104 | withChoices(string, Suggestions(["origin/master"])) 105 | ]} 106 | */ 107 | let withChoices: (t('a), Choices.t('a)) => t('a); 108 | 109 | /** 110 | (alias of {!val:withChoices}) Extends an existing type parser with a list of possible choices 111 | For example a string parser can be extended to suggest the values "apple" and "banana" 112 | */ 113 | let with_choices: (t('a), Choices.t('a)) => t('a); 114 | 115 | /** 116 | {2:type_predefined Predefined basic type parsers} 117 | */ 118 | let char: t(char); 119 | 120 | let string: t(string); 121 | 122 | let int: t(int); 123 | 124 | let float: t(float); 125 | 126 | /** 127 | [bool] type (parses only [true]/[false], check {!val:flag} for parsing [truthy]/[falsey] values) 128 | */ 129 | let bool: t(bool); 130 | 131 | /** 132 | Parses [shell] values like [bash] or [zsh] 133 | */ 134 | let shell: t(Seed.Process.Shell.t); 135 | 136 | /** 137 | Parses [truthy]/[falsey] values like [true]/[false] [on]/[off] [yes]/[no] [y]/[n] and [1]/[0] 138 | */ 139 | let flag: t(bool); 140 | 141 | /** 142 | Autosuggests git branches (without validating them) 143 | */ 144 | let branch: t(string); 145 | -------------------------------------------------------------------------------- /src/rarg/ValidateArgs.re: -------------------------------------------------------------------------------- 1 | module Err = { 2 | type expected = 3 | | Eq(int) 4 | | Gt(int); 5 | let stringOfExpected = 6 | fun 7 | | Eq(expected) => string_of_int(expected) 8 | | Gt(expected) => string_of_int(expected) ++ " or more"; 9 | let intOfExpected = 10 | fun 11 | | Eq(expected) => expected 12 | | Gt(expected) => expected; 13 | 14 | type argsCount = { 15 | expected, 16 | actual: int, 17 | }; 18 | type transform = { 19 | actual: string, 20 | info: option(string), 21 | }; 22 | 23 | type t = 24 | | ArgsCount(argsCount) 25 | | Transform(transform); 26 | 27 | let argsCount = (~expected: expected, ~actual: int) => 28 | ArgsCount({expected, actual}); 29 | 30 | let transform = (~actual: string, ~info: option(string)) => 31 | Transform({actual, info}); 32 | }; 33 | 34 | type value = option(array(string)); 35 | 36 | /** 37 | * One argument, if the input array contains more than 1 argument - return an error 38 | */ 39 | module One = { 40 | module Internal = { 41 | let oneValue = (input: array(string)): option(string) => 42 | switch (Array.length(input)) { 43 | | 1 => Some(input[0]) 44 | | _len => None 45 | }; 46 | 47 | let handleOneArg = 48 | (parse: Type.parse('a), xs: array(string)): result('a, Err.t) => { 49 | switch (oneValue(xs)) { 50 | | None => 51 | Error(Err.argsCount(~expected=Eq(1), ~actual=Array.length(xs))) 52 | | Some(x) => 53 | switch (parse(x)) { 54 | | Error(info) => Error(Err.transform(~actual=x, ~info)) 55 | | Ok(v) => Ok(v) 56 | } 57 | }; 58 | }; 59 | }; 60 | let flag = 61 | (~default: 'a, ~empty: 'a, ~parse: Type.parse('a), value: value) 62 | : result('a, Err.t) => { 63 | switch (value) { 64 | | None => Ok(default) 65 | | Some(xs) => 66 | switch (Array.length(xs)) { 67 | | 0 => Ok(empty) 68 | | _ => Internal.handleOneArg(parse, xs) 69 | } 70 | }; 71 | }; 72 | let req = (~parse: Type.parse('a), value: value): result('a, Err.t) => { 73 | switch (value) { 74 | | None => Error(Err.argsCount(~expected=Eq(1), ~actual=0)) 75 | | Some(xs) => Internal.handleOneArg(parse, xs) 76 | }; 77 | }; 78 | let default = 79 | (~parse: Type.parse('a), ~default: 'a, value: value) 80 | : result('a, Err.t) => { 81 | switch (value) { 82 | | None => Ok(default) 83 | | Some(xs) => Internal.handleOneArg(parse, xs) 84 | }; 85 | }; 86 | let opt = 87 | (~parse: Type.parse('a), value: value): result(option('a), Err.t) => { 88 | switch (value) { 89 | | None => Ok(None) 90 | | Some(xs) => 91 | switch (Internal.handleOneArg(parse, xs)) { 92 | | Error(err) => Error(err) 93 | | Ok(v) => Ok(Some(v)) 94 | } 95 | }; 96 | }; 97 | }; 98 | 99 | /** 100 | * Many arguments, the input array can contain 0 or more items 101 | */ 102 | module Many = { 103 | module Internal = { 104 | let handleTransformMany = 105 | (~parse: Type.parse('a), arr: array(string)) 106 | : result(list('a), Err.t) => { 107 | let rec aux = (acc, ~n) => { 108 | n < 0 109 | ? Ok(acc) 110 | : ( 111 | switch (parse(arr[n])) { 112 | | Error(maybeErr) => Error((arr[n], maybeErr)) 113 | | Ok(v) => aux([v, ...acc], ~n=n - 1) 114 | } 115 | ); 116 | }; 117 | 118 | switch (aux([], ~n=Array.length(arr) - 1)) { 119 | | Error((actual, info)) => Error(Err.transform(~actual, ~info)) 120 | | Ok(v) => Ok(v) 121 | }; 122 | }; 123 | }; 124 | let req = (~parse: Type.parse('a), value: value): result(list('a), Err.t) => { 125 | switch (value) { 126 | | None => Error(Err.argsCount(~expected=Gt(1), ~actual=0)) 127 | | Some(xs) => Internal.handleTransformMany(~parse, xs) 128 | }; 129 | }; 130 | let default = 131 | (~parse: Type.parse('a), ~default: list('a), value: value) 132 | : result(list('a), Err.t) => { 133 | switch (value) { 134 | | None => Ok(default) 135 | | Some(xs) => Internal.handleTransformMany(~parse, xs) 136 | }; 137 | }; 138 | let opt = 139 | (~parse: Type.parse('a), value: value) 140 | : result(option(list('a)), Err.t) => { 141 | switch (value) { 142 | | None => Ok(None) 143 | | Some(xs) => 144 | switch (Internal.handleTransformMany(~parse, xs)) { 145 | | Error(err) => Error(err) 146 | | Ok(v) => Ok(Some(v)) 147 | } 148 | }; 149 | }; 150 | }; 151 | -------------------------------------------------------------------------------- /src/rarg/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name Rarg) 3 | (public_name rarg) 4 | (libraries rarg.internal) 5 | (modules Rarg) 6 | ) 7 | ( 8 | library 9 | (name RargInternal) 10 | (public_name rarg.internal) 11 | (libraries str rarg.seed pastel.lib) 12 | (modules (:standard \ Rarg)) 13 | ) 14 | -------------------------------------------------------------------------------- /src/seed/Arr.re: -------------------------------------------------------------------------------- 1 | let slice = 2 | (arr: array('a), ~starti: int, ~endi: option(int)=?, ()): array('a) => { 3 | let endIndex = 4 | switch (endi) { 5 | | Some(i) when i >= 0 => i 6 | | Some(i) => Array.length(arr) + i 7 | | None => Array.length(arr) 8 | }; 9 | Array.sub(arr, starti, endIndex - starti); 10 | }; 11 | -------------------------------------------------------------------------------- /src/seed/Chan.re: -------------------------------------------------------------------------------- 1 | let readChannelOnce = (ic): option(string) => 2 | try(Some(input_line(ic))) { 3 | | End_of_file => None 4 | }; 5 | 6 | let readChannel = (ic): list(string) => { 7 | let lines = ref([]); 8 | try( 9 | while (true) { 10 | lines := [input_line(ic), ...lines^]; 11 | } 12 | ) { 13 | | End_of_file => () 14 | }; 15 | List.rev(lines^); 16 | }; 17 | 18 | let readOne = (cmd): option(string) => { 19 | let ic = Unix.open_process_in(cmd); 20 | let res = readChannelOnce(ic); 21 | close_in_noerr(ic); 22 | res; 23 | }; 24 | 25 | let readMany = (cmd): list(string) => { 26 | let ic = Unix.open_process_in(cmd); 27 | let res = readChannel(ic); 28 | close_in_noerr(ic); 29 | res; 30 | }; 31 | 32 | let readPipedInput = () => { 33 | switch (in_channel_length(stdin)) { 34 | | 0 => [] 35 | | _ => readChannel(stdin) 36 | | exception _ => readChannel(stdin) 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /src/seed/Env.re: -------------------------------------------------------------------------------- 1 | /** 2 | * Get an environment variable value or None if not found 3 | */ 4 | let getOpt = s => 5 | try (Some(Sys.getenv(s))) { 6 | | Not_found => None 7 | }; 8 | -------------------------------------------------------------------------------- /src/seed/Fs.re: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the closest dir that contains a filename 3 | */ 4 | let rec closestDir = (~dir, ~filename): option(string) => 5 | if (Sys.file_exists(Filename.concat(dir, filename))) { 6 | Some(dir); 7 | } else { 8 | switch (Filename.dirname(dir)) { 9 | // root reached 10 | | parentDir when parentDir == dir => None 11 | | parentDir => closestDir(~dir=parentDir, ~filename) 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /src/seed/Lst.re: -------------------------------------------------------------------------------- 1 | /** 2 | * Take the first n elements from a list 3 | */ 4 | let takeTop = (list: list('a), ~top: int): list('a) => { 5 | let rec aux = (items, acc, i) => 6 | i >= top 7 | ? acc 8 | : ( 9 | switch (items) { 10 | | [] => acc 11 | | [x, ...xs] => aux(xs, [x, ...acc], i + 1) 12 | } 13 | ); 14 | aux(list, [], 0) |> List.rev(_); 15 | }; 16 | 17 | let hasElements = (list: list('a)): bool => 18 | switch (list) { 19 | | [] => false 20 | | _ => true 21 | }; 22 | 23 | let map = (list: list('a), predicate: 'a => 'b): list('b) => 24 | List.map(predicate, list); 25 | -------------------------------------------------------------------------------- /src/seed/Option.re: -------------------------------------------------------------------------------- 1 | let isSome = (maybe: option('a)): bool => 2 | switch (maybe) { 3 | | None => false 4 | | Some(_) => true 5 | }; 6 | let map = (maybe: option('a), ~fn: 'a => 'b): option('b) => 7 | switch (maybe) { 8 | | None => None 9 | | Some(x) => Some(fn(x)) 10 | }; 11 | 12 | let bi = (maybe: option('a), ~fn: 'a => 'b, ~default: 'b): 'b => 13 | switch (maybe) { 14 | | None => default 15 | | Some(x) => fn(x) 16 | }; 17 | 18 | let flatMap = (maybe: option('a), ~fn: 'a => 'b): 'b => 19 | switch (maybe) { 20 | | None => None 21 | | Some(x) => fn(x) 22 | }; 23 | 24 | let getExn = (maybe: option('a)): 'a => 25 | switch (maybe) { 26 | | None => failwith("argument is null") 27 | | Some(x) => x 28 | }; 29 | 30 | let getDefault = (maybe: option('a), ~default: 'a): 'a => 31 | switch (maybe) { 32 | | None => default 33 | | Some(x) => x 34 | }; 35 | 36 | let getDefaultLazy = (maybe: option('a), ~default: unit => 'a): 'a => 37 | switch (maybe) { 38 | | None => default() 39 | | Some(x) => x 40 | }; 41 | 42 | let eq = (maybe: option('a), ~v: 'a): bool => 43 | switch (maybe) { 44 | | None => false 45 | | Some(x) => x == v 46 | }; 47 | 48 | let either = ((maybeA: option('a), maybeB: option('a))): option('a) => 49 | switch (maybeA, maybeB) { 50 | | (None, None) => None 51 | | (Some(x), _) 52 | | (_, Some(x)) => Some(x) 53 | }; 54 | -------------------------------------------------------------------------------- /src/seed/Os.re: -------------------------------------------------------------------------------- 1 | module Platform = { 2 | /* 3 | From esy: https://github.com/esy/esy/blob/master/esy-lib/System.re 4 | License: https://github.com/esy/esy/blob/master/LICENSE 5 | */ 6 | type t = 7 | | Darwin 8 | | Linux 9 | | Cygwin 10 | | Windows /* mingw msvc */ 11 | | Unix /* all other unix-y systems */ 12 | | Unknown; 13 | 14 | let uname = () => { 15 | let ic = Unix.open_process_in("uname"); 16 | let uname = input_line(ic); 17 | let () = close_in(ic); 18 | switch (String.lowercase_ascii(uname)) { 19 | | "linux" => Linux 20 | | "darwin" => Darwin 21 | | _ => Unix 22 | }; 23 | }; 24 | let current = () => { 25 | switch (Sys.os_type) { 26 | | "Unix" => uname() 27 | | "Win32" => Windows 28 | | "Cygwin" => Cygwin 29 | | _ => Unknown 30 | }; 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /src/seed/Print.re: -------------------------------------------------------------------------------- 1 | /** print an arrow below a string at a certain index */ 2 | let arrow = (str: string, ~index: int): unit => { 3 | let (left, right) = Strings.splitAtIndex(str, ~index); 4 | let c = String.make(1, str.[index]); 5 | print_endline( 6 | 7 | left c right 8 | , 9 | ); 10 | print_endline( 11 | 12 | {Strings.repeat(" ", ~times=index)} 13 | "⤒" 14 | , 15 | ); 16 | }; 17 | 18 | /** print 2 arrows above and below a string at left and right indexes */ 19 | let arrows = (str: string, ~l: int, ~r: int): unit => { 20 | print_endline( 21 | 22 | {Strings.repeat(" ", ~times=l)} 23 | "⥙" 24 | , 25 | ); 26 | 27 | let (left, rest) = Strings.splitAtIndex(str, ~index=l); 28 | let lchar = String.make(1, str.[l]); 29 | let content = 30 | switch (l, r) { 31 | | (l, r) when l == r => 32 | Pastel.( 33 | 34 | left lchar rest 35 | 36 | ) 37 | | (l, r) => 38 | let rchar = String.make(1, str.[r]); 39 | let (mid, right) = Strings.splitAtIndex(rest, ~index=r - l - 1); 40 | Pastel.( 41 | 42 | left 43 | lchar mid rchar 44 | right 45 | 46 | ); 47 | }; 48 | print_endline(content); 49 | print_endline( 50 | 51 | {Strings.repeat(" ", ~times=r)} 52 | "⥔" 53 | , 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /src/seed/Process.re: -------------------------------------------------------------------------------- 1 | module Shell = { 2 | type t = 3 | | Bash 4 | | Zsh; 5 | 6 | let isZsh = v => Strings.contains(v, "zsh") ? Some(Zsh) : None; 7 | let readFromEnv = () => { 8 | let pid = string_of_int(Unix.getppid()); 9 | let proc = Chan.readOne("ps -p " ++ pid ++ " -ocomm="); 10 | // currently esy overrides the shell environment with 11 | // env -i /bin/bash --norc --noprofile 12 | // and the process env is different in dev and release 13 | // hence the many checks: 14 | Option.flatMap(proc, ~fn=isZsh) 15 | |> Option.getDefaultLazy(_, ~default=() => { 16 | Option.flatMap(Sys.getenv_opt("SHELL"), ~fn=isZsh) 17 | |> Option.getDefaultLazy(_, ~default=() => { 18 | Option.flatMap(Sys.getenv_opt("ZSH_NAME"), ~fn=isZsh) 19 | |> Option.getDefault(_, ~default=Bash) 20 | }) 21 | }); 22 | }; 23 | 24 | module Bashrc = { 25 | let location = 26 | Os.Platform.( 27 | fun 28 | | Darwin => "~/.bash_profile" 29 | | Linux 30 | | Unix 31 | | Unknown 32 | | Cygwin => "~/.bashrc" 33 | | Windows => failwith("Windows is not supported") 34 | ); 35 | }; 36 | 37 | module Zshrc = { 38 | let location = 39 | Os.Platform.( 40 | fun 41 | | Darwin => "~/.zshrc" 42 | | Linux 43 | | Unix 44 | | Unknown 45 | | Cygwin => "~/.zshrc" 46 | | Windows => failwith("Windows is not supported") 47 | ); 48 | }; 49 | 50 | let getConfigLocation = (~shell=?, ~platform=?, ()) => { 51 | let p = Option.getDefault(platform, ~default=Os.Platform.current()); 52 | switch (Option.getDefaultLazy(shell, ~default=() => readFromEnv())) { 53 | | Bash => Bashrc.location(p) 54 | | Zsh => Zshrc.location(p) 55 | }; 56 | }; 57 | }; 58 | 59 | let isZshShell = () => 60 | switch (Sys.getenv_opt("SHELL")) { 61 | | Some(v) => Strings.contains(v, "zsh") 62 | | None => false 63 | }; 64 | -------------------------------------------------------------------------------- /src/seed/ProjectRoot.re: -------------------------------------------------------------------------------- 1 | let get = () => { 2 | switch (Env.getOpt("RARG_ROOT")) { 3 | | Some(dir) => dir 4 | | None => 5 | switch (Fs.closestDir(~dir=Sys.getcwd(), ~filename=".rarg-root")) { 6 | | Some(dir) => dir 7 | | None => 8 | failwith( 9 | "Expected `REASON_NATIVE_ROOT` environment variable to be set " 10 | ++ "before running tests.", 11 | ) 12 | } 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /src/seed/Result.re: -------------------------------------------------------------------------------- 1 | type t('v, 'e) = result('v, 'e); 2 | 3 | exception ExpectedAnOk; 4 | exception ExpectedAnError; 5 | 6 | let getOkExn = (result: t('v, 'e)): 'v => 7 | switch (result) { 8 | | Ok(v) => v 9 | | Error(_) => raise(ExpectedAnOk) 10 | }; 11 | 12 | let getErrExn = (result: t('v, 'e)): 'e => 13 | switch (result) { 14 | | Ok(_) => raise(ExpectedAnError) 15 | | Error(e) => e 16 | }; 17 | -------------------------------------------------------------------------------- /src/seed/Strings.re: -------------------------------------------------------------------------------- 1 | let slice = (str: string, ~starti: int, ~endi: option(int)=?, ()): string => { 2 | let endIndex = 3 | switch (endi) { 4 | | Some(i) => i 5 | | None => String.length(str) 6 | }; 7 | String.sub(str, starti, endIndex - starti); 8 | }; 9 | 10 | let repeat = (str: string, ~times: int): string => { 11 | let buff = Buffer.create(String.length(str) * times); 12 | for (_x in 1 to times) { 13 | Buffer.add_string(buff, str); 14 | }; 15 | Buffer.contents(buff); 16 | }; 17 | 18 | let splitAtIndex = (str: string, ~index: int): (string, string) => { 19 | let left = slice(str, ~starti=0, ~endi=index, ()); 20 | let right = slice(str, ~starti=index + 1, ()); 21 | (left, right); 22 | }; 23 | let splitAtChar = (str: string, ~char: char): option((string, string)) => { 24 | switch (String.index_opt(str, char)) { 25 | | None => None 26 | | Some(charIndex) => Some(splitAtIndex(str, ~index=charIndex)) 27 | }; 28 | }; 29 | 30 | let split = (str: string): list(char) => { 31 | let rec aux = (list, n) => n >= 0 ? aux([str.[n], ...list], n - 1) : list; 32 | aux([], String.length(str) - 1); 33 | }; 34 | 35 | let contains = (s1: string, s2: string): bool => { 36 | let re = Str.regexp_string(s2); 37 | try ( 38 | { 39 | ignore(Str.search_forward(re, s1, 0)); 40 | true; 41 | } 42 | ) { 43 | | Not_found => false 44 | }; 45 | }; 46 | 47 | let startsWith = (str, ~start): bool => { 48 | switch (String.length(str), String.length(start)) { 49 | | (fullLen, len) when len > fullLen => false 50 | | (_, len) => 51 | let rec aux = ( 52 | fun 53 | | i when i == len => true 54 | | i when str.[i] != start.[i] => false 55 | | i => aux(i + 1) 56 | ); 57 | aux(0); 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /src/seed/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name Seed) 3 | (public_name rarg.seed) 4 | (libraries str pastel.lib) 5 | ) 6 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonstefanov/rarg/0deeeb986f9b3a1b0e1513f98969197ca21ebab6/test -------------------------------------------------------------------------------- /tests.opam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonstefanov/rarg/0deeeb986f9b3a1b0e1513f98969197ca21ebab6/tests.opam -------------------------------------------------------------------------------- /tests/TestCi.re: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */; 7 | 8 | RargTests.TestFramework.run( 9 | Rely.RunConfig.( 10 | initialize() 11 | |> withReporters([Default, JUnit("./junit.xml")]) 12 | |> ciMode(true) 13 | ), 14 | ); 15 | -------------------------------------------------------------------------------- /tests/TestDev.re: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */; 7 | 8 | RargTests.TestFramework.cli(); 9 | -------------------------------------------------------------------------------- /tests/TestFramework.re: -------------------------------------------------------------------------------- 1 | let projectDir = Seed.ProjectRoot.get(); 2 | 3 | RargInternal.Components.Span.setMode(HumanReadable); 4 | 5 | include Rely.Make({ 6 | let config = 7 | Rely.TestFrameworkConfig.initialize({ 8 | snapshotDir: 9 | projectDir 10 | |> (dir => Filename.concat(dir, "tests")) 11 | |> (dir => Filename.concat(dir, "__snapshots__")), 12 | projectDir, 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargCmd_validate.d5f8c28a.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Cmd › validate › prints validation errors 2 | cmd-duplicate 3 | You must define unique argument names. Duplicates: 4 | 5 | --v1 6 | --v1 v1 duplicate doc 7 | --v1 v1 doc 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_DuplicateArgs.0b441d9a.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › DuplicateArgs › renders duplicates when 2 2 | You must define unique argument names. Duplicates: 3 | 4 | --key1 5 | --bake Whether to bake the fruit [bool]=false 6 | --bake Whether to bake the fruit [bool]=false 7 | 8 | --key2 9 | --bake Whether to bake the fruit [bool]=false 10 | --bake Whether to bake the fruit [bool]=false 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_DuplicateArgs.add36454.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › DuplicateArgs › renders duplicates when 1 2 | You must define unique argument names. Duplicates: 3 | 4 | --key1 5 | --bake Whether to bake the fruit [bool]=false 6 | --bake Whether to bake the fruit [bool]=false 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_InvalidArgNames.17d3d187.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › InvalidArgNames › renders no-dash arg names 2 | Argument names must start with a - 3 | 4 | invalid-arg-name Whether to bake the fruit [bool]=false 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_InvalidArgNames.b4b00a55.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › InvalidArgNames › renders empty arg names 2 | Argument names must start with a - 3 | 4 | Whether to bake the fruit [bool]=false 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_UnknownArgs_no_positionals.46a940a4.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › UnknownArgs › no positionals › renders arg key suggestions when subcommands, positionals 2 | 3 | Unknown arguments provided: 4 | --bak uv1 uv2 5 | Did you mean: 6 | --bake, --cake 7 | 8 | cak unknown-value1 unknown-value2 9 | Did you mean: 10 | --cake 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_UnknownArgs_no_positionals.ada734dc.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › UnknownArgs › no positionals › renders arg key suggestions when no subcommands, no positionals 2 | 3 | Unknown arguments provided: 4 | --bak uv1 uv2 5 | Did you mean: 6 | --bake, --cake 7 | 8 | cak unknown-value1 unknown-value2 9 | Did you mean: 10 | --cake 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_UnknownArgs_with_positionals.1bfe4fbf.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › UnknownArgs › with positionals › prioritizes suggestions based on the first positional - tomat 2 | Unknown sub command: 3 | tomat potat 4 | Did you mean to call: 5 | tom, potato 6 | 7 | Unknown arguments provided: 8 | --bak uv1 uv2 9 | Did you mean: 10 | --bake, --cake 11 | 12 | cak unknown-value1 unknown-value2 13 | Did you mean: 14 | --cake 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_UnknownArgs_with_positionals.2a7d66ba.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › UnknownArgs › with positionals › renders suggestions when no subcommands, no positionals 2 | Did not expect positional arguments, but received: 3 | tomat potat 4 | 5 | Unknown arguments provided: 6 | --bak uv1 uv2 7 | Did you mean: 8 | --bake, --cake 9 | 10 | cak unknown-value1 unknown-value2 11 | Did you mean: 12 | --cake 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_UnknownArgs_with_positionals.6103095a.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › UnknownArgs › with positionals › renders suggestions when there's no good subcommands match 2 | Unknown sub command: 3 | tomat potat 4 | Did you mean to call: 5 | cake 6 | 7 | Unknown arguments provided: 8 | --bak uv1 uv2 9 | Did you mean: 10 | --bake, --cake 11 | 12 | cak unknown-value1 unknown-value2 13 | Did you mean: 14 | --cake 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_ValidationError_ArgsCount.54516e19.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › ValidationError › ArgsCount › shows expected=3 received=5 2 | --bake expected: 3 received: 5 values 3 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_ValidationError_ArgsCount.713f94b1.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › ValidationError › ArgsCount › shows true recommendation when info 2 | Some info 3 | Did you mean: 4 | true 5 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_ValidationError_ArgsCount.7825cfbd.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › ValidationError › ArgsCount › shows true recommendation when no info 2 | --bake expected type bool, but receivedtr 3 | Did you mean: 4 | true 5 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsErrors_ValidationError_ArgsCount.f1a29b69.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsErrors › ValidationError › ArgsCount › shows expected>=3 received=5 2 | --bake expected: 3 or more received: 5 values 3 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsHelp_Help.68d44ef8.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsHelp › Help › CmdFind 2 | Find 3 | 4 | Command signature: 5 | my-app-name <..patterns> [options] 6 | 7 | 8 | Positionals: 9 | patterns Patterns to find <..string> 10 | 11 | Options: 12 | --copy Whether to copy the results [bool]=false 13 | 14 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsHelp_Help.6ded9ba6.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsHelp › Help › CmdFruits 2 | Fruits 3 | 4 | Command signature: 5 | my-app-name <..options> 6 | 7 | 8 | 9 | Options: 10 | --fruits Fruits to cook <..fruit> apple, banana 11 | --bake Whether to bake the fruit [bool]=false 12 | --cut Whether to cut the fruit [bool]=false 13 | 14 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsHelp_Help.c75256d5.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsHelp › Help › CmdStart 2 | Start 3 | 4 | Command signature: 5 | my-app-name 6 | 7 | Sub-Commands: 8 | car Build a car 9 | find Find 10 | fruits Fruits 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsHelp_Help.e83467b9.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsHelp › Help › CmdCar 2 | Build a car 3 | Allows you to build amazing cars and more 4 | 5 | Command signature: 6 | my-app-name [sub-command] <..options> 7 | 8 | Sub-Commands: 9 | fruit Fruits 10 | 11 | Options: 12 | --car -c Car to build 13 | --cat Cat 14 | --color Paint color [string]=green 15 | --model The car model [string] 16 | 17 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsTips_AddPathScript.3666b3eb.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsTips › AddPathScript › renders install script for bash 2 | # my-app 3 | alias my-app='/a/b/c' 4 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsTips_AddPathScript.e18b822c.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsTips › AddPathScript › renders install script for zsh 2 | # my-app 3 | alias my-app='/a/b/c' 4 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsTips_AddPathTip.05dc030a.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsTips › AddPathTip › shows a tip for mac bash 2 | To append to your shell config run: 3 | /a/b/c --rarg-add-path >> ~/.bash_profile 4 | Afterwards restart your terminal 5 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsTips_AddPathTip.2a353412.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsTips › AddPathTip › shows a tip for mac zsh 2 | To append to your shell config run: 3 | /a/b/c --rarg-add-path >> ~/.zshrc 4 | Afterwards restart your terminal 5 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsTips_AddPathTip.37293e9c.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsTips › AddPathTip › shows a tip for nix bash 2 | To append to your shell config run: 3 | /a/b/c --rarg-add-path >> ~/.bashrc 4 | Afterwards restart your terminal 5 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargComponentsTips_AddPathTip.5ecbee85.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_ComponentsTips › AddPathTip › shows a tip for nix zsh 2 | To append to your shell config run: 3 | /a/b/c --rarg-add-path >> ~/.zshrc 4 | Afterwards restart your terminal 5 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Error.12ef018d.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Error › UserError required args not provided 2 | Error 3 | UserError 4 | --fruits expected: 1 or more received: 0 values 5 | 6 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Error.8d25981c.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Error › UserError provided args invalid 2 | Error 3 | UserError 4 | pineapple is not a fruit. 5 | Did you mean: 6 | apple 7 | 8 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Error.9aabc109.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Error › RemovePath 2 | Error 3 | UnknownError 4 | Currently it's not possible to automatically remove path 5 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Error.c49fb6ae.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Error › ConfigError duplicate args 2 | Error 3 | ConfigError 4 | You must define unique argument names. Duplicates: 5 | 6 | __positionals__ 7 | patterns2 Patterns to find <..string> 8 | patterns Patterns to find <..string> 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Error.cd367d91.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Error › UserError provided args don't exist 2 | Error 3 | UserError 4 | 5 | Unknown arguments provided: 6 | --fruit 7 | Did you mean: 8 | --fruits, --cut 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Error.f3640ab1.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Error › ConfigError invalid arg name 2 | Error 3 | ConfigError 4 | Argument names must start with a - 5 | 6 | patterns Patterns to find <..string> 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Ok.13df237a.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Ok › Help 2 | Ok 3 | Help 4 | Fruits 5 | 6 | Command signature: 7 | fruits <..options> 8 | 9 | 10 | 11 | Options: 12 | --fruits Fruits to cook <..fruit> apple, banana 13 | --bake Whether to bake the fruit [bool]=false 14 | --cut Whether to cut the fruit [bool]=false 15 | 16 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Ok.161880a3.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Ok › Run 2 | Ok 3 | Run 4 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Ok.237800af.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Ok › AddPath 2 | Ok 3 | AddPath 4 | To append to your shell config run: 5 | Fruits --rarg-add-path >> ~/.bash_profile 6 | Afterwards restart your terminal 7 | # fruits 8 | alias fruits='Fruits' 9 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Ok.4d153a45.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Ok › Suggest - bash 2 | Ok 3 | Suggest 4 | --fruits 5 | --bake 6 | --cut 7 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Ok.7ddf4c4a.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Ok › AutoCompleteScript - bash 2 | Ok 3 | AutoCompleteScript 4 | To append to your shell config run: 5 | fruits --rarg-suggestions-script >> ~/.bash_profile 6 | Afterwards restart your terminal 7 | ###-begin-fruits-completions-### 8 | # Autogenerated, do not edit manually 9 | ######################################## 10 | _rarg_fruits_completions() 11 | { 12 | local cur_word args type_list 13 | 14 | cur_word=\"${COMP_WORDS[COMP_CWORD]}\" 15 | args=(\"${COMP_WORDS[@]}\") 16 | 17 | # ask rarg to generate completions. 18 | type_list=$(fruits \"${args[@]:1}\" --rarg-suggestions-request bash) 19 | 20 | COMPREPLY=( $(compgen -W \"${type_list}\" -- ${cur_word}) ) 21 | 22 | # if no match was found, fall back to filename completion 23 | if [ ${#COMPREPLY[@]} -eq 0 ]; then 24 | COMPREPLY=() 25 | fi 26 | 27 | return 0 28 | } 29 | complete -o default -F _rarg_fruits_completions fruits 30 | ###-end-fruits-completions-### 31 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Ok.a14be28c.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Ok › Version 2 | Ok 3 | Version 4 | 1.0 5 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Ok.b07dfa75.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Ok › AutoCompleteScript - zsh 2 | Ok 3 | AutoCompleteScript 4 | To append to your shell config run: 5 | fruits --rarg-suggestions-script >> ~/.zshrc 6 | Afterwards restart your terminal 7 | ###-begin-fruits-completions-### 8 | # Autogenerated, do not edit manually 9 | ######################################## 10 | _rarg_fruits_completions() 11 | { 12 | local reply 13 | local si=$IFS 14 | IFS=$'\\n' reply=($(COMP_CWORD=\"$((CURRENT-1))\" COMP_LINE=\"$BUFFER\" COMP_POINT=\"$CURSOR\" fruits \"${words[@]:1}\" --rarg-suggestions-request zsh)) 15 | IFS=$si 16 | _describe 'values' reply 17 | } 18 | compdef _rarg_fruits_completions fruits 19 | ###-end-fruits-completions-### 20 | -------------------------------------------------------------------------------- /tests/__snapshots__/RargRun_Ok.c505ca7a.0.snapshot: -------------------------------------------------------------------------------- 1 | Rarg_Run › Ok › Suggest - zsh 2 | Ok 3 | Suggest 4 | --fruits:Fruits to cook 5 | --bake:Whether to bake the fruit 6 | --cut:Whether to cut the fruit 7 | -------------------------------------------------------------------------------- /tests/__tests__/rarg/Rarg_CmdInternal_Validate_test.re: -------------------------------------------------------------------------------- 1 | open TestFramework; 2 | 3 | module T = Rarg.Type; 4 | module V = RargInternal.CmdInternal.Validate; 5 | module Args = RargInternal.Args; 6 | module StringMap = Seed.DataStructures.StringMap; 7 | 8 | describe("Rarg_CmdInternal_Validate", t => { 9 | t.describe("checkUnknownArgs", t => { 10 | t.test("is ok when there are no unknown arguments", t => 11 | t.expect.result( 12 | V.checkUnknownArgs( 13 | ~isKnown=_ => true, 14 | ~providedArgs=[("-a", [||])], 15 | ), 16 | ). 17 | toBeOk() 18 | ); 19 | t.test("is error when there are unknown arguments", t => 20 | t.expect.result( 21 | V.checkUnknownArgs( 22 | ~isKnown=_ => false, 23 | ~providedArgs=[("-a", [||])], 24 | ), 25 | ). 26 | toBeError() 27 | ); 28 | t.test("returns all unknown arguments", t => { 29 | let result = 30 | V.checkUnknownArgs( 31 | ~isKnown=arg => arg == "-b", 32 | ~providedArgs=[("-a", [||]), ("-b", [||])], 33 | ); 34 | let err = Seed.Result.getErrExn(result); 35 | t.expect.list(err).toEqual([("-a", [||])]); 36 | }); 37 | }); 38 | t.describe("getDuplicatesAndKnownArgs", t => { 39 | let (args, _) = 40 | Args.One.req(~args=[], ~name="--v1", ~alias="-v", ~doc="v1", T.string); 41 | let (duplicatedArgs, _) = 42 | Args.One.req(~args, ~name="--v1", ~alias="-z", ~doc="v1", T.string); 43 | t.test("returns a map with both names and aliases", t => { 44 | let (duplicates, knownArgs) = V.getDuplicatesAndKnownArgs(args); 45 | t.expect.list(duplicates).toEqual([]); 46 | t.expect.list(StringMap.keys(knownArgs)).toEqual(["--v1", "-v"]); 47 | }); 48 | t.test("returns a map with names and aliases when there are duplicates", t => { 49 | let (duplicates, knownArgs) = 50 | V.getDuplicatesAndKnownArgs(duplicatedArgs); 51 | t.expect.list(duplicates).not.toEqual([]); 52 | t.expect.list(StringMap.keys(knownArgs)).toEqual([ 53 | "--v1", 54 | "-v", 55 | "-z", 56 | ]); 57 | let (key, _, _) = List.hd(duplicates); 58 | t.expect.string(key).toEqual("--v1"); 59 | }); 60 | }); 61 | t.describe("isValidArgName", t => { 62 | t.test("is valid arg name when starting with a dash", t => { 63 | t.expect.bool(V.isValidArgName("-a")).toBe(true); 64 | t.expect.bool(V.isValidArgName("--a")).toBe(true); 65 | }); 66 | t.test("is not a valid arg name when empty", t => 67 | t.expect.bool(V.isValidArgName("")).toBe(false) 68 | ); 69 | t.test("is not a valid arg name when not starting with a dash", t => 70 | t.expect.bool(V.isValidArgName("a")).toBe(false) 71 | ); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /tests/__tests__/rarg/Rarg_Cmd_test.re: -------------------------------------------------------------------------------- 1 | open TestFramework; 2 | open Rarg; 3 | 4 | module A = Rarg.Type; 5 | module Commands = Rarg_FakeCommands; 6 | 7 | describe("Rarg_Cmd", t => { 8 | module C = Rarg.Cmd; 9 | t.describe("validate", t => { 10 | let run = _ => (); 11 | let (args, _) = 12 | Args.One.req(~args=[], ~name="--v1", ~doc="v1 doc", A.bool); 13 | let (argsDuplicate, _) = 14 | Args.One.req(~args, ~name="--v1", ~doc="v1 duplicate doc", A.string); 15 | let cmd = Cmd.make(~name="cmd", ~version="1.0", ~args, ~run, ()); 16 | let cmdDuplicate = 17 | Cmd.make( 18 | ~name="cmd-duplicate", 19 | ~version="1.0", 20 | ~args=argsDuplicate, 21 | ~run, 22 | (), 23 | ); 24 | 25 | t.test("is ok when cmd is valid", t => 26 | t.expect.result(C.validate(cmd)).toBeOk() 27 | ); 28 | t.test("is error when cmd is invalid", t => 29 | t.expect.result(C.validate(cmdDuplicate)).toBeError() 30 | ); 31 | t.test("is ok when sub cmd is valid", t => { 32 | let cmdTree = 33 | Cmd.make( 34 | ~name="tree", 35 | ~version="1.0", 36 | ~args, 37 | ~run, 38 | ~children=[("valid", cmd)], 39 | (), 40 | ); 41 | t.expect.result(C.validate(cmdTree)).toBeOk(); 42 | }); 43 | t.test("is error when sub cmd is invalid", t => { 44 | let cmdTree = 45 | Cmd.make( 46 | ~name="tree", 47 | ~version="1.0", 48 | ~args, 49 | ~run, 50 | ~children=[("duplicates", cmdDuplicate)], 51 | (), 52 | ); 53 | t.expect.result(C.validate(cmdTree)).toBeError(); 54 | }); 55 | t.test("prints validation errors", t => { 56 | let validateLines = 57 | Cmd.make( 58 | ~name="tree", 59 | ~version="1.0", 60 | ~args, 61 | ~run, 62 | ~children=[("duplicates", cmdDuplicate)], 63 | (), 64 | ) 65 | |> C.validate(_) 66 | |> Seed.Result.getErrExn(_) 67 | |> C.validateErrToString(_); 68 | t.expect.lines(validateLines).toMatchSnapshot(); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /tests/__tests__/rarg/Rarg_ComponentsHelp_test.re: -------------------------------------------------------------------------------- 1 | open TestFramework; 2 | open RargInternal.ComponentsHelp; 3 | 4 | describe("Rarg_ComponentsHelp", t => 5 | t.describe("Help", t => { 6 | let appName = "my-app-name"; 7 | t.test("CmdStart", t => 8 | t.expect.string(). 9 | toMatchSnapshot() 10 | ); 11 | t.test("CmdFruits", t => 12 | t.expect.string(). 13 | toMatchSnapshot() 14 | ); 15 | t.test("CmdCar", t => 16 | t.expect.string(). 17 | toMatchSnapshot() 18 | ); 19 | t.test("CmdFind", t => 20 | t.expect.string(). 21 | toMatchSnapshot() 22 | ); 23 | }) 24 | ); 25 | -------------------------------------------------------------------------------- /tests/__tests__/rarg/Rarg_ComponentsTips_test.re: -------------------------------------------------------------------------------- 1 | open TestFramework; 2 | open RargInternal.ComponentsTips; 3 | 4 | describe("Rarg_ComponentsTips", t => { 5 | t.describe("AddPathTip", t => { 6 | t.test("shows a tip for nix bash", t => 7 | t.expect.string( 8 | , 9 | ). 10 | toMatchSnapshot() 11 | ); 12 | t.test("shows a tip for nix zsh", t => 13 | t.expect.string(). 14 | toMatchSnapshot() 15 | ); 16 | t.test("shows a tip for mac bash", t => 17 | t.expect.string( 18 | , 19 | ). 20 | toMatchSnapshot() 21 | ); 22 | t.test("shows a tip for mac zsh", t => 23 | t.expect.string( 24 | , 25 | ). 26 | toMatchSnapshot() 27 | ); 28 | }); 29 | t.describe("AddPathScript", t => { 30 | t.test("renders install script for bash", t => 31 | t.expect.string(). 32 | toMatchSnapshot() 33 | ); 34 | t.test("renders install script for zsh", t => 35 | t.expect.string(). 36 | toMatchSnapshot() 37 | ); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/__tests__/rarg/Rarg_FakeCommands.re: -------------------------------------------------------------------------------- 1 | module T = Rarg.Type; 2 | module Cmd = Rarg.Cmd; 3 | module Args = Rarg.Args; 4 | 5 | module CmdFruits = { 6 | type t = 7 | | Apple 8 | | Banana; 9 | let fruit: Rarg.Type.t(t) = { 10 | name: "fruit", 11 | parse: 12 | fun 13 | | "apple" => Ok(Apple) 14 | | "banana" => Ok(Banana) 15 | | x => Error(Some(x ++ " is not a fruit.")), 16 | stringify: 17 | fun 18 | | Apple => "apple" 19 | | Banana => "banana", 20 | choices: Some(HelpAndSuggestions([Apple, Banana])), 21 | }; 22 | let args = []; 23 | let (args, getFruits) = 24 | Args.Many.req(~args, ~name="--fruits", ~doc="Fruits to cook", fruit); 25 | let (args, getBake) = 26 | Args.One.boolFlag( 27 | ~args, 28 | ~name="--bake", 29 | ~doc="Whether to bake the fruit", 30 | T.bool, 31 | ); 32 | let (args, getCut) = 33 | Args.One.boolFlag( 34 | ~args, 35 | ~name="--cut", 36 | ~doc="Whether to cut the fruit", 37 | T.bool, 38 | ); 39 | let handle = (~fruits as _: list(t), ~bake as _: bool, ~cut as _: bool) => 40 | (); 41 | let run = m => 42 | handle(~fruits=getFruits(m), ~bake=getBake(m), ~cut=getCut(m)); 43 | let cmd: Cmd.t(unit) = 44 | Cmd.make(~name="Fruits", ~version="1.0", ~args, ~run, ()); 45 | }; 46 | module CmdCar = { 47 | let car = T.withChoices(T.string, T.Choices.Suggestions(["alfa", "bmw"])); 48 | let args = []; 49 | let (args, getCar) = 50 | Args.One.req(~args, ~name="--car", ~alias="-c", ~doc="Car to build", car); 51 | let (args, getCat) = Args.One.req(~args, ~name="--cat", ~doc="Cat", car); 52 | let (args, getColor) = 53 | Args.One.default( 54 | ~args, 55 | ~name="--color", 56 | ~doc="Paint color", 57 | ~default="green", 58 | T.string, 59 | ); 60 | let model = 61 | T.withChoices( 62 | T.string, 63 | T.Choices.Dynamic( 64 | (argsMap, _) => 65 | switch (getCar(argsMap)) { 66 | | car => 67 | switch (car) { 68 | | "alfa" => ["4C"] 69 | | "bmw" => ["3-series", "5-series"] 70 | | _ => [] 71 | } 72 | // suggestions are called before input validation 73 | // -> getCar is not safe to call, so we return an empty list 74 | // when suggestions are requested, but we don't have a valid car yet 75 | | exception _e => [] 76 | }, 77 | ), 78 | ); 79 | let (args, getModel) = 80 | Args.One.opt(~args, ~name="--model", ~doc="The car model", model); 81 | let handler = 82 | ( 83 | ~car as _: string, 84 | ~cat as _: string, 85 | ~color as _: string, 86 | ~model as _: option(string), 87 | ) => 88 | (); 89 | let run = map => 90 | handler( 91 | ~car=getCar(map), 92 | ~cat=getCat(map), 93 | ~color=getColor(map), 94 | ~model=getModel(map), 95 | ); 96 | let cmd: Cmd.t(unit) = 97 | Cmd.make( 98 | ~name="Build a car", 99 | ~version="1.0", 100 | ~doc= 101 | "Allows you to build amazing " 102 | ++ 103 | "cars" 104 | 105 | ++ " and more", 106 | ~args, 107 | ~run, 108 | ~children=[("fruit", CmdFruits.cmd)], 109 | (), 110 | ); 111 | }; 112 | module CmdFind = { 113 | let args = []; 114 | let (args, getPatterns) = 115 | Args.Positional.Many.req( 116 | ~args, 117 | ~name="patterns", 118 | ~doc="Patterns to find", 119 | T.string, 120 | ); 121 | let (args, getCopy) = 122 | Args.One.boolFlag( 123 | ~args, 124 | ~name="--copy", 125 | ~doc="Whether to copy the results", 126 | T.bool, 127 | ); 128 | let handle = (~patterns as _: list(string), ~copy as _: bool) => (); 129 | let run = m => handle(~patterns=getPatterns(m), ~copy=getCopy(m)); 130 | let cmd: Cmd.t(unit) = 131 | Cmd.make(~name="Find", ~version="1.0", ~args, ~run, ()); 132 | }; 133 | module CmdStart = { 134 | let args = []; 135 | let run = _m => 136 | print_endline("Forgot to add a subcommand, enter --help for help."); 137 | let cmd: Cmd.t(unit) = 138 | Cmd.make( 139 | ~name="Start", 140 | ~version="1.0", 141 | ~args, 142 | ~run, 143 | ~children=[ 144 | ("fruits", CmdFruits.cmd), 145 | ("car", CmdCar.cmd), 146 | ("find", CmdFind.cmd), 147 | ], 148 | (), 149 | ); 150 | }; 151 | -------------------------------------------------------------------------------- /tests/__tests__/rarg/Rarg_Recommendations_test.re: -------------------------------------------------------------------------------- 1 | open TestFramework; 2 | 3 | module Recommendations = RargInternal.Recommendations; 4 | 5 | describe("Rarg_Recommendations", t => { 6 | t.describe("levenshteinDistance", t => { 7 | t.test("has 0 distance when same char", t => 8 | t.expect.int(Recommendations.levenshteinDistance("a", "a")).toBe(0) 9 | ); 10 | t.test("has 2 distance when 2 char difference ", t => 11 | t.expect.int(Recommendations.levenshteinDistance("a", "zaz")).toBe(2) 12 | ); 13 | t.test("has 3 distance when 3 char difference ", t => 14 | t.expect.int(Recommendations.levenshteinDistance("a", "zzz")).toBe(3) 15 | ); 16 | }); 17 | t.describe("Internal", t => { 18 | t.test("includes all entities < threshold", t => 19 | t.expect.list( 20 | Recommendations.Internal.get( 21 | "a", 22 | ~candidates=["zxc", "a", "a", "ab", "abc", "abcd", "abcde"], 23 | ~getValue=v => v, 24 | (), 25 | ), 26 | ). 27 | toEqual([ 28 | ("a", 0), 29 | ("ab", 1), 30 | ("abc", 2), 31 | ("abcd", 3), 32 | ("zxc", 3), 33 | ]) 34 | ); 35 | t.test("sorts first by threshold and then alphabetically", t => 36 | t.expect.list( 37 | Recommendations.Internal.sort( 38 | ~compare=String.compare, 39 | [ 40 | ("zyx", 1), 41 | ("zyx", 4), 42 | ("abb", 2), 43 | ("aaa", 2), 44 | ("abc", 5), 45 | ("abb", 5), 46 | ], 47 | ), 48 | ). 49 | toEqual([ 50 | ("zyx", 1), 51 | ("aaa", 2), 52 | ("abb", 2), 53 | ("zyx", 4), 54 | ("abb", 5), 55 | ("abc", 5), 56 | ]) 57 | ); 58 | }); 59 | t.describe("get", t => { 60 | t.test( 61 | "includes all entities < threshold when max returned entities is none", 62 | t => 63 | t.expect.list( 64 | Recommendations.get( 65 | "a", 66 | ~top=99, 67 | ~candidates=["a", "ab", "abc", "abcd", "abcde"], 68 | ~getValue=v => v, 69 | ~compare=String.compare, 70 | (), 71 | ), 72 | ). 73 | toEqual([ 74 | "a", 75 | "ab", 76 | "abc", 77 | "abcd", 78 | ]) 79 | ); 80 | t.test("includes the top 3 recommendations < threshold", t => 81 | t.expect.list( 82 | Recommendations.get( 83 | "a", 84 | ~top=3, 85 | ~candidates=["a", "ab", "abc", "abcd", "abcde"], 86 | ~getValue=v => v, 87 | ~compare=String.compare, 88 | (), 89 | ), 90 | ). 91 | toEqual([ 92 | "a", 93 | "ab", 94 | "abc", 95 | ]) 96 | ); 97 | 98 | t.test("includes both recommendations", t => 99 | t.expect.list( 100 | Recommendations.get( 101 | "potat", 102 | ~candidates=["potato", "potatoes"], 103 | ~getValue=v => v, 104 | ~compare=String.compare, 105 | (), 106 | ), 107 | ). 108 | toEqual([ 109 | "potato", 110 | "potatoes", 111 | ]) 112 | ); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /tests/__tests__/rarg/Rarg_Run_test.re: -------------------------------------------------------------------------------- 1 | open TestFramework; 2 | 3 | module Args = RargInternal.Args; 4 | module ArgsMap = RargInternal.ArgsMap; 5 | module Run = RargInternal.Run; 6 | module Type = RargInternal.Type; 7 | 8 | let run = 9 | ( 10 | ~shell: Seed.Process.Shell.t, 11 | cmd: RargInternal.Cmd.t('a), 12 | args: list(string), 13 | ) => { 14 | let argsArr = Array.of_list([cmd.name, ...args]); 15 | let {args, appName, appPath, argsMap}: RargInternal.Argv.t = 16 | RargInternal.Argv.make(argsArr); 17 | let runResult = 18 | Run.simplify( 19 | ~runAction=Run.getRunAction(~cmd, ~argsMap), 20 | ~args, 21 | ~appName, 22 | ~appPath, 23 | ~shell, 24 | ~platform=Darwin, 25 | (), 26 | ); 27 | 28 | switch (runResult) { 29 | | Ok(ok) => [ 30 | "Ok", 31 | ...switch (ok) { 32 | | Run(_) => ["Run"] 33 | | Help(help) => ["Help", help] 34 | | Version(version) => ["Version", version] 35 | | Suggest(suggestions) => ["Suggest", suggestions] 36 | // autocomplete script depends on environment 37 | | AutoCompleteScript(tip, script) => [ 38 | "AutoCompleteScript", 39 | tip, 40 | script, 41 | ] 42 | | AddPath(tip, script) => ["AddPath", tip, script] 43 | | RemovePath => ["RemovePath"] 44 | }, 45 | ] 46 | | Error(err) => [ 47 | "Error", 48 | ...switch (err) { 49 | | ConfigError(err) => ["ConfigError", err] 50 | | UserError(err) => ["UserError", err] 51 | | UnknownError(err) => ["UnknownError", err] 52 | }, 53 | ] 54 | }; 55 | }; 56 | let runFruits = (args: list(string)) => 57 | run(~shell=Bash, Rarg_FakeCommands.CmdFruits.cmd, args); 58 | let runFruitsZsh = (args: list(string)) => 59 | run(~shell=Zsh, Rarg_FakeCommands.CmdFruits.cmd, args); 60 | let runDuplicateArg = (providedArgs: list(string)) => { 61 | let cmd = Rarg_FakeCommands.CmdFruits.cmd; 62 | let args = cmd.args; 63 | let (args, _) = 64 | Args.Positional.Many.req( 65 | ~args, 66 | ~name="patterns", 67 | ~doc="Patterns to find", 68 | Rarg.Type.string, 69 | ); 70 | let (args, _) = 71 | Args.Positional.Many.req( 72 | ~args, 73 | ~name="patterns2", 74 | ~doc="Patterns to find", 75 | Rarg.Type.string, 76 | ); 77 | let duplicateArgCmd = {...cmd, args}; 78 | run(~shell=Bash, duplicateArgCmd, providedArgs); 79 | }; 80 | let runInvalidArgName = (providedArgs: list(string)) => { 81 | let cmd = Rarg_FakeCommands.CmdFruits.cmd; 82 | let args = cmd.args; 83 | let (args, _) = 84 | Args.Many.req( 85 | ~args, 86 | ~name="patterns", 87 | ~doc="Patterns to find", 88 | Rarg.Type.string, 89 | ); 90 | let invalidArgNameCmd = {...cmd, args}; 91 | run(~shell=Bash, invalidArgNameCmd, providedArgs); 92 | }; 93 | 94 | describe("Rarg_Run", t => { 95 | t.describe("Ok", t => { 96 | t.test("Run", t => { 97 | let result = runFruits(["--fruits", "apple"]); 98 | t.expect.lines(result).toMatchSnapshot(); 99 | }); 100 | t.test("Help", t => { 101 | let result = runFruits(["--help"]); 102 | t.expect.lines(result).toMatchSnapshot(); 103 | }); 104 | t.test("Version", t => { 105 | let result = runFruits(["--version"]); 106 | t.expect.lines(result).toMatchSnapshot(); 107 | }); 108 | t.test("Suggest - bash", t => { 109 | let result = 110 | runFruits([ 111 | "-fru", 112 | ArgsMap.suggestionsRequestKey, 113 | Type.shell.stringify(Bash), 114 | ]); 115 | t.expect.lines(result).toMatchSnapshot(); 116 | }); 117 | t.test("Suggest - zsh", t => { 118 | let result = 119 | runFruits([ 120 | "-fru", 121 | ArgsMap.suggestionsRequestKey, 122 | Type.shell.stringify(Zsh), 123 | ]); 124 | t.expect.lines(result).toMatchSnapshot(); 125 | }); 126 | t.test("AutoCompleteScript - bash", t => { 127 | let result = runFruits([ArgsMap.suggestionsScriptKey]); 128 | t.expect.lines(result).toMatchSnapshot(); 129 | }); 130 | t.test("AutoCompleteScript - zsh", t => { 131 | let result = runFruitsZsh([ArgsMap.suggestionsScriptKey]); 132 | t.expect.lines(result).toMatchSnapshot(); 133 | }); 134 | t.test("AddPath", t => { 135 | let result = runFruits([ArgsMap.addPathKey]); 136 | t.expect.lines(result).toMatchSnapshot(); 137 | }); 138 | }); 139 | t.describe("Error", t => { 140 | t.test("RemovePath", t => { 141 | let result = runFruits([ArgsMap.removePathKey]); 142 | t.expect.lines(result).toMatchSnapshot(); 143 | }); 144 | t.test("UserError required args not provided", t => { 145 | let result = runFruits([]); 146 | t.expect.lines(result).toMatchSnapshot(); 147 | }); 148 | t.test("UserError provided args invalid", t => { 149 | let result = runFruits(["--fruits", "pineapple"]); 150 | t.expect.lines(result).toMatchSnapshot(); 151 | }); 152 | t.test("UserError provided args don't exist", t => { 153 | let result = runFruits(["--fruit"]); 154 | t.expect.lines(result).toMatchSnapshot(); 155 | }); 156 | t.test("ConfigError duplicate args", t => { 157 | let result = runDuplicateArg([]); 158 | t.expect.lines(result).toMatchSnapshot(); 159 | }); 160 | t.test("ConfigError invalid arg name", t => { 161 | let result = runInvalidArgName([]); 162 | t.expect.lines(result).toMatchSnapshot(); 163 | }); 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /tests/__tests__/seed/Seed_Arr_test.re: -------------------------------------------------------------------------------- 1 | open TestFramework; 2 | open Seed; 3 | 4 | describe("Rarg_Seed_Arr", t => 5 | t.describe("slice", t => { 6 | t.test("returns the same array", t => 7 | t.expect.array(Arr.slice([|1, 2, 3, 4|], ~starti=0, ())).toEqual([| 8 | 1, 9 | 2, 10 | 3, 11 | 4, 12 | |]) 13 | ); 14 | t.test("returns 234", t => 15 | t.expect.array(Arr.slice([|1, 2, 3, 4|], ~starti=1, ())).toEqual([| 16 | 2, 17 | 3, 18 | 4, 19 | |]) 20 | ); 21 | t.test("returns 1", t => 22 | t.expect.array(Arr.slice([|1, 2, 3, 4|], ~starti=0, ~endi=1, ())). 23 | toEqual([| 24 | 1, 25 | |]) 26 | ); 27 | t.test("returns 12", t => 28 | t.expect.array(Arr.slice([|1, 2, 3, 4|], ~starti=0, ~endi=2, ())). 29 | toEqual([| 30 | 1, 31 | 2, 32 | |]) 33 | ); 34 | t.test("returns 2", t => 35 | t.expect.array(Arr.slice([|1, 2, 3, 4|], ~starti=1, ~endi=2, ())). 36 | toEqual([| 37 | 2, 38 | |]) 39 | ); 40 | t.test("returns 23", t => 41 | t.expect.array(Arr.slice([|1, 2, 3, 4|], ~starti=1, ~endi=3, ())). 42 | toEqual([| 43 | 2, 44 | 3, 45 | |]) 46 | ); 47 | t.test("works with negative end index", t => 48 | t.expect.array(Arr.slice([|1, 2, 3, 4|], ~starti=0, ~endi=-1, ())). 49 | toEqual([| 50 | 1, 51 | 2, 52 | 3, 53 | |]) 54 | ); 55 | t.test("throws on negative start index", t => 56 | t.expect.fn(() => Arr.slice([||], ~starti=-1, ())).toThrow() 57 | ); 58 | t.test("throws on negative end index when less than start index", t => 59 | t.expect.fn(() => Arr.slice([||], ~starti=0, ~endi=-1, ())).toThrow() 60 | ); 61 | }) 62 | ); 63 | -------------------------------------------------------------------------------- /tests/__tests__/seed/Seed_Lst_test.re: -------------------------------------------------------------------------------- 1 | open TestFramework; 2 | open Seed; 3 | 4 | describe("Rarg_Seed_Lst", t => 5 | t.describe("takeTop", t => { 6 | t.test("returns no elements when negative", t => 7 | t.expect.list(Lst.takeTop([1, 2, 3, 4], ~top=-99)).toEqual([]) 8 | ); 9 | t.test("returns no elements when 0", t => 10 | t.expect.list(Lst.takeTop([1, 2, 3, 4], ~top=0)).toEqual([]) 11 | ); 12 | t.test("returns the top 2 elements", t => 13 | t.expect.list(Lst.takeTop([1, 2, 3, 4], ~top=2)).toEqual([1, 2]) 14 | ); 15 | t.test("returns all elements when top > list len", t => 16 | t.expect.list(Lst.takeTop([1, 2, 3, 4], ~top=99)).toEqual([ 17 | 1, 18 | 2, 19 | 3, 20 | 4, 21 | ]) 22 | ); 23 | }) 24 | ); 25 | -------------------------------------------------------------------------------- /tests/__tests__/seed/Seed_Strings_test.re: -------------------------------------------------------------------------------- 1 | open TestFramework; 2 | open Seed; 3 | 4 | let get = Option.getExn; 5 | 6 | describe("Rarg_Seed_Strings", t => { 7 | t.describe("repeat", t => { 8 | t.test("is empty", t => 9 | t.expect.string(Strings.repeat("abc", ~times=0)).toEqual("") 10 | ); 11 | t.test("repeats abc 3 times", t => 12 | t.expect.string(Strings.repeat("abc", ~times=3)).toEqual("abcabcabc") 13 | ); 14 | }); 15 | t.describe("slice", t => { 16 | t.test("returns the same string", t => 17 | t.expect.string(Strings.slice("abcdef", ~starti=0, ())).toEqual( 18 | "abcdef", 19 | ) 20 | ); 21 | t.test("returns bcdef", t => 22 | t.expect.string(Strings.slice("abcdef", ~starti=1, ())).toEqual( 23 | "bcdef", 24 | ) 25 | ); 26 | t.test("returns a", t => 27 | t.expect.string(Strings.slice("abcdef", ~starti=0, ~endi=1, ())). 28 | toEqual( 29 | "a", 30 | ) 31 | ); 32 | t.test("returns ab", t => 33 | t.expect.string(Strings.slice("abcdef", ~starti=0, ~endi=2, ())). 34 | toEqual( 35 | "ab", 36 | ) 37 | ); 38 | t.test("returns b", t => 39 | t.expect.string(Strings.slice("abcdef", ~starti=1, ~endi=2, ())). 40 | toEqual( 41 | "b", 42 | ) 43 | ); 44 | t.test("returns bc", t => 45 | t.expect.string(Strings.slice("abcdef", ~starti=1, ~endi=3, ())). 46 | toEqual( 47 | "bc", 48 | ) 49 | ); 50 | }); 51 | t.describe("splitAtIndex", t => { 52 | t.test("does not include the split index", t => { 53 | let (left, right) = Strings.splitAtIndex("abcdf", ~index=2); 54 | t.expect.string(left).toEqual("ab"); 55 | t.expect.string(right).toEqual("df"); 56 | }); 57 | t.test("splits correctly at 0 index", t => { 58 | let (left, right) = Strings.splitAtIndex("abcdf", ~index=0); 59 | t.expect.string(left).toEqual(""); 60 | t.expect.string(right).toEqual("bcdf"); 61 | }); 62 | t.test("splits correctly at last index", t => { 63 | let (left, right) = 64 | Strings.splitAtIndex("abcdf", ~index=String.length("abcdf") - 1); 65 | t.expect.string(left).toEqual("abcd"); 66 | t.expect.string(right).toEqual(""); 67 | }); 68 | t.test("throws if index is outside the boundaries", t => 69 | t.expect.fn(() => Strings.splitAtIndex("abcdf", ~index=99)).toThrow() 70 | ); 71 | }); 72 | t.describe("splitAtChar", t => { 73 | t.test("returns none if char does not exist", t => { 74 | let result = Strings.splitAtChar("abcdf", ~char='z'); 75 | t.expect.option(result).toBeNone(); 76 | }); 77 | t.test("returns some if char exists", t => { 78 | let result = Strings.splitAtChar("abcdf", ~char='c'); 79 | t.expect.option(result).toBeSome(); 80 | }); 81 | t.test("does not include the split char", t => { 82 | let (left, right) = Strings.splitAtChar("abcdf", ~char='c') |> get; 83 | t.expect.string(left).toEqual("ab"); 84 | t.expect.string(right).toEqual("df"); 85 | }); 86 | t.test("splits correctly when char is at 0 index", t => { 87 | let (left, right) = Strings.splitAtChar("abcdf", ~char='a') |> get; 88 | t.expect.string(left).toEqual(""); 89 | t.expect.string(right).toEqual("bcdf"); 90 | }); 91 | t.test("splits correctly when char is at last index", t => { 92 | let (left, right) = Strings.splitAtChar("abcdf", ~char='f') |> get; 93 | t.expect.string(left).toEqual("abcd"); 94 | t.expect.string(right).toEqual(""); 95 | }); 96 | }); 97 | t.describe("split", t => { 98 | t.test("returns empty list when string is empty", t => { 99 | let result = Strings.split(""); 100 | t.expect.list(result).toEqual([]); 101 | }); 102 | t.test("splits string on chars", t => { 103 | let result = Strings.split("abcdf"); 104 | t.expect.list(result).toEqual(['a', 'b', 'c', 'd', 'f']); 105 | }); 106 | }); 107 | t.describe("contains", t => { 108 | t.test("returns true when string is contained at the end", t => { 109 | let result = Strings.contains("/bin/zsh", "zsh"); 110 | t.expect.bool(result).toBe(true); 111 | }); 112 | t.test("returns true when string is contained at the start", t => { 113 | let result = Strings.contains("zsh/bin/", "zsh"); 114 | t.expect.bool(result).toBe(true); 115 | }); 116 | t.test("returns true when string is contained in the middle", t => { 117 | let result = Strings.contains("/bin/zsh/", "zsh"); 118 | t.expect.bool(result).toBe(true); 119 | }); 120 | t.test("returns false when string is not contained", t => { 121 | let result = Strings.contains("/bin/sh/", "zsh"); 122 | t.expect.bool(result).toBe(false); 123 | }); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /tests/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name RargTests) 3 | (public_name rarg.tests) 4 | ; IMPORTANT: -linkall flag is required to include all tests. 5 | (ocamlopt_flags -linkall -g) 6 | (libraries 7 | rely.lib 8 | rarg.seed 9 | rarg.internal 10 | rarg 11 | ) 12 | (modules (:standard \ TestDev TestCi)) 13 | ) 14 | (executable 15 | (package tests) 16 | (name TestDev) 17 | (public_name TestDev.exe) 18 | (libraries 19 | rarg.tests 20 | ) 21 | (modules TestDev) 22 | ) 23 | (executable 24 | (package tests) 25 | (name TestCi) 26 | (public_name TestCi.exe) 27 | (libraries 28 | rarg.tests 29 | ) 30 | (modules TestCi) 31 | ) 32 | (include_subdirs unqualified) 33 | (dirs :standard __tests__) 34 | --------------------------------------------------------------------------------