├── .assets └── asciicast.gif ├── .gitignore ├── .ocamlformat ├── CHANGES.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── bin ├── dune ├── main.ml └── main.mli ├── dune ├── dune-project ├── examples ├── binary │ ├── .gitignore │ ├── .ocamlformat │ ├── CHANGES.md │ ├── CONTRIBUTING.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ ├── dune │ │ ├── main.ml │ │ └── main.mli │ ├── binary-help.txt │ ├── dune │ ├── dune-project │ ├── lib │ │ ├── binary.ml │ │ ├── binary.mli │ │ └── dune │ └── test │ │ ├── dune │ │ ├── main.ml │ │ └── main.mli ├── dune ├── executable │ ├── dune │ ├── dune-project │ └── executable.ml └── library │ ├── .gitignore │ ├── .ocamlformat │ ├── CHANGES.md │ ├── CONTRIBUTING.md │ ├── LICENSE │ ├── README.md │ ├── dune-project │ ├── src │ ├── dune │ ├── library.ml │ └── library.mli │ └── test │ ├── dune │ ├── main.ml │ └── main.mli ├── lib ├── config.ml ├── contents.ml ├── contents.mli ├── dune ├── faker.ml ├── faker.mli ├── layouts.ml ├── layouts.mli ├── license.ml ├── license.mli ├── opam.ml ├── opam.mli ├── oskel.ml ├── oskel.mli ├── utils.ml ├── utils.mli └── validate.ml ├── oskel-help.txt ├── oskel.opam └── prettier.config.js /.assets/asciicast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craigfe/oskel/057129faaf8171ff4ed49022346e9179b04735b2/.assets/asciicast.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | _opam/ 3 | 4 | *.install 5 | .merlin 6 | 7 | # Opam files in the `examples` directory are invalid. The CI will fail when it 8 | # attempts to pin them. 9 | examples/**/*.opam 10 | -------------------------------------------------------------------------------- /.ocamlformat: -------------------------------------------------------------------------------- 1 | version = 0.15.0 2 | parse-docstrings = true 3 | break-infix = fit-or-vertical 4 | break-infix-before-func = true 5 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # 0.3.0 (2020-05-05) 2 | 3 | - Get latest versions of Opam packages using `opam info`. (@CraigFe, #15) 4 | - Always supply a `public_name` field on libraries. (@CraigFe, #14) 5 | 6 | # 0.2.0 (2020-01-25) 7 | 8 | - Add a `CONTRIBUTING.md` file to the skeletons. (@NathanReb, #5) 9 | - Read name and email from `git config` if not set by the CLI (@CraigFe, #6) 10 | - Allow various project options to be set in an interactive mode (@CraigFe, #7 #9) 11 | - Allow customising the project's initial release version (@CraigFe, #8) 12 | 13 | # 0.1.0 (2020-01-09) 14 | 15 | Initial release. 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Setting up your working environment 2 | 3 | Oskel requires OCaml 4.05.0 or higher so you will need a corresponding opam 4 | switch. You can install a switch with the latest OCaml version by running: 5 | 6 | ``` 7 | opam switch create 4.09.0 ocaml-base-compiler.4.09.0 8 | ``` 9 | 10 | To clone the project's sources and install both its regular and test 11 | dependencies run: 12 | 13 | ``` 14 | git clone https://github.com:CraigFe/oskel.git 15 | cd oskel 16 | opam install -t --deps-only . 17 | ``` 18 | 19 | From there you can build all of the project's public libraries and executables 20 | with: 21 | 22 | ``` 23 | dune build @install 24 | ``` 25 | 26 | and run the test suite with: 27 | 28 | ``` 29 | dune runtest 30 | ``` 31 | 32 | ## Generating the examples 33 | 34 | The repo contains examples of projects that were generated with `oskel`. When 35 | modifying the various skeletons you should refresh the examples so that they 36 | reflect the changes you introduced. You can do so by running: 37 | 38 | ``` 39 | make examples 40 | ``` 41 | 42 | If you add a new project layout, edit [`examples/dune`](examples/dune) to 43 | generate a new example folder showcasing it: 44 | 45 | ```diff 46 | (progn 47 | (run oskel --synopsis "Single package in `src`" --kind=library library) 48 | + (run oskel --synopsis "" --kind= ) 49 | (run oskel --synopsis "Binary that depends on a tested library" 50 | --kind=binary binary) 51 | (run oskel --synopsis "Individual executable" --kind=executable 52 | executable))))) 53 | ``` 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2020 Craig Ferguson 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MD_FILES = *.md examples/**/*.md 2 | 3 | .PHONY: examples 4 | examples: 5 | dune clean 6 | dune build @examples 7 | find examples/ -mindepth 1 -maxdepth 1 -type d -exec rm -rf '{}' \; 8 | find _build/default/examples -mindepth 1 -maxdepth 1 -type d -not -path '*/\.*' -exec cp -rf '{}' examples/ \; 9 | 10 | .PHONY: lint 11 | lint: 12 | prettier --check $(MD_FILES) 13 | dune build @fmt 14 | dune-release lint 15 | 16 | .PHONY: format 17 | format: 18 | prettier --check $(MD_FILES) 19 | dune build --auto-promote @fmt 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :skull: oskel: skeleton generator for OCaml projects 2 | [![OCaml-CI Build Status](https://img.shields.io/endpoint?url=https%3A%2F%2Fci.ocamllabs.io%2Fbadge%2FCraigFe%2Foskel%2Fmaster&logo=ocaml)](https://ci.ocamllabs.io/github/CraigFe/oskel) 3 | 4 | [![asciicast](./.assets/asciicast.gif)](https://asciinema.org/a/298707) 5 | 6 | The standard project type is initialised with: 7 | 8 | - [`.opam` file autogeneration][dune-opam-files] via `dune-project`; 9 | - [OCamlformat][ocamlformat] config file; 10 | - [Alcotest][alcotest] testing boilerplate, with pre-configured [Logs][logs] 11 | initialisation. 12 | - Git repository with an initial commit; 13 | - OCaml `.gitignore`; 14 | - `README.md` with installation instructions for `opam`. 15 | 16 | ## Choice of project layouts 17 | 18 | There are multiple project structures, which can be selected via the `--kind` 19 | flag: 20 | 21 | - [`library`][example-library] (**default**): library-only package; 22 | - [`executable`][example-executable]: a single binary with minimal 23 | configuration; 24 | - [`binary`][example-binary]: package providing a [Cmdliner][cmdliner] binary in 25 | `bin/`, making use of a tested library in `lib/`. 26 | 27 | 32 | 33 | Examples of each layout can be seen in the [`examples/`][examples] directory. 34 | You can also use e.g. `oskel --dry-run --kind executable` to see a preview of 35 | the project structure. 36 | 37 | ## Installation 38 | 39 | ``` 40 | opam install oskel 41 | ``` 42 | 43 | If you want to contribute to the project, please read 44 | [CONTRIBUTING.md](CONTRIBUTING.md). 45 | 46 | ## Configuration 47 | 48 | `oskel` is very configurable (see [`oskel --help`](./oskel-help.txt) for 49 | details). Most options can be set via environment variables. In particular, you 50 | can set your personal metadata in your shell `.profile`: 51 | 52 | ``` 53 | export OSKEL_FULL_NAME="Joe Bloggs" 54 | export OSKEL_EMAIL="joe@example.com" 55 | export OSKEL_GITHUB_ORG="JoeBlo" 56 | ``` 57 | 58 | 59 | [examples]: https://github.com/CraigFe/oskel/tree/master/examples 60 | [example-library]: https://github.com/CraigFe/oskel/tree/master/examples/library 61 | [example-binary]: https://github.com/CraigFe/oskel/tree/master/examples/binary 62 | [example-ppx_deriver]: https://github.com/CraigFe/oskel/tree/master/examples/ppx_deriver 63 | [example-executable]: https://github.com/CraigFe/oskel/tree/master/examples/executable 64 | [dune-opam-files]: https://dune.readthedocs.io/en/stable/opam.html#generating-opam-files 65 | [logs]: https://erratique.ch/software/logs 66 | [cmdliner]: https://erratique.ch/software/cmdliner 67 | [ocamlformat]: https://github.com/ocaml-ppx/ocamlformat/ 68 | [alcotest]: https://github.com/mirage/alcotest/ 69 | [ppx_deriving]: https://github.com/ocaml-ppx/ppx_deriving 70 | [nathanreb-ppx-blog]: https://tarides.com/blog/2019-05-09-an-introduction-to-ocaml-ppx-ecosystem 71 | 72 | -------------------------------------------------------------------------------- /bin/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name main) 3 | (libraries cmdliner fmt fmt.cli fmt.tty logs logs.cli logs.fmt oskel lwt)) 4 | 5 | (install 6 | (section bin) 7 | (files 8 | (main.exe as oskel))) 9 | -------------------------------------------------------------------------------- /bin/main.ml: -------------------------------------------------------------------------------- 1 | let ( >>| ) x f = Lwt.map f x 2 | 3 | let git_user_name () = 4 | Oskel.Utils.Utils_unix.exec "git config user.name" 5 | >>| function 6 | | Ok [ name ] -> if String.(equal (trim name) "") then None else Some name 7 | | Ok o -> 8 | Oskel.show_errorf "Unexpected output from `git config`: %a" 9 | Fmt.Dump.(list string) 10 | o 11 | | Error _ -> None 12 | 13 | let git_email () = 14 | Oskel.Utils.Utils_unix.exec "git config user.email" 15 | >>| function 16 | | Ok [ email ] -> if String.(equal (trim email) "") then None else Some email 17 | | Ok o -> 18 | Oskel.show_errorf "Unexpected output from `git config`: %a" 19 | Fmt.Dump.(list string) 20 | o 21 | | Error _ -> None 22 | 23 | let ( >>? ) x f = match x with Some s -> Lwt.return (Some s) | None -> f () 24 | 25 | let run name project_kind project_synopsis maintainer_fullname maintainer_email 26 | github_organisation initial_version working_dir assume_yes license 27 | dependencies versions ocamlformat_options dry_run non_interactive git_repo 28 | current_year () : unit = 29 | let maintainer_fullname = maintainer_fullname >>? git_user_name in 30 | let maintainer_email = maintainer_email >>? git_email in 31 | Oskel.run ?name ~project_kind ?project_synopsis ~maintainer_fullname 32 | ~maintainer_email ?github_organisation ?initial_version ?working_dir 33 | ~assume_yes ~license ~dependencies ~versions ~ocamlformat_options ~dry_run 34 | ~non_interactive ~git_repo ?current_year () 35 | 36 | open Cmdliner 37 | 38 | let fmap f x = Term.(app (const f) x) 39 | 40 | module Arg = struct 41 | include Arg 42 | 43 | let env_var s = env_var ("OSKEL_" ^ s) 44 | end 45 | 46 | let project_name = Arg.(value & pos 0 (some string) None & info ~docv:"NAME" []) 47 | 48 | let project_kind = 49 | let kinds = 50 | [ ("library", `Library); ("binary", `Binary); ("executable", `Executable) ] 51 | in 52 | let doc = 53 | Fmt.str "Type of project to create. One of %s." (Arg.doc_alts_enum kinds) 54 | in 55 | let env = Arg.env_var "KIND" in 56 | Arg.(value & opt (enum kinds) `Library & info [ "kind" ] ~doc ~env) 57 | 58 | let project_synopsis = 59 | let doc = "Synopsis of the project skeleton." in 60 | Arg.(value & opt (some string) None & info [ "synopsis" ] ~doc) 61 | 62 | let maintainer_fullname = 63 | let doc = 64 | "Maintainer's full name. If not specified, Oskel will attempt to read this \ 65 | from `git config user.name`." 66 | in 67 | let env = Arg.env_var "FULL_NAME" in 68 | Arg.(value & opt (some string) None & info [ "full-name" ] ~doc ~env) 69 | 70 | let maintainer_email = 71 | let doc = 72 | "Maintainer's contact email. If not specified, Oskel will attempt to read \ 73 | this from `git config user.email`." 74 | in 75 | let env = Arg.env_var "EMAIL" in 76 | Arg.(value & opt (some string) None & info [ "email" ] ~doc ~env) 77 | 78 | let github_organisation = 79 | let doc = "GitHub organisation associated with the project." in 80 | let env = Arg.env_var "GITHUB_ORG" in 81 | Arg.(value & opt (some string) None & info [ "github-org" ] ~doc ~env) 82 | 83 | let initial_version = 84 | let doc = "Initial version at which to release the project." in 85 | let env = Arg.env_var "INITIAL_VERSION" in 86 | Arg.(value & opt (some string) None & info [ "initial-version" ] ~doc ~env) 87 | 88 | let working_dir = 89 | let doc = 90 | "Run as if Oskel was started in instead of the current working \ 91 | directory." 92 | in 93 | Arg.(value & opt (some string) None & info [ "working-dir" ] ~doc) 94 | 95 | let assume_yes = 96 | let doc = "Respond `yes' to all prompts and accept all defaults." in 97 | Arg.(value & flag & info [ "yes"; "assume-yes" ] ~doc) 98 | 99 | let license = 100 | let licenses = Oskel.License.all in 101 | let doc = 102 | Fmt.str "License to add to the project. One of %s." 103 | (Arg.doc_alts_enum licenses) 104 | in 105 | let env = Arg.env_var "LICENSE" in 106 | Arg.( 107 | value & opt (enum licenses) Oskel.License.Mit & info [ "license" ] ~doc ~env) 108 | 109 | let dependencies = 110 | let doc = "Dependencies of the project in a comma-separated list." in 111 | let env = Arg.env_var "DEPENDS" in 112 | Arg.( 113 | value 114 | & opt (list ~sep:',' string) [ "fmt"; "logs" ] 115 | & info [ "depends" ] ~doc ~env) 116 | 117 | let version_dune = 118 | let doc = "Version of dune to associate with the project." in 119 | let env = Arg.env_var "VERSION_DUNE" in 120 | Arg.(value & opt (some string) None & info [ "version-dune" ] ~doc ~env) 121 | |> fmap (fun x -> `Dune x) 122 | 123 | let version_ocaml = 124 | let doc = "Version of OCaml to associate with the project." in 125 | let env = Arg.env_var "VERSION_OCAML" in 126 | Arg.(value & opt (some string) None & info [ "version-ocaml" ] ~doc ~env) 127 | |> fmap (fun x -> `OCaml x) 128 | 129 | let version_opam = 130 | let doc = 131 | Fmt.strf 132 | "Version of opam to associate with the project. The default value is \ 133 | `%s`." 134 | Oskel.Opam.default_opam_version 135 | in 136 | let env = Arg.env_var "VERSION_OPAM" in 137 | Arg.(value & opt (some string) None & info [ "version-opam" ] ~doc ~env) 138 | |> fmap (fun x -> `Opam x) 139 | 140 | let version_ocamlformat = 141 | let doc = "Version of OCamlformat to associate with the project." in 142 | let env = Arg.env_var "VERSION_OCAMLFORMAT" in 143 | Arg.( 144 | value & opt (some string) None & info [ "version-ocamlformat" ] ~doc ~env) 145 | |> fmap (fun x -> `OCamlformat x) 146 | 147 | let versions = 148 | Term.( 149 | const Oskel.Opam.get_versions 150 | $ version_dune 151 | $ version_ocaml 152 | $ version_opam 153 | $ version_ocamlformat) 154 | 155 | let ocamlformat_options = 156 | let doc = 157 | "Options to add to the .ocamlformat file, as a comma-separated list of \ 158 | key-value pairs. (e.g. \ 159 | \"parse-docstrings=true,break-infix=fit-or-vertical\")" 160 | in 161 | let env = Arg.env_var "OCAMLFORMAT_OPTIONS" in 162 | Arg.( 163 | value 164 | & opt (list ~sep:',' (pair ~sep:'=' string string)) [] 165 | & info [ "ocamlformat-options" ] ~doc ~env) 166 | 167 | let dry_run = 168 | let doc = "Simulate the command, but don't actually perform any changes." in 169 | Arg.(value & flag & info [ "dry-run" ] ~doc) 170 | 171 | let non_interactive = 172 | let doc = "Do not show interactive prompts." in 173 | Arg.(value & flag & info [ "non-interactive" ] ~doc) 174 | 175 | let git_repo = 176 | let doc = "Don't generate a git repository for the project." in 177 | let env = Arg.env_var "DISABLE_GIT" in 178 | Term.(pure not $ Arg.(value & flag & info [ "disable-git" ] ~doc ~env)) 179 | 180 | let current_year = 181 | let doc = 182 | "Set the current year. Useful for achieving deterministic output." 183 | in 184 | let env = Arg.env_var "CURRENT_YEAR" in 185 | Arg.(value & opt (some int) None & info [ "current-year" ] ~env ~doc) 186 | 187 | let setup_log = 188 | let init style_renderer level = 189 | Fmt_tty.setup_std_outputs ?style_renderer (); 190 | Logs.set_level level; 191 | Logs.set_reporter (Logs_fmt.reporter ()) 192 | in 193 | Term.(const init $ Fmt_cli.style_renderer () $ Logs_cli.level ()) 194 | 195 | let term = 196 | let doc = "Generate skeleton OCaml projects." in 197 | let exits = Term.default_exits in 198 | let man = [] in 199 | Term. 200 | ( const run 201 | $ project_name 202 | $ project_kind 203 | $ project_synopsis 204 | $ maintainer_fullname 205 | $ maintainer_email 206 | $ github_organisation 207 | $ initial_version 208 | $ working_dir 209 | $ assume_yes 210 | $ license 211 | $ dependencies 212 | $ versions 213 | $ ocamlformat_options 214 | $ dry_run 215 | $ non_interactive 216 | $ git_repo 217 | $ current_year 218 | $ setup_log, 219 | info "oskel" ~version:"%%VERSION%%" ~doc ~exits ~man ) 220 | 221 | let () = Term.exit (Term.eval term) 222 | -------------------------------------------------------------------------------- /bin/main.mli: -------------------------------------------------------------------------------- 1 | (* intentionally empty *) 2 | -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (rule 2 | (with-stdout-to 3 | oskel-help.txt.gen 4 | (run oskel --help=plain))) 5 | 6 | (rule 7 | (alias runtest) 8 | (action 9 | (diff oskel-help.txt oskel-help.txt.gen))) 10 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.0) 2 | (name oskel) 3 | (implicit_transitive_deps false) 4 | 5 | (generate_opam_files true) 6 | (source (github CraigFe/oskel)) 7 | (maintainers "Craig Ferguson ") 8 | (authors "Craig Ferguson ") 9 | 10 | (package 11 | (name oskel) 12 | (synopsis "Skeleton generator for OCaml projects") 13 | (documentation https://CraigFe.github.io/oskel) 14 | (description "\ 15 | Skeleton generator for OCaml projects 16 | 17 | oskel is a tool for generating stubs of OCaml projects pre-equipped with the 18 | standard boilerplate: OCamlformat config file, Alcotest scaffolding, 19 | auto-generation of [*.opam] files via [dune-project] etc. It supports generating 20 | binaries with Cmdliner, Fmt and Logs support. The resulting projects are 21 | compliant with [dune-release lint]. 22 | ") 23 | (depends cmdliner (fmt (>= 0.8.7)) logs stdlib-shims ocaml-syntax-shims lwt)) 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/binary/.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | _opam/ 3 | *~ 4 | \.\#* 5 | \#*# 6 | *.install 7 | .merlin 8 | -------------------------------------------------------------------------------- /examples/binary/.ocamlformat: -------------------------------------------------------------------------------- 1 | version = 0.12 2 | -------------------------------------------------------------------------------- /examples/binary/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 2 | -------------------------------------------------------------------------------- /examples/binary/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Setting up your working environment 2 | 3 | binary requires OCaml 4.09.0 or higher so you will need a corresponding opam 4 | switch. You can install a switch with the latest OCaml version by running: 5 | 6 | ``` 7 | opam switch create 4.09.0 ocaml-base-compiler.4.09.0 8 | ``` 9 | 10 | To clone the project's sources and install both its regular and test 11 | dependencies run: 12 | 13 | ``` 14 | git clone https://github.com:JoeBloggs/binary.git 15 | cd binary 16 | opam install -t --deps-only . 17 | ``` 18 | 19 | From there you can build all of the project's public libraries and executables 20 | with: 21 | 22 | ``` 23 | dune build @install 24 | ``` 25 | 26 | and run the test suite with: 27 | 28 | ``` 29 | dune runtest 30 | ``` 31 | 32 | If the test suite fails, it may propose a diff to fix the issue. You may accept 33 | the proposed diff with `dune promote`. 34 | -------------------------------------------------------------------------------- /examples/binary/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 1999 Joe Bloggs 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/binary/README.md: -------------------------------------------------------------------------------- 1 | # binary 2 | 3 | Binary that depends on a tested library 4 | 5 | ## Installation 6 | 7 | ``` 8 | opam pin add --yes https://github.com/JoeBloggs/binary.git 9 | opam install binary 10 | ``` 11 | 12 | If you want to contribute to the project, please read 13 | [CONTRIBUTING.md](CONTRIBUTING.md). 14 | -------------------------------------------------------------------------------- /examples/binary/bin/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name main) 3 | (libraries binary cmdliner fmt fmt.cli fmt.tty logs logs.cli logs.fmt)) 4 | 5 | (install 6 | (section bin) 7 | (files 8 | (main.exe as binary))) 9 | -------------------------------------------------------------------------------- /examples/binary/bin/main.ml: -------------------------------------------------------------------------------- 1 | let main () = Binary.main () |> print_endline 2 | 3 | open Cmdliner 4 | 5 | let setup_log = 6 | let init style_renderer level = 7 | Fmt_tty.setup_std_outputs ?style_renderer (); 8 | Logs.set_level level; 9 | Logs.set_reporter (Logs_fmt.reporter ()) 10 | in 11 | Term.(const init $ Fmt_cli.style_renderer () $ Logs_cli.level ()) 12 | 13 | let term = 14 | let doc = "Binary that depends on a tested library" in 15 | let exits = Term.default_exits in 16 | let man = [] in 17 | Term.(const main $ setup_log, info "binary" ~doc ~exits ~man) 18 | 19 | let () = Term.exit (Term.eval term) 20 | -------------------------------------------------------------------------------- /examples/binary/bin/main.mli: -------------------------------------------------------------------------------- 1 | (* intentionally empty *) 2 | -------------------------------------------------------------------------------- /examples/binary/binary-help.txt: -------------------------------------------------------------------------------- 1 | NAME 2 | binary - Binary that depends on a tested library 3 | 4 | SYNOPSIS 5 | binary [OPTION]... 6 | 7 | OPTIONS 8 | --color=WHEN (absent=auto) 9 | Colorize the output. WHEN must be one of `auto', `always' or 10 | `never'. 11 | 12 | --help[=FMT] (default=auto) 13 | Show this help in format FMT. The value FMT must be one of `auto', 14 | `pager', `groff' or `plain'. With `auto', the format is `pager` or 15 | `plain' whenever the TERM env var is `dumb' or undefined. 16 | 17 | -q, --quiet 18 | Be quiet. Takes over -v and --verbosity. 19 | 20 | -v, --verbose 21 | Increase verbosity. Repeatable, but more than twice does not bring 22 | more. 23 | 24 | --verbosity=LEVEL (absent=warning) 25 | Be more or less verbose. LEVEL must be one of `quiet', `error', 26 | `warning', `info' or `debug'. Takes over -v. 27 | 28 | EXIT STATUS 29 | binary exits with the following status: 30 | 31 | 0 on success. 32 | 33 | 124 on command line parsing errors. 34 | 35 | 125 on unexpected internal errors (bugs). 36 | 37 | -------------------------------------------------------------------------------- /examples/binary/dune: -------------------------------------------------------------------------------- 1 | (rule 2 | (targets binary-help.txt.gen) 3 | (action 4 | (with-stdout-to 5 | %{targets} 6 | (run binary --help=plain)))) 7 | 8 | (rule 9 | (alias runtest) 10 | (action 11 | (diff binary-help.txt binary-help.txt.gen))) 12 | -------------------------------------------------------------------------------- /examples/binary/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.0) 2 | (name binary) 3 | (implicit_transitive_deps false) 4 | 5 | (generate_opam_files true) 6 | (source (github JoeBloggs/binary)) 7 | (maintainers "Joe Bloggs ") 8 | (authors "Joe Bloggs ") 9 | 10 | (package 11 | (name binary) 12 | (synopsis "Binary that depends on a tested library") 13 | (description "\ 14 | Binary that depends on a tested library 15 | ") 16 | (documentation https://JoeBloggs.github.io/binary/) 17 | (depends alcotest fmt logs)) 18 | -------------------------------------------------------------------------------- /examples/binary/lib/binary.ml: -------------------------------------------------------------------------------- 1 | let main () = 2 | let () = Logs.debug (fun m -> m "Program has started") in 3 | "Hello, World!" 4 | -------------------------------------------------------------------------------- /examples/binary/lib/binary.mli: -------------------------------------------------------------------------------- 1 | val main : unit -> string 2 | -------------------------------------------------------------------------------- /examples/binary/lib/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name binary) 3 | (libraries logs)) 4 | -------------------------------------------------------------------------------- /examples/binary/test/dune: -------------------------------------------------------------------------------- 1 | (test 2 | (name main) 3 | (libraries alcotest binary logs logs.fmt)) 4 | -------------------------------------------------------------------------------- /examples/binary/test/main.ml: -------------------------------------------------------------------------------- 1 | let test expected () = 2 | Alcotest.(check string) "test 0" expected (Binary.main ()) 3 | 4 | let suite = 5 | [ 6 | ("Dummy passing test", `Quick, test "Hello, World!"); 7 | ("Dummy failing test", `Quick, test "Bye, World!"); 8 | ] 9 | 10 | let () = 11 | Logs.set_level (Some Logs.Debug); 12 | Logs.set_reporter (Logs_fmt.reporter ()); 13 | Alcotest.run "binary" [ ("suite", suite) ] 14 | -------------------------------------------------------------------------------- /examples/binary/test/main.mli: -------------------------------------------------------------------------------- 1 | (* intentionally empty *) 2 | -------------------------------------------------------------------------------- /examples/dune: -------------------------------------------------------------------------------- 1 | (rule 2 | (alias examples) 3 | (deps (universe)) 4 | (action 5 | (chdir 6 | %{project_root}/examples 7 | (progn 8 | (run oskel --non-interactive --synopsis "Single package in `src`" 9 | --kind=library library) 10 | (run oskel --non-interactive --synopsis 11 | "Binary that depends on a tested library" --kind=binary binary) 12 | (run oskel --non-interactive --synopsis "Individual executable" 13 | --kind=executable executable))))) 14 | 15 | (env 16 | (_ 17 | (env-vars 18 | (OSKEL_DISABLE_GIT "true") 19 | (OSKEL_INITIAL_VERSION "0.1.0") 20 | (OSKEL_CURRENT_YEAR "1999") 21 | (OSKEL_FULL_NAME "Joe Bloggs") 22 | (OSKEL_EMAIL "joe@example.com") 23 | (OSKEL_GITHUB_ORG "JoeBloggs")))) 24 | 25 | (data_only_dirs *) 26 | -------------------------------------------------------------------------------- /examples/executable/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name executable) 3 | (libraries fmt logs)) 4 | -------------------------------------------------------------------------------- /examples/executable/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.0) 2 | -------------------------------------------------------------------------------- /examples/executable/executable.ml: -------------------------------------------------------------------------------- 1 | let () = print_endline "Hello, World!" 2 | -------------------------------------------------------------------------------- /examples/library/.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | _opam/ 3 | *~ 4 | \.\#* 5 | \#*# 6 | *.install 7 | .merlin 8 | -------------------------------------------------------------------------------- /examples/library/.ocamlformat: -------------------------------------------------------------------------------- 1 | version = 0.12 2 | -------------------------------------------------------------------------------- /examples/library/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 2 | -------------------------------------------------------------------------------- /examples/library/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Setting up your working environment 2 | 3 | library requires OCaml 4.09.0 or higher so you will need a corresponding opam 4 | switch. You can install a switch with the latest OCaml version by running: 5 | 6 | ``` 7 | opam switch create 4.09.0 ocaml-base-compiler.4.09.0 8 | ``` 9 | 10 | To clone the project's sources and install both its regular and test 11 | dependencies run: 12 | 13 | ``` 14 | git clone https://github.com:JoeBloggs/library.git 15 | cd library 16 | opam install -t --deps-only . 17 | ``` 18 | 19 | From there you can build all of the project's public libraries and executables 20 | with: 21 | 22 | ``` 23 | dune build @install 24 | ``` 25 | 26 | and run the test suite with: 27 | 28 | ``` 29 | dune runtest 30 | ``` 31 | -------------------------------------------------------------------------------- /examples/library/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 1999 Joe Bloggs 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/library/README.md: -------------------------------------------------------------------------------- 1 | # library 2 | 3 | Single package in `src` 4 | 5 | ## Installation 6 | 7 | ``` 8 | opam pin add --yes https://github.com/JoeBloggs/library.git 9 | opam install library 10 | ``` 11 | 12 | If you want to contribute to the project, please read 13 | [CONTRIBUTING.md](CONTRIBUTING.md). 14 | -------------------------------------------------------------------------------- /examples/library/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.0) 2 | (name library) 3 | (implicit_transitive_deps false) 4 | 5 | (generate_opam_files true) 6 | (source (github JoeBloggs/library)) 7 | (maintainers "Joe Bloggs ") 8 | (authors "Joe Bloggs ") 9 | 10 | (package 11 | (name library) 12 | (synopsis "Single package in `src`") 13 | (description "\ 14 | Single package in `src` 15 | ") 16 | (documentation https://JoeBloggs.github.io/library/) 17 | (depends alcotest fmt logs)) 18 | -------------------------------------------------------------------------------- /examples/library/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name library) 3 | (libraries logs)) 4 | -------------------------------------------------------------------------------- /examples/library/src/library.ml: -------------------------------------------------------------------------------- 1 | let main () = 2 | let () = Logs.debug (fun m -> m "Program has started") in 3 | "Hello, World!" 4 | -------------------------------------------------------------------------------- /examples/library/src/library.mli: -------------------------------------------------------------------------------- 1 | val main : unit -> string 2 | -------------------------------------------------------------------------------- /examples/library/test/dune: -------------------------------------------------------------------------------- 1 | (test 2 | (name main) 3 | (libraries alcotest library logs logs.fmt)) 4 | -------------------------------------------------------------------------------- /examples/library/test/main.ml: -------------------------------------------------------------------------------- 1 | let test expected () = 2 | Alcotest.(check string) "test 0" expected (Library.main ()) 3 | 4 | let suite = 5 | [ 6 | ("Dummy passing test", `Quick, test "Hello, World!"); 7 | ("Dummy failing test", `Quick, test "Bye, World!"); 8 | ] 9 | 10 | let () = 11 | Logs.set_level (Some Logs.Debug); 12 | Logs.set_reporter (Logs_fmt.reporter ()); 13 | Alcotest.run "library" [ ("suite", suite) ] 14 | -------------------------------------------------------------------------------- /examples/library/test/main.mli: -------------------------------------------------------------------------------- 1 | (* intentionally empty *) 2 | -------------------------------------------------------------------------------- /lib/config.ml: -------------------------------------------------------------------------------- 1 | let pp_license ppf _ = Fmt.pf ppf "MIT" 2 | 3 | type versions = { 4 | dune : string; 5 | ocaml : string; 6 | opam : string; 7 | ocamlformat : string; 8 | } 9 | 10 | type t = { 11 | name : string; 12 | project_synopsis : string; 13 | maintainer_fullname : string; 14 | maintainer_email : string; 15 | github_organisation : string; 16 | initial_version : string; 17 | license : License.t; 18 | dependencies : string list; 19 | versions : versions; 20 | ocamlformat_options : (string * string) list; 21 | current_year : int; 22 | git_repo : bool; 23 | } 24 | -------------------------------------------------------------------------------- /lib/contents.ml: -------------------------------------------------------------------------------- 1 | open Config 2 | open Utils 3 | 4 | type file_printer = Config.t -> Format.formatter -> unit 5 | 6 | module Dune_project = struct 7 | let package c ppf = 8 | let dependencies = 9 | c.dependencies 10 | |> List.append [ "alcotest" ] 11 | |> List.sort_uniq String.compare 12 | in 13 | Fmt.pf ppf {|(lang dune %s) 14 | (name %s) 15 | (implicit_transitive_deps false) 16 | 17 | |} 18 | c.versions.dune c.name; 19 | Fmt.pf ppf 20 | {|(generate_opam_files true) 21 | (source (github %s/%s)) 22 | (maintainers "%s <%s>") 23 | (authors "%s <%s>") 24 | 25 | |} 26 | c.github_organisation c.name c.maintainer_fullname c.maintainer_email 27 | c.maintainer_fullname c.maintainer_email; 28 | Fmt.pf ppf 29 | {|(package 30 | (name %s) 31 | (synopsis "%s") 32 | (description "\ 33 | %s 34 | ") 35 | (documentation https://%s.github.io/%s/) 36 | (depends %a))|} 37 | c.name c.project_synopsis c.project_synopsis c.github_organisation c.name 38 | Fmt.(list ~sep:(const string " ") string) 39 | dependencies 40 | 41 | let minimal config ppf = Fmt.pf ppf "(lang dune %s)" config.versions.dune 42 | end 43 | 44 | module Dune = struct 45 | let library { name; _ } ppf = 46 | let file = Utils_naming.file_of_project name in 47 | Fmt.pf ppf {|(library 48 | (name %s) 49 | (public_name %s) 50 | (libraries logs))|} file 51 | name 52 | 53 | let pp_libraries ppf deps = 54 | match deps with 55 | | [] -> () 56 | | _ :: _ -> 57 | let open Fmt in 58 | pf ppf "@,(libraries %a)" (list ~sep:(const string " ") string) deps 59 | 60 | let executable ~name ?(libraries = []) ppf = 61 | let file = Utils_naming.file_of_project name in 62 | Fmt.pf ppf "@[(executable@,(name %s)%a)@]" file pp_libraries libraries 63 | 64 | let install ~exe_name ~bin_name ppf = 65 | Fmt.pf ppf 66 | "@[(install@,(section bin)@,@[(files@,(%s.exe as %s))@])@]" 67 | exe_name bin_name 68 | 69 | let test config ppf = 70 | let dependencies = 71 | [ config.name; "alcotest"; "logs"; "logs.fmt" ] 72 | |> List.sort String.compare 73 | in 74 | Fmt.pf ppf "@[(test@,(name main)%a)@]" pp_libraries dependencies 75 | 76 | let ppx_deriver { name = n; _ } ppf = 77 | Fmt.pf ppf 78 | {|(library 79 | (public_name %s) 80 | (kind ppx_deriver) 81 | (libraries %s_lib ppxlib))|} 82 | n n 83 | 84 | let ppx_deriver_lib _ _ppf = () 85 | 86 | let generate_help { name = n; _ } ppf = 87 | Fmt.pf ppf 88 | {|(rule 89 | (targets %s-help.txt.gen) 90 | (action 91 | (with-stdout-to 92 | %%{targets} 93 | (run %s --help=plain)))) 94 | 95 | (rule 96 | (alias runtest) 97 | (action 98 | (diff %s-help.txt %s-help.txt.gen)))|} 99 | n n n n 100 | end 101 | 102 | let hello_world_bin _config ppf = 103 | Fmt.pf ppf "let () = print_endline \"Hello, World!\"" 104 | 105 | let hello_world_lib_ml _config ppf = 106 | Fmt.pf ppf 107 | {|let main () = 108 | let () = Logs.debug (fun m -> m "Program has started") in 109 | "Hello, World!"|} 110 | 111 | let hello_world_lib_mli _config ppf = Fmt.pf ppf "val main : unit -> string" 112 | 113 | let gitignore _config ppf = 114 | Fmt.pf ppf {|_build/ 115 | _opam/ 116 | *~ 117 | \.\#* 118 | \#*# 119 | *.install 120 | .merlin|} 121 | 122 | let readme config ppf = 123 | Fmt.pf ppf 124 | {|# %s 125 | 126 | %s 127 | 128 | ## Installation 129 | 130 | ``` 131 | opam pin add --yes https://github.com/%s/%s.git 132 | opam install %s 133 | ``` 134 | 135 | If you want to contribute to the project, please read 136 | [CONTRIBUTING.md](CONTRIBUTING.md).|} 137 | config.name config.project_synopsis config.github_organisation config.name 138 | config.name 139 | 140 | let contributing ?promote config ppf = 141 | Fmt.pf ppf 142 | {|## Setting up your working environment 143 | 144 | %s requires OCaml %s or higher so you will need a corresponding opam 145 | switch. You can install a switch with the latest OCaml version by running: 146 | 147 | ``` 148 | opam switch create 4.09.0 ocaml-base-compiler.4.09.0 149 | ``` 150 | 151 | To clone the project's sources and install both its regular and test 152 | dependencies run: 153 | 154 | ``` 155 | git clone https://github.com:%s/%s.git 156 | cd %s 157 | opam install -t --deps-only . 158 | ``` 159 | 160 | From there you can build all of the project's public libraries and executables 161 | with: 162 | 163 | ``` 164 | dune build @install 165 | ``` 166 | 167 | and run the test suite with: 168 | 169 | ``` 170 | dune runtest 171 | ```|} 172 | config.name config.versions.ocaml config.github_organisation config.name 173 | config.name; 174 | match promote with 175 | | Some () -> 176 | Fmt.pf ppf 177 | {| 178 | 179 | If the test suite fails, it may propose a diff to fix the issue. You may accept 180 | the proposed diff with `dune promote`.|} 181 | | None -> () 182 | 183 | let readme_ppx = readme 184 | 185 | let changes { initial_version; _ } ppf = Fmt.pf ppf "# %s" initial_version 186 | 187 | let ocamlformat config ppf = 188 | Fmt.pf ppf "@[version = %s%a@]" config.versions.ocamlformat 189 | Fmt.(list (cut ++ pair ~sep:(const string " = ") string string)) 190 | config.ocamlformat_options 191 | 192 | let opam config ppf = 193 | let pp_homepage ppf config = 194 | Fmt.pf ppf "https://github.com/%s/%s" config.github_organisation config.name 195 | in 196 | let pp_bugreports ppf config = Fmt.pf ppf "%a/issues" pp_homepage config in 197 | let pp_devrepo ppf config = 198 | Fmt.pf ppf "git+https://github.com/%s/%s.git" config.github_organisation 199 | config.name 200 | in 201 | Fmt.pf ppf 202 | {|opam-version: "%s" 203 | maintainer: "%s" 204 | authors: ["%s"] 205 | license: "%a" 206 | homepage: "%a" 207 | bug-reports: "%a" 208 | dev-repo: "%a" 209 | 210 | build: [ 211 | ["dune" "subst"] {pinned} 212 | ["dune" "build" "-p" name "-j" jobs] 213 | ["dune" "runtest" "-p" name] {with-test} 214 | ] 215 | 216 | depends: [ 217 | "ocaml" {>= "%s"} 218 | "dune" {build & >= "%s"} 219 | "fmt" 220 | "logs" 221 | "alcotest" {with-test} 222 | ] 223 | synopsis: "%s"|} 224 | config.versions.opam config.maintainer_fullname config.maintainer_fullname 225 | Config.pp_license config.license pp_homepage config pp_bugreports config 226 | pp_devrepo config config.versions.ocaml config.versions.dune 227 | config.project_synopsis 228 | 229 | let test_main_ml config ppf = 230 | Fmt.pf ppf 231 | {|let test expected () = 232 | Alcotest.(check string) "test 0" expected (%s.main ()) 233 | 234 | let suite = 235 | [ 236 | ("Dummy passing test", `Quick, test "Hello, World!"); 237 | ("Dummy failing test", `Quick, test "Bye, World!"); 238 | ] 239 | 240 | let () = 241 | Logs.set_level (Some Logs.Debug); 242 | Logs.set_reporter (Logs_fmt.reporter ()); 243 | Alcotest.run "%s" [ ("suite", suite) ]|} 244 | (Utils_naming.findlib_of_project config.name) 245 | config.name 246 | 247 | let empty_mli _config ppf = Fmt.pf ppf "(* intentionally empty *)" 248 | 249 | let src_ppx_deriver_ml config ppf = 250 | Fmt.pf ppf 251 | {|open Ppxlib 252 | open Ppx_%s_lib 253 | 254 | let ppx_name = "%s" 255 | 256 | let expand_str ~loc ~path:_ input_ast name = 257 | let (module S) = Ast_builder.make loc in 258 | let (module L) = (module Deriver.Located (S) : Deriver.S) in 259 | L.derive_str ?name input_ast 260 | 261 | let expand_sig ~loc ~path:_ input_ast name = 262 | let (module S) = Ast_builder.make loc in 263 | let (module L) = (module Deriver.Located (S) : Deriver.S) in 264 | L.derive_sig ?name input_ast 265 | 266 | let str_type_decl_generator = 267 | let args = Deriving.Args.(empty +> arg "name" (estring __)) in 268 | let attributes = Attributes.all in 269 | Deriving.Generator.make ~attributes args expand_str 270 | 271 | let sig_typ_decl_generator = 272 | let args = Deriving.Args.(empty +> arg "name" (estring __)) in 273 | Deriving.Generator.make args expand_sig 274 | 275 | let %s = 276 | Deriving.add ~str_type_decl:str_type_decl_generator 277 | ~sig_type_decl:sig_typ_decl_generator ppx_name|} 278 | config.name config.name config.name 279 | 280 | let src_ppx_deriver_mli config ppf = 281 | Fmt.pf ppf {|val %s : Ppxlib.Deriving.t 282 | |} config.name 283 | 284 | let dune_gen_dune_rules _ _ppf = () 285 | 286 | let gen_dune_rules_ml _ _ppf = () 287 | 288 | let bin_cmdliner config ppf = 289 | Fmt.pf ppf 290 | {|let main () = %s.main () |> print_endline 291 | 292 | open Cmdliner 293 | 294 | let setup_log = 295 | let init style_renderer level = 296 | Fmt_tty.setup_std_outputs ?style_renderer (); 297 | Logs.set_level level; 298 | Logs.set_reporter (Logs_fmt.reporter ()) 299 | in 300 | Term.(const init $ Fmt_cli.style_renderer () $ Logs_cli.level ()) 301 | 302 | let term = 303 | let doc = "%s" in 304 | let exits = Term.default_exits in 305 | let man = [] in 306 | Term.(const main $ setup_log, info "%s" ~doc ~exits ~man) 307 | 308 | let () = Term.exit (Term.eval term)|} 309 | (Utils_naming.findlib_of_project config.name) 310 | config.project_synopsis config.name 311 | 312 | let bin_help_txt config ppf = 313 | Fmt.pf ppf 314 | {|NAME 315 | %s - %s 316 | 317 | SYNOPSIS 318 | %s [OPTION]... 319 | 320 | OPTIONS 321 | --color=WHEN (absent=auto) 322 | Colorize the output. WHEN must be one of `auto', `always' or 323 | `never'. 324 | 325 | --help[=FMT] (default=auto) 326 | Show this help in format FMT. The value FMT must be one of `auto', 327 | `pager', `groff' or `plain'. With `auto', the format is `pager` or 328 | `plain' whenever the TERM env var is `dumb' or undefined. 329 | 330 | -q, --quiet 331 | Be quiet. Takes over -v and --verbosity. 332 | 333 | -v, --verbose 334 | Increase verbosity. Repeatable, but more than twice does not bring 335 | more. 336 | 337 | --verbosity=LEVEL (absent=warning) 338 | Be more or less verbose. LEVEL must be one of `quiet', `error', 339 | `warning', `info' or `debug'. Takes over -v. 340 | 341 | EXIT STATUS 342 | %s exits with the following status: 343 | 344 | 0 on success. 345 | 346 | 124 on command line parsing errors. 347 | 348 | 125 on unexpected internal errors (bugs). 349 | |} 350 | config.name config.project_synopsis config.name config.name 351 | -------------------------------------------------------------------------------- /lib/contents.mli: -------------------------------------------------------------------------------- 1 | type file_printer = Config.t -> Format.formatter -> unit 2 | 3 | module Dune_project : sig 4 | val package : file_printer 5 | 6 | val minimal : file_printer 7 | end 8 | 9 | module Dune : sig 10 | val library : file_printer 11 | 12 | val executable : 13 | name:string -> ?libraries:string list -> Format.formatter -> unit 14 | 15 | val install : exe_name:string -> bin_name:string -> Format.formatter -> unit 16 | 17 | val test : file_printer 18 | 19 | val ppx_deriver : file_printer 20 | 21 | val ppx_deriver_lib : file_printer 22 | 23 | val generate_help : file_printer 24 | end 25 | 26 | val gitignore : file_printer 27 | 28 | val readme : file_printer 29 | 30 | val contributing : ?promote:unit -> file_printer 31 | 32 | val readme_ppx : file_printer 33 | 34 | val changes : file_printer 35 | 36 | val ocamlformat : file_printer 37 | 38 | val opam : file_printer 39 | 40 | val hello_world_bin : file_printer 41 | 42 | val hello_world_lib_ml : file_printer 43 | 44 | val hello_world_lib_mli : file_printer 45 | 46 | val test_main_ml : file_printer 47 | 48 | val empty_mli : file_printer 49 | 50 | val src_ppx_deriver_ml : file_printer 51 | 52 | val src_ppx_deriver_mli : file_printer 53 | 54 | val dune_gen_dune_rules : file_printer 55 | 56 | val gen_dune_rules_ml : file_printer 57 | 58 | val bin_cmdliner : file_printer 59 | 60 | val bin_help_txt : file_printer 61 | -------------------------------------------------------------------------------- /lib/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name oskel) 3 | (libraries unix logs logs.lwt fmt stdlib-shims lwt lwt.unix) 4 | (preprocess future_syntax)) 5 | -------------------------------------------------------------------------------- /lib/faker.ml: -------------------------------------------------------------------------------- 1 | let adjectives = 2 | [ 3 | "abandoned"; 4 | "able"; 5 | "absolute"; 6 | "academic"; 7 | "acceptable"; 8 | "acclaimed"; 9 | "accomplished"; 10 | "accurate"; 11 | "aching"; 12 | "acidic"; 13 | "acrobatic"; 14 | "active"; 15 | "actual"; 16 | "adept"; 17 | "admirable"; 18 | "admired"; 19 | "adolescent"; 20 | "adorable"; 21 | "adorable"; 22 | "adored"; 23 | "advanced"; 24 | "adventurous"; 25 | "affectionate"; 26 | "afraid"; 27 | "aged"; 28 | "aggravating"; 29 | "aggressive"; 30 | "agile"; 31 | "agitated"; 32 | "agonizing"; 33 | "agreeable"; 34 | "ajar"; 35 | "alarmed"; 36 | "alarming"; 37 | "alert"; 38 | "alienated"; 39 | "alive"; 40 | "all"; 41 | "altruistic"; 42 | "amazing"; 43 | "ambitious"; 44 | "ample"; 45 | "amused"; 46 | "amusing"; 47 | "anchored"; 48 | "ancient"; 49 | "angelic"; 50 | "angry"; 51 | "anguished"; 52 | "animated"; 53 | "annual"; 54 | "another"; 55 | "antique"; 56 | "anxious"; 57 | "apprehensive"; 58 | "appropriate"; 59 | "apt"; 60 | "arctic"; 61 | "arid"; 62 | "aromatic"; 63 | "artistic"; 64 | "ashamed"; 65 | "assured"; 66 | "astonishing"; 67 | "athletic"; 68 | "attached"; 69 | "attentive"; 70 | "attractive"; 71 | "austere"; 72 | "authentic"; 73 | "authorized"; 74 | "automatic"; 75 | "avaricious"; 76 | "average"; 77 | "aware"; 78 | "awesome"; 79 | "awful"; 80 | "awkward"; 81 | "babyish"; 82 | "back"; 83 | "bad"; 84 | "baggy"; 85 | "bare"; 86 | "barren"; 87 | "basic"; 88 | "beautiful"; 89 | "belated"; 90 | "beloved"; 91 | "beneficial"; 92 | "best"; 93 | "better"; 94 | "bewitched"; 95 | "big"; 96 | "bighearted"; 97 | "biodegradable"; 98 | "bitesized"; 99 | "bitter"; 100 | "black"; 101 | "blackandwhite"; 102 | "bland"; 103 | "blank"; 104 | "blaring"; 105 | "bleak"; 106 | "blind"; 107 | "blissful"; 108 | "blond"; 109 | "blue"; 110 | "blushing"; 111 | "bogus"; 112 | "boiling"; 113 | "bold"; 114 | "bony"; 115 | "boring"; 116 | "bossy"; 117 | "both"; 118 | "bouncy"; 119 | "bountiful"; 120 | "bowed"; 121 | "brave"; 122 | "breakable"; 123 | "brief"; 124 | "bright"; 125 | "brilliant"; 126 | "brisk"; 127 | "broken"; 128 | "bronze"; 129 | "brown"; 130 | "bruised"; 131 | "bubbly"; 132 | "bulky"; 133 | "bumpy"; 134 | "buoyant"; 135 | "burdensome"; 136 | "burly"; 137 | "bustling"; 138 | "busy"; 139 | "buttery"; 140 | "buzzing"; 141 | "calculating"; 142 | "calm"; 143 | "candid"; 144 | "canine"; 145 | "capital"; 146 | "carefree"; 147 | "careful"; 148 | "careless"; 149 | "caring"; 150 | "cautious"; 151 | "cavernous"; 152 | "celebrated"; 153 | "charming"; 154 | "cheap"; 155 | "cheerful"; 156 | "cheery"; 157 | "chief"; 158 | "chilly"; 159 | "chubby"; 160 | "circular"; 161 | "classic"; 162 | "clean"; 163 | "clear"; 164 | "clearcut"; 165 | "clever"; 166 | "close"; 167 | "closed"; 168 | "cloudy"; 169 | "clueless"; 170 | "clumsy"; 171 | "cluttered"; 172 | "coarse"; 173 | "cold"; 174 | "colorful"; 175 | "colorless"; 176 | "colossal"; 177 | "comfortable"; 178 | "common"; 179 | "compassionate"; 180 | "competent"; 181 | "complete"; 182 | "complex"; 183 | "complicated"; 184 | "composed"; 185 | "concerned"; 186 | "concrete"; 187 | "confused"; 188 | "conscious"; 189 | "considerate"; 190 | "constant"; 191 | "content"; 192 | "conventional"; 193 | "cooked"; 194 | "cool"; 195 | "cooperative"; 196 | "coordinated"; 197 | "corny"; 198 | "corrupt"; 199 | "costly"; 200 | "courageous"; 201 | "courteous"; 202 | "crafty"; 203 | "crazy"; 204 | "creamy"; 205 | "creative"; 206 | "creepy"; 207 | "criminal"; 208 | "crisp"; 209 | "critical"; 210 | "crooked"; 211 | "crowded"; 212 | "cruel"; 213 | "crushing"; 214 | "cuddly"; 215 | "cultivated"; 216 | "cultured"; 217 | "cumbersome"; 218 | "curly"; 219 | "curvy"; 220 | "cute"; 221 | "cylindrical"; 222 | "damaged"; 223 | "damp"; 224 | "dangerous"; 225 | "dapper"; 226 | "daring"; 227 | "dark"; 228 | "darling"; 229 | "dazzling"; 230 | "dead"; 231 | "deadly"; 232 | "deafening"; 233 | "dear"; 234 | "dearest"; 235 | "decent"; 236 | "decimal"; 237 | "decisive"; 238 | "deep"; 239 | "defenseless"; 240 | "defensive"; 241 | "defiant"; 242 | "deficient"; 243 | "definite"; 244 | "definitive"; 245 | "delayed"; 246 | "delectable"; 247 | "delicious"; 248 | "delightful"; 249 | "delirious"; 250 | "demanding"; 251 | "dense"; 252 | "dental"; 253 | "dependable"; 254 | "dependent"; 255 | "descriptive"; 256 | "deserted"; 257 | "detailed"; 258 | "determined"; 259 | "devoted"; 260 | "different"; 261 | "difficult"; 262 | "digital"; 263 | "diligent"; 264 | "dim"; 265 | "dimpled"; 266 | "dimwitted"; 267 | "direct"; 268 | "dirty"; 269 | "disastrous"; 270 | "discrete"; 271 | "disfigured"; 272 | "disguised"; 273 | "disgusting"; 274 | "dishonest"; 275 | "disloyal"; 276 | "dismal"; 277 | "dismal"; 278 | "distant"; 279 | "distant"; 280 | "distinct"; 281 | "distorted"; 282 | "dizzy"; 283 | "dopey"; 284 | "doting"; 285 | "double"; 286 | "downright"; 287 | "downright"; 288 | "drab"; 289 | "drafty"; 290 | "dramatic"; 291 | "dreary"; 292 | "dreary"; 293 | "droopy"; 294 | "dry"; 295 | "dual"; 296 | "dull"; 297 | "dutiful"; 298 | "each"; 299 | "eager"; 300 | "early"; 301 | "earnest"; 302 | "easy"; 303 | "easygoing"; 304 | "ecstatic"; 305 | "edible"; 306 | "educated"; 307 | "elaborate"; 308 | "elastic"; 309 | "elated"; 310 | "elderly"; 311 | "electric"; 312 | "elegant"; 313 | "elementary"; 314 | "elliptical"; 315 | "embarrassed"; 316 | "embellished"; 317 | "eminent"; 318 | "emotional"; 319 | "empty"; 320 | "enchanted"; 321 | "enchanting"; 322 | "energetic"; 323 | "enlightened"; 324 | "enormous"; 325 | "enraged"; 326 | "entire"; 327 | "envious"; 328 | "equal"; 329 | "equatorial"; 330 | "essential"; 331 | "esteemed"; 332 | "ethical"; 333 | "euphoric"; 334 | "even"; 335 | "evergreen"; 336 | "everlasting"; 337 | "every"; 338 | "evil"; 339 | "exalted"; 340 | "excellent"; 341 | "excitable"; 342 | "excited"; 343 | "exciting"; 344 | "exemplary"; 345 | "exhausted"; 346 | "exotic"; 347 | "expensive"; 348 | "experienced"; 349 | "expert"; 350 | "extralarge"; 351 | "extraneous"; 352 | "extrasmall"; 353 | "extroverted"; 354 | "fabulous"; 355 | "failing"; 356 | "faint"; 357 | "fair"; 358 | "faithful"; 359 | "fake"; 360 | "familiar"; 361 | "famous"; 362 | "fancy"; 363 | "fantastic"; 364 | "far"; 365 | "faraway"; 366 | "farflung"; 367 | "faroff"; 368 | "fast"; 369 | "fat"; 370 | "fatal"; 371 | "fatherly"; 372 | "favorable"; 373 | "favorite"; 374 | "fearful"; 375 | "fearless"; 376 | "feisty"; 377 | "feline"; 378 | "female"; 379 | "feminine"; 380 | "few"; 381 | "fickle"; 382 | "filthy"; 383 | "fine"; 384 | "finished"; 385 | "firm"; 386 | "first"; 387 | "firsthand"; 388 | "fitting"; 389 | "fixed"; 390 | "flaky"; 391 | "flamboyant"; 392 | "flashy"; 393 | "flat"; 394 | "flawed"; 395 | "flawless"; 396 | "flickering"; 397 | "flimsy"; 398 | "flippant"; 399 | "flowery"; 400 | "fluffy"; 401 | "fluid"; 402 | "flustered"; 403 | "focused"; 404 | "fond"; 405 | "foolhardy"; 406 | "foolish"; 407 | "forceful"; 408 | "forked"; 409 | "formal"; 410 | "forsaken"; 411 | "forthright"; 412 | "fortunate"; 413 | "fragrant"; 414 | "frail"; 415 | "frank"; 416 | "frayed"; 417 | "free"; 418 | "french"; 419 | "frequent"; 420 | "fresh"; 421 | "friendly"; 422 | "frightened"; 423 | "frightening"; 424 | "frigid"; 425 | "frilly"; 426 | "frivolous"; 427 | "frizzy"; 428 | "front"; 429 | "frosty"; 430 | "frozen"; 431 | "frugal"; 432 | "fruitful"; 433 | "full"; 434 | "fumbling"; 435 | "functional"; 436 | "funny"; 437 | "fussy"; 438 | "fuzzy"; 439 | "gargantuan"; 440 | "gaseous"; 441 | "general"; 442 | "generous"; 443 | "gentle"; 444 | "genuine"; 445 | "giant"; 446 | "giddy"; 447 | "gifted"; 448 | "gigantic"; 449 | "giving"; 450 | "glamorous"; 451 | "glaring"; 452 | "glass"; 453 | "gleaming"; 454 | "gleeful"; 455 | "glistening"; 456 | "glittering"; 457 | "gloomy"; 458 | "glorious"; 459 | "glossy"; 460 | "glum"; 461 | "golden"; 462 | "good"; 463 | "goodnatured"; 464 | "gorgeous"; 465 | "graceful"; 466 | "gracious"; 467 | "grand"; 468 | "grandiose"; 469 | "granular"; 470 | "grateful"; 471 | "grave"; 472 | "gray"; 473 | "great"; 474 | "greedy"; 475 | "green"; 476 | "gregarious"; 477 | "grim"; 478 | "grimy"; 479 | "gripping"; 480 | "grizzled"; 481 | "gross"; 482 | "grotesque"; 483 | "grouchy"; 484 | "grounded"; 485 | "growing"; 486 | "growling"; 487 | "grown"; 488 | "grubby"; 489 | "gruesome"; 490 | "grumpy"; 491 | "guilty"; 492 | "gullible"; 493 | "gummy"; 494 | "hairy"; 495 | "half"; 496 | "handmade"; 497 | "handsome"; 498 | "handy"; 499 | "happy"; 500 | "happygolucky"; 501 | "hard"; 502 | "hardtofind"; 503 | "harmful"; 504 | "harmless"; 505 | "harmonious"; 506 | "harsh"; 507 | "hasty"; 508 | "hateful"; 509 | "haunting"; 510 | "healthy"; 511 | "heartfelt"; 512 | "hearty"; 513 | "heavenly"; 514 | "heavy"; 515 | "hefty"; 516 | "helpful"; 517 | "helpless"; 518 | "hidden"; 519 | "hideous"; 520 | "high"; 521 | "highlevel"; 522 | "hilarious"; 523 | "hoarse"; 524 | "hollow"; 525 | "homely"; 526 | "honest"; 527 | "honorable"; 528 | "honored"; 529 | "hopeful"; 530 | "horrible"; 531 | "hospitable"; 532 | "hot"; 533 | "huge"; 534 | "humble"; 535 | "humiliating"; 536 | "humming"; 537 | "humongous"; 538 | "hungry"; 539 | "hurtful"; 540 | "husky"; 541 | "icky"; 542 | "icy"; 543 | "ideal"; 544 | "idealistic"; 545 | "identical"; 546 | "idiotic"; 547 | "idle"; 548 | "idolized"; 549 | "ignorant"; 550 | "ill"; 551 | "illegal"; 552 | "illfated"; 553 | "illinformed"; 554 | "illiterate"; 555 | "illustrious"; 556 | "imaginary"; 557 | "imaginative"; 558 | "immaculate"; 559 | "immaterial"; 560 | "immediate"; 561 | "immense"; 562 | "impartial"; 563 | "impassioned"; 564 | "impeccable"; 565 | "imperfect"; 566 | "imperturbable"; 567 | "impish"; 568 | "impolite"; 569 | "important"; 570 | "impossible"; 571 | "impractical"; 572 | "impressionable"; 573 | "impressive"; 574 | "improbable"; 575 | "impure"; 576 | "inborn"; 577 | "incomparable"; 578 | "incompatible"; 579 | "incomplete"; 580 | "inconsequential"; 581 | "incredible"; 582 | "indelible"; 583 | "indolent"; 584 | "inexperienced"; 585 | "infamous"; 586 | "infantile"; 587 | "infatuated"; 588 | "inferior"; 589 | "infinite"; 590 | "informal"; 591 | "innocent"; 592 | "insecure"; 593 | "insidious"; 594 | "insignificant"; 595 | "insistent"; 596 | "instructive"; 597 | "insubstantial"; 598 | "intelligent"; 599 | "intent"; 600 | "intentional"; 601 | "interesting"; 602 | "internal"; 603 | "international"; 604 | "intrepid"; 605 | "ironclad"; 606 | "irresponsible"; 607 | "irritating"; 608 | "itchy"; 609 | "jaded"; 610 | "jagged"; 611 | "jampacked"; 612 | "jaunty"; 613 | "jealous"; 614 | "jittery"; 615 | "joint"; 616 | "jolly"; 617 | "jovial"; 618 | "joyful"; 619 | "joyous"; 620 | "jubilant"; 621 | "judicious"; 622 | "juicy"; 623 | "jumbo"; 624 | "jumpy"; 625 | "junior"; 626 | "juvenile"; 627 | "kaleidoscopic"; 628 | "keen"; 629 | "key"; 630 | "kind"; 631 | "kindhearted"; 632 | "kindly"; 633 | "klutzy"; 634 | "knobby"; 635 | "knotty"; 636 | "knowing"; 637 | "knowledgeable"; 638 | "known"; 639 | "kooky"; 640 | "kosher"; 641 | "lame"; 642 | "lanky"; 643 | "large"; 644 | "last"; 645 | "lasting"; 646 | "late"; 647 | "lavish"; 648 | "lawful"; 649 | "lazy"; 650 | "leading"; 651 | "leafy"; 652 | "lean"; 653 | "left"; 654 | "legal"; 655 | "legitimate"; 656 | "light"; 657 | "lighthearted"; 658 | "likable"; 659 | "likely"; 660 | "limited"; 661 | "limp"; 662 | "limping"; 663 | "linear"; 664 | "lined"; 665 | "liquid"; 666 | "little"; 667 | "live"; 668 | "lively"; 669 | "livid"; 670 | "loathsome"; 671 | "lone"; 672 | "lonely"; 673 | "long"; 674 | "longterm"; 675 | "loose"; 676 | "lopsided"; 677 | "lost"; 678 | "loud"; 679 | "lovable"; 680 | "lovely"; 681 | "loving"; 682 | "low"; 683 | "loyal"; 684 | "lucky"; 685 | "lumbering"; 686 | "luminous"; 687 | "lumpy"; 688 | "lustrous"; 689 | "luxurious"; 690 | "mad"; 691 | "madeup"; 692 | "magnificent"; 693 | "majestic"; 694 | "major"; 695 | "male"; 696 | "mammoth"; 697 | "married"; 698 | "marvelous"; 699 | "masculine"; 700 | "massive"; 701 | "mature"; 702 | "meager"; 703 | "mealy"; 704 | "mean"; 705 | "measly"; 706 | "meaty"; 707 | "medical"; 708 | "mediocre"; 709 | "medium"; 710 | "meek"; 711 | "mellow"; 712 | "melodic"; 713 | "memorable"; 714 | "menacing"; 715 | "merry"; 716 | "messy"; 717 | "metallic"; 718 | "mild"; 719 | "milky"; 720 | "mindless"; 721 | "miniature"; 722 | "minor"; 723 | "minty"; 724 | "miserable"; 725 | "miserly"; 726 | "misguided"; 727 | "misty"; 728 | "mixed"; 729 | "modern"; 730 | "modest"; 731 | "moist"; 732 | "monstrous"; 733 | "monthly"; 734 | "monumental"; 735 | "moral"; 736 | "mortified"; 737 | "motherly"; 738 | "motionless"; 739 | "mountainous"; 740 | "muddy"; 741 | "muffled"; 742 | "multicolored"; 743 | "mundane"; 744 | "murky"; 745 | "mushy"; 746 | "musty"; 747 | "muted"; 748 | "mysterious"; 749 | "naive"; 750 | "narrow"; 751 | "nasty"; 752 | "natural"; 753 | "naughty"; 754 | "nautical"; 755 | "near"; 756 | "neat"; 757 | "necessary"; 758 | "needy"; 759 | "negative"; 760 | "neglected"; 761 | "negligible"; 762 | "neighboring"; 763 | "nervous"; 764 | "new"; 765 | "next"; 766 | "nice"; 767 | "nifty"; 768 | "nimble"; 769 | "nippy"; 770 | "nocturnal"; 771 | "noisy"; 772 | "nonstop"; 773 | "normal"; 774 | "notable"; 775 | "noted"; 776 | "noteworthy"; 777 | "novel"; 778 | "noxious"; 779 | "numb"; 780 | "nutritious"; 781 | "nutty"; 782 | "obedient"; 783 | "obese"; 784 | "oblong"; 785 | "oblong"; 786 | "obvious"; 787 | "occasional"; 788 | "odd"; 789 | "oddball"; 790 | "offbeat"; 791 | "offensive"; 792 | "official"; 793 | "oily"; 794 | "old"; 795 | "oldfashioned"; 796 | "onerlooked"; 797 | "only"; 798 | "open"; 799 | "optimal"; 800 | "optimistic"; 801 | "opulent"; 802 | "orange"; 803 | "orderly"; 804 | "ordinary"; 805 | "organic"; 806 | "original"; 807 | "ornate"; 808 | "ornery"; 809 | "other"; 810 | "our"; 811 | "outgoing"; 812 | "outlandish"; 813 | "outlying"; 814 | "outrageous"; 815 | "outstanding"; 816 | "oval"; 817 | "overcooked"; 818 | "overdue"; 819 | "overjoyed"; 820 | "palatable"; 821 | "pale"; 822 | "paltry"; 823 | "parallel"; 824 | "parched"; 825 | "partial"; 826 | "passionate"; 827 | "past"; 828 | "pastel"; 829 | "peaceful"; 830 | "peppery"; 831 | "perfect"; 832 | "perfumed"; 833 | "periodic"; 834 | "perky"; 835 | "personal"; 836 | "pertinent"; 837 | "pesky"; 838 | "pessimistic"; 839 | "petty"; 840 | "phony"; 841 | "physical"; 842 | "piercing"; 843 | "pink"; 844 | "pitiful"; 845 | "plain"; 846 | "plaintive"; 847 | "plastic"; 848 | "playful"; 849 | "pleasant"; 850 | "pleased"; 851 | "pleasing"; 852 | "plump"; 853 | "plush"; 854 | "pointed"; 855 | "pointless"; 856 | "poised"; 857 | "polished"; 858 | "polite"; 859 | "political"; 860 | "poor"; 861 | "popular"; 862 | "portly"; 863 | "posh"; 864 | "positive"; 865 | "possible"; 866 | "potable"; 867 | "powerful"; 868 | "powerless"; 869 | "practical"; 870 | "precious"; 871 | "precious"; 872 | "present"; 873 | "prestigious"; 874 | "pretty"; 875 | "previous"; 876 | "pricey"; 877 | "prickly"; 878 | "primary"; 879 | "prime"; 880 | "pristine"; 881 | "private"; 882 | "prize"; 883 | "probable"; 884 | "productive"; 885 | "profitable"; 886 | "profuse"; 887 | "proper"; 888 | "proud"; 889 | "prudent"; 890 | "punctual"; 891 | "pungent"; 892 | "puny"; 893 | "pure"; 894 | "purple"; 895 | "pushy"; 896 | "putrid"; 897 | "puzzled"; 898 | "puzzling"; 899 | "quaint"; 900 | "qualified"; 901 | "quarrelsome"; 902 | "quarterly"; 903 | "queasy"; 904 | "querulous"; 905 | "questionable"; 906 | "quick"; 907 | "quickwitted"; 908 | "quiet"; 909 | "quintessential"; 910 | "quirky"; 911 | "quixotic"; 912 | "quizzical"; 913 | "radiant"; 914 | "ragged"; 915 | "rapid"; 916 | "rare"; 917 | "rash"; 918 | "raw"; 919 | "ready"; 920 | "real"; 921 | "realistic"; 922 | "reasonable"; 923 | "recent"; 924 | "reckless"; 925 | "rectangular"; 926 | "red"; 927 | "reflecting"; 928 | "regal"; 929 | "regular"; 930 | "reliable"; 931 | "relieved"; 932 | "remarkable"; 933 | "remorseful"; 934 | "remote"; 935 | "repentant"; 936 | "repulsive"; 937 | "required"; 938 | "respectful"; 939 | "responsible"; 940 | "revolving"; 941 | "rewarding"; 942 | "rich"; 943 | "right"; 944 | "rigid"; 945 | "ringed"; 946 | "ripe"; 947 | "roasted"; 948 | "robust"; 949 | "rosy"; 950 | "rotating"; 951 | "rotten"; 952 | "rough"; 953 | "round"; 954 | "rowdy"; 955 | "royal"; 956 | "rubbery"; 957 | "ruddy"; 958 | "rude"; 959 | "rundown"; 960 | "runny"; 961 | "rural"; 962 | "rusty"; 963 | "sad"; 964 | "safe"; 965 | "salty"; 966 | "same"; 967 | "sandy"; 968 | "sane"; 969 | "sarcastic"; 970 | "sardonic"; 971 | "satisfied"; 972 | "scaly"; 973 | "scarce"; 974 | "scared"; 975 | "scary"; 976 | "scented"; 977 | "scholarly"; 978 | "scientific"; 979 | "scornful"; 980 | "scratchy"; 981 | "scrawny"; 982 | "second"; 983 | "secondary"; 984 | "secondhand"; 985 | "secret"; 986 | "selfassured"; 987 | "selfish"; 988 | "selfreliant"; 989 | "sentimental"; 990 | "separate"; 991 | "serene"; 992 | "serious"; 993 | "serpentine"; 994 | "several"; 995 | "severe"; 996 | "shabby"; 997 | "shadowy"; 998 | "shady"; 999 | "shallow"; 1000 | "shameful"; 1001 | "shameless"; 1002 | "sharp"; 1003 | "shimmering"; 1004 | "shiny"; 1005 | "shocked"; 1006 | "shocking"; 1007 | "shoddy"; 1008 | "short"; 1009 | "shortterm"; 1010 | "showy"; 1011 | "shrill"; 1012 | "shy"; 1013 | "sick"; 1014 | "silent"; 1015 | "silky"; 1016 | "silly"; 1017 | "silver"; 1018 | "similar"; 1019 | "simple"; 1020 | "simplistic"; 1021 | "sinful"; 1022 | "single"; 1023 | "sizzling"; 1024 | "skeletal"; 1025 | "skinny"; 1026 | "sleepy"; 1027 | "slight"; 1028 | "slim"; 1029 | "slimy"; 1030 | "slippery"; 1031 | "slow"; 1032 | "slushy"; 1033 | "small"; 1034 | "smart"; 1035 | "smoggy"; 1036 | "smooth"; 1037 | "smug"; 1038 | "snappy"; 1039 | "snarling"; 1040 | "sneaky"; 1041 | "sniveling"; 1042 | "snoopy"; 1043 | "sociable"; 1044 | "soft"; 1045 | "soggy"; 1046 | "solid"; 1047 | "somber"; 1048 | "some"; 1049 | "sophisticated"; 1050 | "sore"; 1051 | "sorrowful"; 1052 | "soulful"; 1053 | "soupy"; 1054 | "sour"; 1055 | "spanish"; 1056 | "sparkling"; 1057 | "sparse"; 1058 | "specific"; 1059 | "spectacular"; 1060 | "speedy"; 1061 | "spherical"; 1062 | "spicy"; 1063 | "spiffy"; 1064 | "spirited"; 1065 | "spiteful"; 1066 | "splendid"; 1067 | "spotless"; 1068 | "spotted"; 1069 | "spry"; 1070 | "square"; 1071 | "squeaky"; 1072 | "squiggly"; 1073 | "stable"; 1074 | "staid"; 1075 | "stained"; 1076 | "stale"; 1077 | "standard"; 1078 | "starchy"; 1079 | "stark"; 1080 | "starry"; 1081 | "steel"; 1082 | "steep"; 1083 | "sticky"; 1084 | "stiff"; 1085 | "stimulating"; 1086 | "stingy"; 1087 | "stormy"; 1088 | "straight"; 1089 | "strange"; 1090 | "strict"; 1091 | "strident"; 1092 | "striking"; 1093 | "striped"; 1094 | "strong"; 1095 | "studious"; 1096 | "stunning"; 1097 | "stupendous"; 1098 | "stupid"; 1099 | "sturdy"; 1100 | "stylish"; 1101 | "subdued"; 1102 | "submissive"; 1103 | "substantial"; 1104 | "subtle"; 1105 | "suburban"; 1106 | "sudden"; 1107 | "sugary"; 1108 | "sunny"; 1109 | "super"; 1110 | "superb"; 1111 | "superficial"; 1112 | "superior"; 1113 | "supportive"; 1114 | "surefooted"; 1115 | "surprised"; 1116 | "suspicious"; 1117 | "svelte"; 1118 | "sweaty"; 1119 | "sweet"; 1120 | "sweltering"; 1121 | "swift"; 1122 | "sympathetic"; 1123 | "talkative"; 1124 | "tall"; 1125 | "tame"; 1126 | "tan"; 1127 | "tangible"; 1128 | "tart"; 1129 | "tasty"; 1130 | "tattered"; 1131 | "taut"; 1132 | "tedious"; 1133 | "teeming"; 1134 | "tempting"; 1135 | "tender"; 1136 | "tense"; 1137 | "tepid"; 1138 | "terrible"; 1139 | "terrific"; 1140 | "testy"; 1141 | "thankful"; 1142 | "that"; 1143 | "these"; 1144 | "thick"; 1145 | "thin"; 1146 | "third"; 1147 | "thirsty"; 1148 | "this"; 1149 | "thorny"; 1150 | "thorough"; 1151 | "those"; 1152 | "thoughtful"; 1153 | "threadbare"; 1154 | "thrifty"; 1155 | "thunderous"; 1156 | "tidy"; 1157 | "tight"; 1158 | "timely"; 1159 | "tinted"; 1160 | "tiny"; 1161 | "tired"; 1162 | "torn"; 1163 | "total"; 1164 | "tough"; 1165 | "tragic"; 1166 | "trained"; 1167 | "traumatic"; 1168 | "treasured"; 1169 | "tremendous"; 1170 | "tremendous"; 1171 | "triangular"; 1172 | "tricky"; 1173 | "trifling"; 1174 | "trim"; 1175 | "trivial"; 1176 | "troubled"; 1177 | "trusting"; 1178 | "trustworthy"; 1179 | "trusty"; 1180 | "truthful"; 1181 | "tubby"; 1182 | "turbulent"; 1183 | "twin"; 1184 | "ugly"; 1185 | "ultimate"; 1186 | "unacceptable"; 1187 | "unaware"; 1188 | "uncomfortable"; 1189 | "uncommon"; 1190 | "unconscious"; 1191 | "understated"; 1192 | "unequaled"; 1193 | "uneven"; 1194 | "unfinished"; 1195 | "unfit"; 1196 | "unfolded"; 1197 | "unfortunate"; 1198 | "unhappy"; 1199 | "unhealthy"; 1200 | "uniform"; 1201 | "unimportant"; 1202 | "unique"; 1203 | "united"; 1204 | "unkempt"; 1205 | "unknown"; 1206 | "unlawful"; 1207 | "unlined"; 1208 | "unlucky"; 1209 | "unnatural"; 1210 | "unpleasant"; 1211 | "unrealistic"; 1212 | "unripe"; 1213 | "unruly"; 1214 | "unselfish"; 1215 | "unsightly"; 1216 | "unsteady"; 1217 | "unsung"; 1218 | "untidy"; 1219 | "untimely"; 1220 | "untried"; 1221 | "untrue"; 1222 | "unused"; 1223 | "unusual"; 1224 | "unwelcome"; 1225 | "unwieldy"; 1226 | "unwilling"; 1227 | "unwitting"; 1228 | "unwritten"; 1229 | "upbeat"; 1230 | "upright"; 1231 | "upset"; 1232 | "urban"; 1233 | "usable"; 1234 | "used"; 1235 | "useful"; 1236 | "useless"; 1237 | "utilized"; 1238 | "utter"; 1239 | "vacant"; 1240 | "vague"; 1241 | "vain"; 1242 | "valid"; 1243 | "valuable"; 1244 | "vapid"; 1245 | "variable"; 1246 | "vast"; 1247 | "velvety"; 1248 | "venerated"; 1249 | "vengeful"; 1250 | "verifiable"; 1251 | "vibrant"; 1252 | "vicious"; 1253 | "victorious"; 1254 | "vigilant"; 1255 | "vigorous"; 1256 | "villainous"; 1257 | "violent"; 1258 | "violet"; 1259 | "virtual"; 1260 | "virtuous"; 1261 | "visible"; 1262 | "vital"; 1263 | "vivacious"; 1264 | "vivid"; 1265 | "voluminous"; 1266 | "wan"; 1267 | "warlike"; 1268 | "warm"; 1269 | "warmhearted"; 1270 | "warped"; 1271 | "wary"; 1272 | "wasteful"; 1273 | "watchful"; 1274 | "waterlogged"; 1275 | "watery"; 1276 | "wavy"; 1277 | "weak"; 1278 | "wealthy"; 1279 | "weary"; 1280 | "webbed"; 1281 | "wee"; 1282 | "weekly"; 1283 | "weepy"; 1284 | "weighty"; 1285 | "weird"; 1286 | "welcome"; 1287 | "welldocumented"; 1288 | "wellgroomed"; 1289 | "wellinformed"; 1290 | "welllit"; 1291 | "wellmade"; 1292 | "welloff"; 1293 | "welltodo"; 1294 | "wellworn"; 1295 | "wet"; 1296 | "which"; 1297 | "whimsical"; 1298 | "whirlwind"; 1299 | "whispered"; 1300 | "white"; 1301 | "whole"; 1302 | "whopping"; 1303 | "wicked"; 1304 | "wide"; 1305 | "wideeyed"; 1306 | "wiggly"; 1307 | "wild"; 1308 | "willing"; 1309 | "wilted"; 1310 | "winding"; 1311 | "windy"; 1312 | "winged"; 1313 | "wiry"; 1314 | "wise"; 1315 | "witty"; 1316 | "wobbly"; 1317 | "woeful"; 1318 | "wonderful"; 1319 | "wooden"; 1320 | "woozy"; 1321 | "wordy"; 1322 | "worldly"; 1323 | "worn"; 1324 | "worried"; 1325 | "worrisome"; 1326 | "worse"; 1327 | "worst"; 1328 | "worthless"; 1329 | "worthwhile"; 1330 | "worthy"; 1331 | "wrathful"; 1332 | "wretched"; 1333 | "writhing"; 1334 | "wrong"; 1335 | "wry"; 1336 | "yawning"; 1337 | "yearly"; 1338 | "yellow"; 1339 | "yellowish"; 1340 | "young"; 1341 | "youthful"; 1342 | "yummy"; 1343 | "zany"; 1344 | "zealous"; 1345 | "zesty"; 1346 | "zigzag"; 1347 | ] 1348 | 1349 | let animals = 1350 | [ 1351 | "aardvark"; 1352 | "aardwolf"; 1353 | "abalone"; 1354 | "abyssiniancat"; 1355 | "abyssiniangroundhornbill"; 1356 | "acaciarat"; 1357 | "achillestang"; 1358 | "acornbarnacle"; 1359 | "acornweevil"; 1360 | "acornwoodpecker"; 1361 | "acouchi"; 1362 | "adamsstaghornedbeetle"; 1363 | "addax"; 1364 | "adder"; 1365 | "adeliepenguin"; 1366 | "admiralbutterfly"; 1367 | "adouri"; 1368 | "aegeancat"; 1369 | "affenpinscher"; 1370 | "afghanhound"; 1371 | "africanaugurbuzzard"; 1372 | "africanbushviper"; 1373 | "africancivet"; 1374 | "africanclawedfrog"; 1375 | "africanelephant"; 1376 | "africanfisheagle"; 1377 | "africangoldencat"; 1378 | "africangroundhornbill"; 1379 | "africanharrierhawk"; 1380 | "africanhornbill"; 1381 | "africanjacana"; 1382 | "africanmolesnake"; 1383 | "africanparadiseflycatcher"; 1384 | "africanpiedkingfisher"; 1385 | "africanporcupine"; 1386 | "africanrockpython"; 1387 | "africanwildcat"; 1388 | "africanwilddog"; 1389 | "agama"; 1390 | "agouti"; 1391 | "aidi"; 1392 | "airedale"; 1393 | "airedaleterrier"; 1394 | "akitainu"; 1395 | "alabamamapturtle"; 1396 | "alaskajingle"; 1397 | "alaskanhusky"; 1398 | "alaskankleekai"; 1399 | "alaskanmalamute"; 1400 | "albacoretuna"; 1401 | "albatross"; 1402 | "albertosaurus"; 1403 | "albino"; 1404 | "aldabratortoise"; 1405 | "allensbigearedbat"; 1406 | "alleycat"; 1407 | "alligator"; 1408 | "alligatorgar"; 1409 | "alligatorsnappingturtle"; 1410 | "allosaurus"; 1411 | "alpaca"; 1412 | "alpinegoat"; 1413 | "alpineroadguidetigerbeetle"; 1414 | "altiplanochinchillamouse"; 1415 | "amazondolphin"; 1416 | "amazonparrot"; 1417 | "amazontreeboa"; 1418 | "amberpenshell"; 1419 | "ambushbug"; 1420 | "americanalligator"; 1421 | "americanavocet"; 1422 | "americanbadger"; 1423 | "americanbittern"; 1424 | "americanblackvulture"; 1425 | "americanbobtail"; 1426 | "americanbulldog"; 1427 | "americancicada"; 1428 | "americancrayfish"; 1429 | "americancreamdraft"; 1430 | "americancrocodile"; 1431 | "americancrow"; 1432 | "americancurl"; 1433 | "americangoldfinch"; 1434 | "americanindianhorse"; 1435 | "americankestrel"; 1436 | "americanlobster"; 1437 | "americanmarten"; 1438 | "americanpainthorse"; 1439 | "americanquarterhorse"; 1440 | "americanratsnake"; 1441 | "americanredsquirrel"; 1442 | "americanriverotter"; 1443 | "americanrobin"; 1444 | "americansaddlebred"; 1445 | "americanshorthair"; 1446 | "americantoad"; 1447 | "americanwarmblood"; 1448 | "americanwigeon"; 1449 | "americanwirehair"; 1450 | "amethystgemclam"; 1451 | "amethystinepython"; 1452 | "amethystsunbird"; 1453 | "ammonite"; 1454 | "amoeba"; 1455 | "amphibian"; 1456 | "amphiuma"; 1457 | "amurminnow"; 1458 | "amurratsnake"; 1459 | "amurstarfish"; 1460 | "anaconda"; 1461 | "anchovy"; 1462 | "andalusianhorse"; 1463 | "andeancat"; 1464 | "andeancockoftherock"; 1465 | "andeancondor"; 1466 | "anemone"; 1467 | "anemonecrab"; 1468 | "anemoneshrimp"; 1469 | "angelfish"; 1470 | "angelwingmussel"; 1471 | "anglerfish"; 1472 | "angora"; 1473 | "angwantibo"; 1474 | "anhinga"; 1475 | "ankole"; 1476 | "ankolewatusi"; 1477 | "annashummingbird"; 1478 | "annelid"; 1479 | "annelida"; 1480 | "anole"; 1481 | "anophelesmosquito"; 1482 | "ant"; 1483 | "antarcticfurseal"; 1484 | "antarcticgiantpetrel"; 1485 | "antbear"; 1486 | "anteater"; 1487 | "antelope"; 1488 | "antelopegroundsquirrel"; 1489 | "antipodesgreenparakeet"; 1490 | "antlion"; 1491 | "anura"; 1492 | "aoudad"; 1493 | "apatosaur"; 1494 | "ape"; 1495 | "aphid"; 1496 | "apisdorsatalaboriosa"; 1497 | "aplomadofalcon"; 1498 | "appaloosa"; 1499 | "aquaticleech"; 1500 | "arabianhorse"; 1501 | "arabianoryx"; 1502 | "arabianwildcat"; 1503 | "aracari"; 1504 | "arachnid"; 1505 | "arawana"; 1506 | "archaeocete"; 1507 | "archaeopteryx"; 1508 | "archerfish"; 1509 | "arcticduck"; 1510 | "arcticfox"; 1511 | "arctichare"; 1512 | "arcticseal"; 1513 | "arcticwolf"; 1514 | "argali"; 1515 | "argentinehornedfrog"; 1516 | "argentineruddyduck"; 1517 | "argusfish"; 1518 | "arieltoucan"; 1519 | "arizonaalligatorlizard"; 1520 | "arkshell"; 1521 | "armadillo"; 1522 | "armedcrab"; 1523 | "armednylonshrimp"; 1524 | "armyant"; 1525 | "armyworm"; 1526 | "arrowana"; 1527 | "arrowcrab"; 1528 | "arrowworm"; 1529 | "arthropods"; 1530 | "aruanas"; 1531 | "asianconstablebutterfly"; 1532 | "asiandamselfly"; 1533 | "asianelephant"; 1534 | "asianlion"; 1535 | "asianpiedstarling"; 1536 | "asianporcupine"; 1537 | "asiansmallclawedotter"; 1538 | "asiantrumpetfish"; 1539 | "asianwaterbuffalo"; 1540 | "asiaticgreaterfreshwaterclam"; 1541 | "asiaticlesserfreshwaterclam"; 1542 | "asiaticmouflon"; 1543 | "asiaticwildass"; 1544 | "asp"; 1545 | "ass"; 1546 | "assassinbug"; 1547 | "astarte"; 1548 | "astrangiacoral"; 1549 | "atlanticblackgoby"; 1550 | "atlanticbluetang"; 1551 | "atlanticridleyturtle"; 1552 | "atlanticsharpnosepuffer"; 1553 | "atlanticspadefish"; 1554 | "atlasmoth"; 1555 | "attwatersprairiechicken"; 1556 | "auk"; 1557 | "auklet"; 1558 | "aurochs"; 1559 | "australiancattledog"; 1560 | "australiancurlew"; 1561 | "australianfreshwatercrocodile"; 1562 | "australianfurseal"; 1563 | "australiankelpie"; 1564 | "australiankestrel"; 1565 | "australianshelduck"; 1566 | "australiansilkyterrier"; 1567 | "austrianpinscher"; 1568 | "avians"; 1569 | "avocet"; 1570 | "axisdeer"; 1571 | "axolotl"; 1572 | "ayeaye"; 1573 | "aztecant"; 1574 | "azurevase"; 1575 | "azurevasesponge"; 1576 | "azurewingedmagpie"; 1577 | "babirusa"; 1578 | "baboon"; 1579 | "backswimmer"; 1580 | "bactrian"; 1581 | "badger"; 1582 | "bagworm"; 1583 | "baiji"; 1584 | "baldeagle"; 1585 | "baleenwhale"; 1586 | "balloonfish"; 1587 | "ballpython"; 1588 | "bandicoot"; 1589 | "bangeltiger"; 1590 | "bantamrooster"; 1591 | "banteng"; 1592 | "barasinga"; 1593 | "barasingha"; 1594 | "barb"; 1595 | "barbet"; 1596 | "barebirdbat"; 1597 | "barnacle"; 1598 | "barnowl"; 1599 | "barnswallow"; 1600 | "barracuda"; 1601 | "basenji"; 1602 | "basil"; 1603 | "basilisk"; 1604 | "bass"; 1605 | "bassethound"; 1606 | "bat"; 1607 | "bats"; 1608 | "beagle"; 1609 | "bear"; 1610 | "beardedcollie"; 1611 | "beardeddragon"; 1612 | "beauceron"; 1613 | "beaver"; 1614 | "bedbug"; 1615 | "bedlingtonterrier"; 1616 | "bee"; 1617 | "beetle"; 1618 | "bellfrog"; 1619 | "bellsnake"; 1620 | "belugawhale"; 1621 | "bengaltiger"; 1622 | "bergerpicard"; 1623 | "bernesemountaindog"; 1624 | "betafish"; 1625 | "bettong"; 1626 | "bichonfrise"; 1627 | "bighorn"; 1628 | "bighornedsheep"; 1629 | "bighornsheep"; 1630 | "bigmouthbass"; 1631 | "bilby"; 1632 | "billygoat"; 1633 | "binturong"; 1634 | "bird"; 1635 | "birdofparadise"; 1636 | "bison"; 1637 | "bittern"; 1638 | "blackandtancoonhound"; 1639 | "blackbear"; 1640 | "blackbird"; 1641 | "blackbuck"; 1642 | "blackcrappie"; 1643 | "blackfish"; 1644 | "blackfly"; 1645 | "blackfootedferret"; 1646 | "blacklab"; 1647 | "blacklemur"; 1648 | "blackmamba"; 1649 | "blacknorwegianelkhound"; 1650 | "blackpanther"; 1651 | "blackrhino"; 1652 | "blackrussianterrier"; 1653 | "blackwidowspider"; 1654 | "blesbok"; 1655 | "blobfish"; 1656 | "blowfish"; 1657 | "blueandgoldmackaw"; 1658 | "bluebird"; 1659 | "bluebottle"; 1660 | "bluebottlejellyfish"; 1661 | "bluebreastedkookaburra"; 1662 | "bluefintuna"; 1663 | "bluefish"; 1664 | "bluegill"; 1665 | "bluejay"; 1666 | "bluemorphobutterfly"; 1667 | "blueshark"; 1668 | "bluet"; 1669 | "bluetickcoonhound"; 1670 | "bluetonguelizard"; 1671 | "bluewhale"; 1672 | "boa"; 1673 | "boaconstrictor"; 1674 | "boar"; 1675 | "bobcat"; 1676 | "bobolink"; 1677 | "bobwhite"; 1678 | "boilweevil"; 1679 | "bongo"; 1680 | "bonobo"; 1681 | "booby"; 1682 | "bordercollie"; 1683 | "borderterrier"; 1684 | "borer"; 1685 | "borzoi"; 1686 | "boto"; 1687 | "boubou"; 1688 | "boutu"; 1689 | "bovine"; 1690 | "brahmanbull"; 1691 | "brahmancow"; 1692 | "brant"; 1693 | "bream"; 1694 | "brocketdeer"; 1695 | "bronco"; 1696 | "brontosaurus"; 1697 | "brownbear"; 1698 | "brownbutterfly"; 1699 | "bubblefish"; 1700 | "buck"; 1701 | "buckeyebutterfly"; 1702 | "budgie"; 1703 | "bufeo"; 1704 | "buffalo"; 1705 | "bufflehead"; 1706 | "bug"; 1707 | "bull"; 1708 | "bullfrog"; 1709 | "bullmastiff"; 1710 | "bumblebee"; 1711 | "bunny"; 1712 | "bunting"; 1713 | "burro"; 1714 | "bushbaby"; 1715 | "bushsqueaker"; 1716 | "bustard"; 1717 | "butterfly"; 1718 | "buzzard"; 1719 | "caecilian"; 1720 | "caiman"; 1721 | "caimanlizard"; 1722 | "calf"; 1723 | "camel"; 1724 | "canadagoose"; 1725 | "canary"; 1726 | "canine"; 1727 | "canvasback"; 1728 | "capeghostfrog"; 1729 | "capybara"; 1730 | "caracal"; 1731 | "cardinal"; 1732 | "caribou"; 1733 | "carp"; 1734 | "carpenterant"; 1735 | "cassowary"; 1736 | "cat"; 1737 | "catbird"; 1738 | "caterpillar"; 1739 | "catfish"; 1740 | "cats"; 1741 | "cattle"; 1742 | "caudata"; 1743 | "cavy"; 1744 | "centipede"; 1745 | "cero"; 1746 | "chafer"; 1747 | "chameleon"; 1748 | "chamois"; 1749 | "chanticleer"; 1750 | "cheetah"; 1751 | "chevrotain"; 1752 | "chick"; 1753 | "chickadee"; 1754 | "chicken"; 1755 | "chihuahua"; 1756 | "chimneyswift"; 1757 | "chimpanzee"; 1758 | "chinchilla"; 1759 | "chinesecrocodilelizard"; 1760 | "chipmunk"; 1761 | "chital"; 1762 | "chrysalis"; 1763 | "chrysomelid"; 1764 | "chuckwalla"; 1765 | "chupacabra"; 1766 | "cicada"; 1767 | "cirriped"; 1768 | "civet"; 1769 | "clam"; 1770 | "cleanerwrasse"; 1771 | "clingfish"; 1772 | "clownanemonefish"; 1773 | "clumber"; 1774 | "coati"; 1775 | "cob"; 1776 | "cobra"; 1777 | "cock"; 1778 | "cockatiel"; 1779 | "cockatoo"; 1780 | "cockerspaniel"; 1781 | "cockroach"; 1782 | "cod"; 1783 | "coelacanth"; 1784 | "collardlizard"; 1785 | "collie"; 1786 | "colt"; 1787 | "comet"; 1788 | "commabutterfly"; 1789 | "commongonolek"; 1790 | "conch"; 1791 | "condor"; 1792 | "coney"; 1793 | "conure"; 1794 | "cony"; 1795 | "coot"; 1796 | "cooter"; 1797 | "copepod"; 1798 | "copperbutterfly"; 1799 | "copperhead"; 1800 | "coqui"; 1801 | "coral"; 1802 | "cormorant"; 1803 | "cornsnake"; 1804 | "corydorascatfish"; 1805 | "cottonmouth"; 1806 | "cottontail"; 1807 | "cougar"; 1808 | "cow"; 1809 | "cowbird"; 1810 | "cowrie"; 1811 | "coyote"; 1812 | "coypu"; 1813 | "crab"; 1814 | "crane"; 1815 | "cranefly"; 1816 | "crayfish"; 1817 | "creature"; 1818 | "cricket"; 1819 | "crocodile"; 1820 | "crocodileskink"; 1821 | "crossbill"; 1822 | "crow"; 1823 | "crownofthornsstarfish"; 1824 | "crustacean"; 1825 | "cub"; 1826 | "cuckoo"; 1827 | "cur"; 1828 | "curassow"; 1829 | "curlew"; 1830 | "cuscus"; 1831 | "cusimanse"; 1832 | "cuttlefish"; 1833 | "cutworm"; 1834 | "cygnet"; 1835 | "dachshund"; 1836 | "daddylonglegs"; 1837 | "dairycow"; 1838 | "dalmatian"; 1839 | "damselfly"; 1840 | "danishswedishfarmdog"; 1841 | "darklingbeetle"; 1842 | "dartfrog"; 1843 | "darwinsfox"; 1844 | "dassie"; 1845 | "dassierat"; 1846 | "davidstiger"; 1847 | "deer"; 1848 | "deermouse"; 1849 | "degu"; 1850 | "degus"; 1851 | "deinonychus"; 1852 | "desertpupfish"; 1853 | "devilfish"; 1854 | "deviltasmanian"; 1855 | "diamondbackrattlesnake"; 1856 | "dikdik"; 1857 | "dikkops"; 1858 | "dingo"; 1859 | "dinosaur"; 1860 | "diplodocus"; 1861 | "dipper"; 1862 | "discus"; 1863 | "dobermanpinscher"; 1864 | "doctorfish"; 1865 | "dodo"; 1866 | "dodobird"; 1867 | "doe"; 1868 | "dog"; 1869 | "dogfish"; 1870 | "dogwoodclubgall"; 1871 | "dogwoodtwigborer"; 1872 | "dolphin"; 1873 | "donkey"; 1874 | "dorado"; 1875 | "dore"; 1876 | "dorking"; 1877 | "dormouse"; 1878 | "dotterel"; 1879 | "douglasfirbarkbeetle"; 1880 | "dove"; 1881 | "dowitcher"; 1882 | "drafthorse"; 1883 | "dragon"; 1884 | "dragonfly"; 1885 | "drake"; 1886 | "drever"; 1887 | "dromaeosaur"; 1888 | "dromedary"; 1889 | "drongo"; 1890 | "duck"; 1891 | "duckbillcat"; 1892 | "duckbillplatypus"; 1893 | "duckling"; 1894 | "dugong"; 1895 | "duiker"; 1896 | "dungbeetle"; 1897 | "dungenesscrab"; 1898 | "dunlin"; 1899 | "dunnart"; 1900 | "dutchshepherddog"; 1901 | "dutchsmoushond"; 1902 | "dwarfmongoose"; 1903 | "dwarfrabbit"; 1904 | "eagle"; 1905 | "earthworm"; 1906 | "earwig"; 1907 | "easternglasslizard"; 1908 | "easternnewt"; 1909 | "easteuropeanshepherd"; 1910 | "eastrussiancoursinghounds"; 1911 | "eastsiberianlaika"; 1912 | "echidna"; 1913 | "eel"; 1914 | "eelelephant"; 1915 | "eeve"; 1916 | "eft"; 1917 | "egg"; 1918 | "egret"; 1919 | "eider"; 1920 | "eidolonhelvum"; 1921 | "ekaltadeta"; 1922 | "eland"; 1923 | "electriceel"; 1924 | "elephant"; 1925 | "elephantbeetle"; 1926 | "elephantseal"; 1927 | "elk"; 1928 | "elkhound"; 1929 | "elver"; 1930 | "emeraldtreeskink"; 1931 | "emperorpenguin"; 1932 | "emperorshrimp"; 1933 | "emu"; 1934 | "englishpointer"; 1935 | "englishsetter"; 1936 | "equestrian"; 1937 | "equine"; 1938 | "erin"; 1939 | "ermine"; 1940 | "erne"; 1941 | "eskimodog"; 1942 | "esok"; 1943 | "estuarinecrocodile"; 1944 | "ethiopianwolf"; 1945 | "europeanfiresalamander"; 1946 | "europeanpolecat"; 1947 | "ewe"; 1948 | "eyas"; 1949 | "eyelashpitviper"; 1950 | "eyra"; 1951 | "fairybluebird"; 1952 | "fairyfly"; 1953 | "falcon"; 1954 | "fallowdeer"; 1955 | "fantail"; 1956 | "fanworms"; 1957 | "fattaileddunnart"; 1958 | "fawn"; 1959 | "feline"; 1960 | "fennecfox"; 1961 | "ferret"; 1962 | "fiddlercrab"; 1963 | "fieldmouse"; 1964 | "fieldspaniel"; 1965 | "finch"; 1966 | "finnishspitz"; 1967 | "finwhale"; 1968 | "fireant"; 1969 | "firebelliedtoad"; 1970 | "firecrest"; 1971 | "firefly"; 1972 | "fish"; 1973 | "fishingcat"; 1974 | "flamingo"; 1975 | "flatcoatretriever"; 1976 | "flatfish"; 1977 | "flea"; 1978 | "flee"; 1979 | "flicker"; 1980 | "flickertailsquirrel"; 1981 | "flies"; 1982 | "flounder"; 1983 | "fluke"; 1984 | "fly"; 1985 | "flycatcher"; 1986 | "flyingfish"; 1987 | "flyingfox"; 1988 | "flyinglemur"; 1989 | "flyingsquirrel"; 1990 | "foal"; 1991 | "fossa"; 1992 | "fowl"; 1993 | "fox"; 1994 | "foxhound"; 1995 | "foxterrier"; 1996 | "frenchbulldog"; 1997 | "freshwatereel"; 1998 | "frigatebird"; 1999 | "frilledlizard"; 2000 | "frillneckedlizard"; 2001 | "fritillarybutterfly"; 2002 | "frog"; 2003 | "frogmouth"; 2004 | "fruitbat"; 2005 | "fruitfly"; 2006 | "fugu"; 2007 | "fulmar"; 2008 | "funnelweaverspider"; 2009 | "furseal"; 2010 | "gadwall"; 2011 | "galago"; 2012 | "galah"; 2013 | "galapagosalbatross"; 2014 | "galapagosdove"; 2015 | "galapagoshawk"; 2016 | "galapagosmockingbird"; 2017 | "galapagospenguin"; 2018 | "galapagossealion"; 2019 | "galapagostortoise"; 2020 | "gallinule"; 2021 | "gallowaycow"; 2022 | "gander"; 2023 | "gangesdolphin"; 2024 | "gannet"; 2025 | "gar"; 2026 | "gardensnake"; 2027 | "garpike"; 2028 | "gartersnake"; 2029 | "gaur"; 2030 | "gavial"; 2031 | "gazelle"; 2032 | "gecko"; 2033 | "geese"; 2034 | "gelada"; 2035 | "gelding"; 2036 | "gemsbok"; 2037 | "gemsbuck"; 2038 | "genet"; 2039 | "gentoopenguin"; 2040 | "gerbil"; 2041 | "gerenuk"; 2042 | "germanpinscher"; 2043 | "germanshepherd"; 2044 | "germanshorthairedpointer"; 2045 | "germanspaniel"; 2046 | "germanspitz"; 2047 | "germanwirehairedpointer"; 2048 | "gharial"; 2049 | "ghostshrimp"; 2050 | "giantschnauzer"; 2051 | "gibbon"; 2052 | "gilamonster"; 2053 | "giraffe"; 2054 | "glassfrog"; 2055 | "globefish"; 2056 | "glowworm"; 2057 | "gnat"; 2058 | "gnatcatcher"; 2059 | "gnu"; 2060 | "goa"; 2061 | "goat"; 2062 | "godwit"; 2063 | "goitered"; 2064 | "goldeneye"; 2065 | "goldenmantledgroundsquirrel"; 2066 | "goldenretriever"; 2067 | "goldfinch"; 2068 | "goldfish"; 2069 | "gonolek"; 2070 | "goose"; 2071 | "goosefish"; 2072 | "gopher"; 2073 | "goral"; 2074 | "gordonsetter"; 2075 | "gorilla"; 2076 | "goshawk"; 2077 | "gosling"; 2078 | "gossamerwingedbutterfly"; 2079 | "gourami"; 2080 | "grackle"; 2081 | "grasshopper"; 2082 | "grassspider"; 2083 | "grayfox"; 2084 | "grayling"; 2085 | "grayreefshark"; 2086 | "graysquirrel"; 2087 | "graywolf"; 2088 | "greatargus"; 2089 | "greatdane"; 2090 | "greathornedowl"; 2091 | "greatwhiteshark"; 2092 | "grebe"; 2093 | "greendarnerdragonfly"; 2094 | "greyhounddog"; 2095 | "grison"; 2096 | "grizzlybear"; 2097 | "grosbeak"; 2098 | "groundbeetle"; 2099 | "groundhog"; 2100 | "grouper"; 2101 | "grouse"; 2102 | "grub"; 2103 | "grunion"; 2104 | "guanaco"; 2105 | "guernseycow"; 2106 | "guillemot"; 2107 | "guineafowl"; 2108 | "guineapig"; 2109 | "gull"; 2110 | "guppy"; 2111 | "gypsymoth"; 2112 | "gyrfalcon"; 2113 | "hackee"; 2114 | "haddock"; 2115 | "hadrosaurus"; 2116 | "hagfish"; 2117 | "hairstreak"; 2118 | "hairstreakbutterfly"; 2119 | "hake"; 2120 | "halcyon"; 2121 | "halibut"; 2122 | "halicore"; 2123 | "hamadryad"; 2124 | "hamadryas"; 2125 | "hammerheadbird"; 2126 | "hammerheadshark"; 2127 | "hammerkop"; 2128 | "hamster"; 2129 | "hanumanmonkey"; 2130 | "hapuka"; 2131 | "hapuku"; 2132 | "harborporpoise"; 2133 | "harborseal"; 2134 | "hare"; 2135 | "harlequinbug"; 2136 | "harpseal"; 2137 | "harpyeagle"; 2138 | "harrier"; 2139 | "harrierhawk"; 2140 | "hart"; 2141 | "hartebeest"; 2142 | "harvestmen"; 2143 | "harvestmouse"; 2144 | "hatchetfish"; 2145 | "hawaiianmonkseal"; 2146 | "hawk"; 2147 | "hectorsdolphin"; 2148 | "hedgehog"; 2149 | "heifer"; 2150 | "hellbender"; 2151 | "hen"; 2152 | "herald"; 2153 | "herculesbeetle"; 2154 | "hermitcrab"; 2155 | "heron"; 2156 | "herring"; 2157 | "heterodontosaurus"; 2158 | "hind"; 2159 | "hippopotamus"; 2160 | "hoatzin"; 2161 | "hochstettersfrog"; 2162 | "hog"; 2163 | "hogget"; 2164 | "hoiho"; 2165 | "hoki"; 2166 | "homalocephale"; 2167 | "honeybadger"; 2168 | "honeybee"; 2169 | "honeycreeper"; 2170 | "honeyeater"; 2171 | "hookersealion"; 2172 | "hoopoe"; 2173 | "hornbill"; 2174 | "hornedtoad"; 2175 | "hornedviper"; 2176 | "hornet"; 2177 | "hornshark"; 2178 | "horse"; 2179 | "horsechestnutleafminer"; 2180 | "horsefly"; 2181 | "horsemouse"; 2182 | "horseshoebat"; 2183 | "horseshoecrab"; 2184 | "hound"; 2185 | "housefly"; 2186 | "hoverfly"; 2187 | "howlermonkey"; 2188 | "huemul"; 2189 | "huia"; 2190 | "human"; 2191 | "hummingbird"; 2192 | "humpbackwhale"; 2193 | "husky"; 2194 | "hydatidtapeworm"; 2195 | "hydra"; 2196 | "hyena"; 2197 | "hylaeosaurus"; 2198 | "hypacrosaurus"; 2199 | "hypsilophodon"; 2200 | "hyracotherium"; 2201 | "hyrax"; 2202 | "iaerismetalmark"; 2203 | "ibadanmalimbe"; 2204 | "iberianbarbel"; 2205 | "iberianchiffchaff"; 2206 | "iberianemeraldlizard"; 2207 | "iberianlynx"; 2208 | "iberianmidwifetoad"; 2209 | "iberianmole"; 2210 | "iberiannase"; 2211 | "ibex"; 2212 | "ibis"; 2213 | "ibisbill"; 2214 | "ibizanhound"; 2215 | "iceblueredtopzebra"; 2216 | "icefish"; 2217 | "icelandgull"; 2218 | "icelandichorse"; 2219 | "icelandicsheepdog"; 2220 | "ichidna"; 2221 | "ichneumonfly"; 2222 | "ichthyosaurs"; 2223 | "ichthyostega"; 2224 | "icterinewarbler"; 2225 | "iggypops"; 2226 | "iguana"; 2227 | "iguanodon"; 2228 | "illadopsis"; 2229 | "ilsamochadegu"; 2230 | "imago"; 2231 | "impala"; 2232 | "imperatorangel"; 2233 | "imperialeagle"; 2234 | "incatern"; 2235 | "inchworm"; 2236 | "indianabat"; 2237 | "indiancow"; 2238 | "indianelephant"; 2239 | "indianglassfish"; 2240 | "indianhare"; 2241 | "indianjackal"; 2242 | "indianpalmsquirrel"; 2243 | "indianpangolin"; 2244 | "indianrhinoceros"; 2245 | "indianringneckparakeet"; 2246 | "indianrockpython"; 2247 | "indianskimmer"; 2248 | "indianspinyloach"; 2249 | "indigobunting"; 2250 | "indigowingedparrot"; 2251 | "indochinahogdeer"; 2252 | "indochinesetiger"; 2253 | "indri"; 2254 | "indusriverdolphin"; 2255 | "inexpectatumpleco"; 2256 | "inganue"; 2257 | "insect"; 2258 | "intermediateegret"; 2259 | "invisiblerail"; 2260 | "iraniangroundjay"; 2261 | "iridescentshark"; 2262 | "iriomotecat"; 2263 | "irishdraughthorse"; 2264 | "irishredandwhitesetter"; 2265 | "irishsetter"; 2266 | "irishterrier"; 2267 | "irishwaterspaniel"; 2268 | "irishwolfhound"; 2269 | "irrawaddydolphin"; 2270 | "irukandjijellyfish"; 2271 | "isabellineshrike"; 2272 | "isabellinewheatear"; 2273 | "islandcanary"; 2274 | "islandwhistler"; 2275 | "isopod"; 2276 | "italianbrownbear"; 2277 | "italiangreyhound"; 2278 | "ivorybackedwoodswallow"; 2279 | "ivorybilledwoodpecker"; 2280 | "ivorygull"; 2281 | "izuthrush"; 2282 | "jabiru"; 2283 | "jackal"; 2284 | "jackrabbit"; 2285 | "jaeger"; 2286 | "jaguar"; 2287 | "jaguarundi"; 2288 | "janenschia"; 2289 | "japanesebeetle"; 2290 | "javalina"; 2291 | "jay"; 2292 | "jellyfish"; 2293 | "jenny"; 2294 | "jerboa"; 2295 | "joey"; 2296 | "johndory"; 2297 | "juliabutterfly"; 2298 | "jumpingbean"; 2299 | "junco"; 2300 | "junebug"; 2301 | "kagu"; 2302 | "kakapo"; 2303 | "kakarikis"; 2304 | "kangaroo"; 2305 | "karakul"; 2306 | "katydid"; 2307 | "kawala"; 2308 | "kentrosaurus"; 2309 | "kestrel"; 2310 | "kid"; 2311 | "killdeer"; 2312 | "killerwhale"; 2313 | "killifish"; 2314 | "kingbird"; 2315 | "kingfisher"; 2316 | "kinglet"; 2317 | "kingsnake"; 2318 | "kinkajou"; 2319 | "kiskadee"; 2320 | "kissingbug"; 2321 | "kite"; 2322 | "kitfox"; 2323 | "kitten"; 2324 | "kittiwake"; 2325 | "kitty"; 2326 | "kiwi"; 2327 | "koala"; 2328 | "koalabear"; 2329 | "kob"; 2330 | "kodiakbear"; 2331 | "koi"; 2332 | "komododragon"; 2333 | "koodoo"; 2334 | "kookaburra"; 2335 | "kouprey"; 2336 | "krill"; 2337 | "kronosaurus"; 2338 | "kudu"; 2339 | "kusimanse"; 2340 | "labradorretriever"; 2341 | "lacewing"; 2342 | "ladybird"; 2343 | "ladybug"; 2344 | "lamb"; 2345 | "lamprey"; 2346 | "langur"; 2347 | "lark"; 2348 | "larva"; 2349 | "laughingthrush"; 2350 | "lcont"; 2351 | "leafbird"; 2352 | "leafcutterant"; 2353 | "leafhopper"; 2354 | "leafwing"; 2355 | "leech"; 2356 | "lemming"; 2357 | "lemur"; 2358 | "leonberger"; 2359 | "leopard"; 2360 | "leopardseal"; 2361 | "leveret"; 2362 | "lhasaapso"; 2363 | "lice"; 2364 | "liger"; 2365 | "lightningbug"; 2366 | "limpet"; 2367 | "limpkin"; 2368 | "ling"; 2369 | "lion"; 2370 | "lionfish"; 2371 | "littlenightmonkeys"; 2372 | "lizard"; 2373 | "llama"; 2374 | "lobo"; 2375 | "lobster"; 2376 | "locust"; 2377 | "loggerheadturtle"; 2378 | "longhorn"; 2379 | "longhornbeetle"; 2380 | "longspur"; 2381 | "loon"; 2382 | "lorikeet"; 2383 | "loris"; 2384 | "louse"; 2385 | "lovebird"; 2386 | "lowchen"; 2387 | "lunamoth"; 2388 | "lungfish"; 2389 | "lynx"; 2390 | "lynx "; 2391 | "macaque"; 2392 | "macaw"; 2393 | "macropod"; 2394 | "madagascarhissingroach"; 2395 | "maggot"; 2396 | "magpie"; 2397 | "maiasaura"; 2398 | "majungatholus"; 2399 | "malamute"; 2400 | "mallard"; 2401 | "maltesedog"; 2402 | "mamba"; 2403 | "mamenchisaurus"; 2404 | "mammal"; 2405 | "mammoth"; 2406 | "manatee"; 2407 | "mandrill"; 2408 | "mangabey"; 2409 | "manta"; 2410 | "mantaray"; 2411 | "mantid"; 2412 | "mantis"; 2413 | "mantisray"; 2414 | "manxcat"; 2415 | "mara"; 2416 | "marabou"; 2417 | "marbledmurrelet"; 2418 | "mare"; 2419 | "marlin"; 2420 | "marmoset"; 2421 | "marmot"; 2422 | "marten"; 2423 | "martin"; 2424 | "massasauga"; 2425 | "massospondylus"; 2426 | "mastiff"; 2427 | "mastodon"; 2428 | "mayfly"; 2429 | "meadowhawk"; 2430 | "meadowlark"; 2431 | "mealworm"; 2432 | "meerkat"; 2433 | "megalosaurus"; 2434 | "megalotomusquinquespinosus"; 2435 | "megaraptor"; 2436 | "merganser"; 2437 | "merlin"; 2438 | "metalmarkbutterfly"; 2439 | "metamorphosis"; 2440 | "mice"; 2441 | "microvenator"; 2442 | "midge"; 2443 | "milksnake"; 2444 | "milkweedbug"; 2445 | "millipede"; 2446 | "minibeast"; 2447 | "mink"; 2448 | "minnow"; 2449 | "mite"; 2450 | "moa"; 2451 | "mockingbird"; 2452 | "mole"; 2453 | "mollies"; 2454 | "mollusk"; 2455 | "molly"; 2456 | "monarch"; 2457 | "mongoose"; 2458 | "mongrel"; 2459 | "monkey"; 2460 | "monkfish "; 2461 | "monoclonius"; 2462 | "montanoceratops"; 2463 | "moorhen"; 2464 | "moose"; 2465 | "moray"; 2466 | "morayeel"; 2467 | "morpho"; 2468 | "mosasaur"; 2469 | "mosquito"; 2470 | "moth"; 2471 | "motmot"; 2472 | "mouflon"; 2473 | "mountaincat"; 2474 | "mountainlion"; 2475 | "mouse"; 2476 | "mouse/mice"; 2477 | "mousebird"; 2478 | "mudpuppy"; 2479 | "mule"; 2480 | "mullet"; 2481 | "muntjac"; 2482 | "murrelet"; 2483 | "muskox"; 2484 | "muskrat"; 2485 | "mussaurus"; 2486 | "mussel"; 2487 | "mustang"; 2488 | "mutt"; 2489 | "myna"; 2490 | "mynah"; 2491 | "myotis "; 2492 | "nabarlek"; 2493 | "nag"; 2494 | "naga"; 2495 | "nagapies"; 2496 | "nakedmolerat"; 2497 | "nandine"; 2498 | "nandoo"; 2499 | "nandu"; 2500 | "narwhal"; 2501 | "narwhale"; 2502 | "natterjacktoad"; 2503 | "nauplius"; 2504 | "nautilus"; 2505 | "needlefish"; 2506 | "needletail"; 2507 | "nematode"; 2508 | "nene"; 2509 | "neonblueguppy"; 2510 | "neonbluehermitcrab"; 2511 | "neondwarfgourami"; 2512 | "neonrainbowfish"; 2513 | "neonredguppy"; 2514 | "neontetra"; 2515 | "nerka"; 2516 | "nettlefish"; 2517 | "newfoundlanddog"; 2518 | "newt"; 2519 | "newtnutria"; 2520 | "nightcrawler"; 2521 | "nighthawk"; 2522 | "nightheron"; 2523 | "nightingale"; 2524 | "nightjar"; 2525 | "nijssenissdwarfchihlid"; 2526 | "nilgai"; 2527 | "ninebandedarmadillo"; 2528 | "noctilio"; 2529 | "noctule"; 2530 | "noddy"; 2531 | "noolbenger"; 2532 | "northerncardinals"; 2533 | "northernelephantseal"; 2534 | "northernflyingsquirrel"; 2535 | "northernfurseal"; 2536 | "northernhairynosedwombat"; 2537 | "northernpike"; 2538 | "northernseahorse"; 2539 | "northernspottedowl"; 2540 | "norwaylobster"; 2541 | "norwayrat"; 2542 | "nubiangoat"; 2543 | "nudibranch"; 2544 | "numbat"; 2545 | "nurseshark"; 2546 | "nutcracker"; 2547 | "nuthatch"; 2548 | "nutria"; 2549 | "nyala"; 2550 | "nymph"; 2551 | "ocelot"; 2552 | "octopus"; 2553 | "okapi"; 2554 | "olingo"; 2555 | "olm"; 2556 | "opossum"; 2557 | "orangutan"; 2558 | "orca"; 2559 | "oregonsilverspotbutterfly"; 2560 | "oriole"; 2561 | "oropendola"; 2562 | "oropendula"; 2563 | "oryx"; 2564 | "osprey"; 2565 | "ostracod"; 2566 | "ostrich"; 2567 | "otter"; 2568 | "ovenbird"; 2569 | "owl"; 2570 | "owlbutterfly"; 2571 | "ox"; 2572 | "oxen"; 2573 | "oxpecker"; 2574 | "oyster"; 2575 | "ozarkbigearedbat"; 2576 | "paca "; 2577 | "pachyderm"; 2578 | "pacificparrotlet"; 2579 | "paddlefish"; 2580 | "paintedladybutterfly"; 2581 | "panda"; 2582 | "pangolin"; 2583 | "panther"; 2584 | "paperwasp"; 2585 | "papillon"; 2586 | "parakeet"; 2587 | "parrot"; 2588 | "partridge"; 2589 | "peacock"; 2590 | "peafowl"; 2591 | "peccary"; 2592 | "pekingese"; 2593 | "pelican"; 2594 | "pelicinuspetrel"; 2595 | "penguin"; 2596 | "perch"; 2597 | "peregrinefalcon"; 2598 | "pewee"; 2599 | "phalarope"; 2600 | "pharaohhound"; 2601 | "pheasant"; 2602 | "phoebe"; 2603 | "phoenix"; 2604 | "pig"; 2605 | "pigeon"; 2606 | "piglet"; 2607 | "pika"; 2608 | "pike"; 2609 | "pikeperch "; 2610 | "pilchard"; 2611 | "pinemarten"; 2612 | "pinkriverdolphin"; 2613 | "pinniped"; 2614 | "pintail"; 2615 | "pipistrelle"; 2616 | "pipit"; 2617 | "piranha"; 2618 | "pitbull"; 2619 | "pittabird"; 2620 | "plainsqueaker"; 2621 | "plankton"; 2622 | "planthopper"; 2623 | "platypus"; 2624 | "plover"; 2625 | "polarbear"; 2626 | "polecat"; 2627 | "polliwog"; 2628 | "polyp"; 2629 | "polyturator"; 2630 | "pomeranian"; 2631 | "pondskater"; 2632 | "pony"; 2633 | "pooch"; 2634 | "poodle"; 2635 | "porcupine"; 2636 | "porpoise"; 2637 | "portuguesemanofwar"; 2638 | "possum"; 2639 | "prairiedog"; 2640 | "prawn"; 2641 | "prayingmantid"; 2642 | "prayingmantis"; 2643 | "primate"; 2644 | "pronghorn"; 2645 | "pseudodynerusquadrisectus"; 2646 | "ptarmigan"; 2647 | "pterodactyls"; 2648 | "pterosaurs"; 2649 | "puffer"; 2650 | "pufferfish"; 2651 | "puffin"; 2652 | "pug"; 2653 | "pullet"; 2654 | "puma"; 2655 | "pupa"; 2656 | "pupfish"; 2657 | "puppy"; 2658 | "purplemarten"; 2659 | "pussycat"; 2660 | "pygmy"; 2661 | "python"; 2662 | "quadrisectus"; 2663 | "quagga"; 2664 | "quahog"; 2665 | "quail"; 2666 | "queenalexandrasbirdwing"; 2667 | "queenalexandrasbirdwingbutterfly"; 2668 | "queenant"; 2669 | "queenbee"; 2670 | "queenconch"; 2671 | "queenslandgrouper"; 2672 | "queenslandheeler"; 2673 | "queensnake"; 2674 | "quelea"; 2675 | "quetzal"; 2676 | "quetzalcoatlus"; 2677 | "quillback"; 2678 | "quinquespinosus"; 2679 | "quokka"; 2680 | "quoll"; 2681 | "rabbit"; 2682 | "rabidsquirrel"; 2683 | "raccoon"; 2684 | "racer"; 2685 | "racerunner"; 2686 | "ragfish"; 2687 | "rail"; 2688 | "rainbowfish"; 2689 | "rainbowlorikeet"; 2690 | "rainbowtrout"; 2691 | "ram"; 2692 | "raptors"; 2693 | "rasbora"; 2694 | "rat"; 2695 | "ratfish"; 2696 | "rattail"; 2697 | "rattlesnake"; 2698 | "raven"; 2699 | "ray"; 2700 | "redhead"; 2701 | "redheadedwoodpecker"; 2702 | "redpoll"; 2703 | "redstart"; 2704 | "redtailedhawk"; 2705 | "reindeer"; 2706 | "reptile"; 2707 | "reynard"; 2708 | "rhea"; 2709 | "rhesusmonkey"; 2710 | "rhino"; 2711 | "rhinoceros"; 2712 | "rhinocerosbeetle"; 2713 | "rhodesianridgeback"; 2714 | "ringtailedlemur"; 2715 | "ringworm"; 2716 | "riograndeescuerzo"; 2717 | "roach"; 2718 | "roadrunner"; 2719 | "roan"; 2720 | "robberfly"; 2721 | "robin"; 2722 | "rockrat"; 2723 | "rodent"; 2724 | "roebuck"; 2725 | "roller"; 2726 | "rook"; 2727 | "rooster"; 2728 | "rottweiler"; 2729 | "sable"; 2730 | "sableantelope"; 2731 | "sablefish "; 2732 | "saiga"; 2733 | "sakimonkey"; 2734 | "salamander"; 2735 | "salmon"; 2736 | "saltwatercrocodile"; 2737 | "sambar"; 2738 | "samoyeddog"; 2739 | "sandbarshark"; 2740 | "sanddollar"; 2741 | "sanderling"; 2742 | "sandpiper"; 2743 | "sapsucker"; 2744 | "sardine"; 2745 | "sawfish"; 2746 | "scallop"; 2747 | "scarab"; 2748 | "scarletibis"; 2749 | "scaup"; 2750 | "schapendoes"; 2751 | "schipperke"; 2752 | "schnauzer"; 2753 | "scorpion"; 2754 | "scoter"; 2755 | "screamer"; 2756 | "seabird"; 2757 | "seagull"; 2758 | "seahog"; 2759 | "seahorse"; 2760 | "seal"; 2761 | "sealion"; 2762 | "seamonkey"; 2763 | "seaslug"; 2764 | "seaurchin"; 2765 | "senegalpython"; 2766 | "seriema"; 2767 | "serpent"; 2768 | "serval"; 2769 | "shark"; 2770 | "shearwater"; 2771 | "sheep"; 2772 | "sheldrake"; 2773 | "shelduck"; 2774 | "shibainu"; 2775 | "shihtzu"; 2776 | "shorebird"; 2777 | "shoveler"; 2778 | "shrew"; 2779 | "shrike"; 2780 | "shrimp"; 2781 | "siamang"; 2782 | "siamesecat"; 2783 | "siberiantiger"; 2784 | "sidewinder"; 2785 | "sifaka"; 2786 | "silkworm"; 2787 | "silverfish"; 2788 | "silverfox"; 2789 | "silversidefish"; 2790 | "siskin"; 2791 | "skimmer"; 2792 | "skink"; 2793 | "skipper"; 2794 | "skua"; 2795 | "skunk"; 2796 | "skylark"; 2797 | "sloth"; 2798 | "slothbear"; 2799 | "slug"; 2800 | "smelts"; 2801 | "smew"; 2802 | "snail"; 2803 | "snake"; 2804 | "snipe"; 2805 | "snoutbutterfly"; 2806 | "snowdog"; 2807 | "snowgeese"; 2808 | "snowleopard"; 2809 | "snowmonkey"; 2810 | "snowyowl"; 2811 | "sockeyesalmon"; 2812 | "solenodon"; 2813 | "solitaire"; 2814 | "songbird"; 2815 | "sora"; 2816 | "southernhairnosedwombat"; 2817 | "sow"; 2818 | "spadefoot"; 2819 | "sparrow"; 2820 | "sphinx"; 2821 | "spider"; 2822 | "spidermonkey"; 2823 | "spiketail"; 2824 | "spittlebug"; 2825 | "sponge"; 2826 | "spoonbill"; 2827 | "spotteddolphin"; 2828 | "spreadwing"; 2829 | "springbok"; 2830 | "springpeeper"; 2831 | "springtail"; 2832 | "squab"; 2833 | "squamata"; 2834 | "squeaker"; 2835 | "squid"; 2836 | "squirrel"; 2837 | "stag"; 2838 | "stagbeetle"; 2839 | "stallion"; 2840 | "starfish"; 2841 | "starling"; 2842 | "steed"; 2843 | "steer"; 2844 | "stegosaurus"; 2845 | "stickinsect"; 2846 | "stickleback"; 2847 | "stilt"; 2848 | "stingray"; 2849 | "stinkbug"; 2850 | "stinkpot"; 2851 | "stoat"; 2852 | "stonefly"; 2853 | "stork"; 2854 | "stud"; 2855 | "sturgeon"; 2856 | "sugarglider"; 2857 | "sulphurbutterfly"; 2858 | "sunbear"; 2859 | "sunbittern"; 2860 | "sunfish"; 2861 | "swallow"; 2862 | "swallowtail"; 2863 | "swallowtailbutterfly"; 2864 | "swan"; 2865 | "swellfish"; 2866 | "swift"; 2867 | "swordfish"; 2868 | "tadpole"; 2869 | "tahr"; 2870 | "takin"; 2871 | "tamarin"; 2872 | "tanager"; 2873 | "tapaculo"; 2874 | "tapeworm"; 2875 | "tapir"; 2876 | "tarantula"; 2877 | "tarpan"; 2878 | "tarsier"; 2879 | "taruca"; 2880 | "tasmaniandevil"; 2881 | "tasmaniantiger"; 2882 | "tattler"; 2883 | "tayra"; 2884 | "teal"; 2885 | "tegus"; 2886 | "teledu"; 2887 | "tench"; 2888 | "tenrec"; 2889 | "termite"; 2890 | "tern"; 2891 | "terrapin"; 2892 | "terrier"; 2893 | "thoroughbred"; 2894 | "thrasher"; 2895 | "thrip"; 2896 | "thrush"; 2897 | "thunderbird"; 2898 | "thylacine"; 2899 | "tick"; 2900 | "tiger"; 2901 | "tigerbeetle"; 2902 | "tigermoth"; 2903 | "tigershark"; 2904 | "tilefish"; 2905 | "tinamou"; 2906 | "titi"; 2907 | "titmouse"; 2908 | "toad"; 2909 | "toadfish"; 2910 | "tomtit "; 2911 | "topi"; 2912 | "tortoise"; 2913 | "toucan"; 2914 | "towhee"; 2915 | "tragopan"; 2916 | "treecreeper"; 2917 | "trex"; 2918 | "triceratops"; 2919 | "trogon"; 2920 | "trout"; 2921 | "trumpeterbird"; 2922 | "trumpeterswan"; 2923 | "tsetsefly"; 2924 | "tuatara"; 2925 | "tuna"; 2926 | "turaco"; 2927 | "turkey"; 2928 | "turnstone"; 2929 | "turtle"; 2930 | "turtledove"; 2931 | "uakari"; 2932 | "ugandakob"; 2933 | "uintagroundsquirrel"; 2934 | "ulyssesbutterfly"; 2935 | "umbrellabird"; 2936 | "umbrette"; 2937 | "unau"; 2938 | "ungulate"; 2939 | "unicorn"; 2940 | "upupa"; 2941 | "urchin"; 2942 | "urial"; 2943 | "uromastyxmaliensis"; 2944 | "uromastyxspinipes"; 2945 | "urson"; 2946 | "urubu"; 2947 | "urus"; 2948 | "urutu"; 2949 | "urva"; 2950 | "utahprairiedog"; 2951 | "vampirebat"; 2952 | "vaquita"; 2953 | "veery"; 2954 | "velociraptor"; 2955 | "velvetcrab"; 2956 | "velvetworm"; 2957 | "venomoussnake"; 2958 | "verdin"; 2959 | "vervet"; 2960 | "viceroybutterfly"; 2961 | "vicuna"; 2962 | "viper"; 2963 | "viperfish"; 2964 | "vipersquid"; 2965 | "vireo"; 2966 | "virginiaopossum"; 2967 | "vixen"; 2968 | "vole"; 2969 | "volvox"; 2970 | "vulpesvelox"; 2971 | "vulpesvulpes"; 2972 | "vulture"; 2973 | "walkingstick"; 2974 | "wallaby"; 2975 | "wallaroo"; 2976 | "walleye"; 2977 | "walrus"; 2978 | "warbler"; 2979 | "warthog"; 2980 | "wasp"; 2981 | "waterboatman"; 2982 | "waterbuck"; 2983 | "waterbuffalo"; 2984 | "waterbug"; 2985 | "waterdogs"; 2986 | "waterdragons"; 2987 | "watermoccasin"; 2988 | "waterstrider"; 2989 | "waterthrush"; 2990 | "wattlebird"; 2991 | "watussi"; 2992 | "waxwing"; 2993 | "weasel"; 2994 | "weaverbird"; 2995 | "weevil"; 2996 | "westafricanantelope"; 2997 | "whale"; 2998 | "whapuku"; 2999 | "whelp"; 3000 | "whimbrel"; 3001 | "whippet"; 3002 | "whippoorwill"; 3003 | "whitebeakeddolphin"; 3004 | "whiteeye"; 3005 | "whitepelican"; 3006 | "whiterhino"; 3007 | "whitetaileddeer"; 3008 | "whitetippedreefshark"; 3009 | "whooper"; 3010 | "whoopingcrane"; 3011 | "widgeon"; 3012 | "widowspider"; 3013 | "wildcat"; 3014 | "wildebeast"; 3015 | "wildebeest"; 3016 | "willet"; 3017 | "wireworm"; 3018 | "wisent"; 3019 | "wobbegongshark"; 3020 | "wolf"; 3021 | "wolfspider"; 3022 | "wolverine"; 3023 | "wombat"; 3024 | "woodborer"; 3025 | "woodchuck"; 3026 | "woodcock"; 3027 | "woodnymphbutterfly"; 3028 | "woodpecker"; 3029 | "woodstorks"; 3030 | "woollybearcaterpillar"; 3031 | "worm"; 3032 | "wrasse"; 3033 | "wreckfish"; 3034 | "wren"; 3035 | "wrenchbird"; 3036 | "wryneck"; 3037 | "wuerhosaurus"; 3038 | "wyvern"; 3039 | "xanclomys"; 3040 | "xanthareel"; 3041 | "xantus"; 3042 | "xantusmurrelet"; 3043 | "xeme"; 3044 | "xenarthra"; 3045 | "xenoposeidon"; 3046 | "xenops"; 3047 | "xenopterygii"; 3048 | "xenopus"; 3049 | "xenotarsosaurus"; 3050 | "xenurine"; 3051 | "xenurusunicinctus"; 3052 | "xerus"; 3053 | "xiaosaurus"; 3054 | "xinjiangovenator"; 3055 | "xiphias"; 3056 | "xiphiasgladius"; 3057 | "xiphosuran"; 3058 | "xoloitzcuintli"; 3059 | "xoni"; 3060 | "xrayfish"; 3061 | "xraytetra"; 3062 | "xuanhanosaurus"; 3063 | "xuanhuaceratops"; 3064 | "xuanhuasaurus"; 3065 | "yaffle"; 3066 | "yak"; 3067 | "yapok"; 3068 | "yardant"; 3069 | "yearling"; 3070 | "yellowbelliedmarmot"; 3071 | "yellowbellylizard"; 3072 | "yellowhammer"; 3073 | "yellowjacket"; 3074 | "yellowlegs"; 3075 | "yellowthroat"; 3076 | "yellowwhitebutterfly"; 3077 | "yeti"; 3078 | "ynambu"; 3079 | "yorkshireterrier"; 3080 | "yosemitetoad"; 3081 | "yucker"; 3082 | "zander"; 3083 | "zanzibardaygecko"; 3084 | "zebra"; 3085 | "zebradove"; 3086 | "zebrafinch"; 3087 | "zebrafish"; 3088 | "zebralongwingbutterfly"; 3089 | "zebraswallowtailbutterfly"; 3090 | "zebratailedlizard"; 3091 | "zebu"; 3092 | "zenaida"; 3093 | "zeren"; 3094 | "zethusspinipes"; 3095 | "zethuswasp"; 3096 | "zigzagsalamander"; 3097 | "zonetailedpigeon"; 3098 | "zooplankton"; 3099 | "zopilote"; 3100 | "zorilla"; 3101 | ] 3102 | -------------------------------------------------------------------------------- /lib/faker.mli: -------------------------------------------------------------------------------- 1 | val adjectives : string list 2 | (** List of random adjectives. Extracted from 3 | http://assets.gfycat.com/adjectives. *) 4 | 5 | val animals : string list 6 | (** List of random animal names. Extracted from 7 | http://assets.gfycat.com/animals. *) 8 | -------------------------------------------------------------------------------- /lib/layouts.ml: -------------------------------------------------------------------------------- 1 | open Utils 2 | 3 | type file = 4 | | Folder of string * file list 5 | | File of string * (Format.formatter -> unit) 6 | 7 | type project = { layout : Config.t -> file; post_init : string list } 8 | 9 | let compare_file a b = 10 | match (a, b) with 11 | | Folder _, File _ -> -1 12 | | File _, Folder _ -> 1 13 | | Folder (a, _), Folder (b, _) -> String.compare a b 14 | | File (a, _), File (b, _) -> String.compare a b 15 | 16 | let rec pp_file ~pre ~last_dir ppf = 17 | let open Fmt in 18 | let pp_files ~pre ~last_dir ppf files = 19 | (* Only print newline at the end if our ancestors have not already done so 20 | (i.e. we are not the descendant of a last directory *) 21 | let pp_last_dir ppf last_dir = if not last_dir then pf ppf "@,%s" pre in 22 | let pp_children_last ppf = 23 | pf ppf "%s`-- %a" pre (pp_file ~last_dir:true ~pre:(pre ^ " ")) 24 | and pp_children_not_last ppf = 25 | pf ppf "%s|-- %a" pre (pp_file ~last_dir:false ~pre:(pre ^ "| ")) 26 | in 27 | match files with 28 | | [] -> () 29 | | [ last ] -> pf ppf "@,%a%a" pp_children_last last pp_last_dir last_dir 30 | | _ :: _ :: _ -> 31 | let last, not_last = 32 | files 33 | |> List.sort compare_file 34 | |> List.rev 35 | |> fun x -> (List.hd x, List.rev (List.tl x)) 36 | in 37 | pf ppf "@,%a@,%a%a" 38 | (list ~sep:cut pp_children_not_last) 39 | not_last pp_children_last last pp_last_dir last_dir 40 | in 41 | let pp_folder_name = styled `Bold (styled `Blue string) in 42 | function 43 | | File (s, _) -> pf ppf "%s" s 44 | | Folder (s, children) -> 45 | pf ppf "%a%a" pp_folder_name s (pp_files ~pre ~last_dir) children 46 | 47 | let pp_file = pp_file ~pre:"" ~last_dir:false 48 | 49 | let pp_project config = Fmt.using (fun { layout; _ } -> layout config) pp_file 50 | 51 | let license (config : Config.t) = 52 | License.v config.license ~year:config.current_year 53 | ~author:config.maintainer_fullname 54 | 55 | let library (config : Config.t) = 56 | let open Contents in 57 | let src_file = Utils_naming.file_of_project config.name in 58 | Folder 59 | ( config.name, 60 | [ 61 | Folder 62 | ( "src", 63 | [ 64 | File ("dune", Dune.library config); 65 | File (src_file ^ ".ml", hello_world_lib_ml config); 66 | File (src_file ^ ".mli", hello_world_lib_mli config); 67 | ] ); 68 | Folder 69 | ( "test", 70 | [ 71 | File ("dune", Dune.test config); 72 | File ("main.ml", test_main_ml config); 73 | File ("main.mli", empty_mli config); 74 | ] ); 75 | File (".gitignore", gitignore config); 76 | File (".ocamlformat", ocamlformat config); 77 | File ("dune-project", Dune_project.package config); 78 | File ("LICENSE", license config); 79 | File ("README.md", readme config); 80 | File ("CONTRIBUTING.md", contributing config); 81 | File ("CHANGES.md", changes config); 82 | (* Empty structure here only for pretty-printing to the user *) 83 | File (config.name ^ ".opam", fun _ -> ()); 84 | ] 85 | @ if config.git_repo then [ Folder (".git", []) ] else [] ) 86 | 87 | let library = { layout = library; post_init = [] } 88 | 89 | let binary (config : Config.t) = 90 | let open Contents in 91 | let exe_name = "main" in 92 | let lib_file = Utils_naming.file_of_project config.name in 93 | let bin_dune ppf = 94 | let libraries = 95 | [ 96 | config.name; 97 | "cmdliner"; 98 | "fmt"; 99 | "fmt.cli"; 100 | "fmt.tty"; 101 | "logs"; 102 | "logs.cli"; 103 | "logs.fmt"; 104 | ] 105 | |> List.sort String.compare 106 | in 107 | Dune.executable ~name:exe_name ~libraries ppf; 108 | Fmt.pf ppf "\n"; 109 | Dune.install ~exe_name ~bin_name:config.name ppf 110 | in 111 | Folder 112 | ( config.name, 113 | [ 114 | Folder 115 | ( "bin", 116 | [ 117 | File ("dune", bin_dune); 118 | File (exe_name ^ ".ml", bin_cmdliner config); 119 | File (exe_name ^ ".mli", empty_mli config); 120 | ] ); 121 | Folder 122 | ( "lib", 123 | [ 124 | File ("dune", Dune.library config); 125 | File (lib_file ^ ".ml", hello_world_lib_ml config); 126 | File (lib_file ^ ".mli", hello_world_lib_mli config); 127 | ] ); 128 | Folder 129 | ( "test", 130 | [ 131 | File ("dune", Dune.test config); 132 | File ("main.ml", test_main_ml config); 133 | File ("main.mli", empty_mli config); 134 | ] ); 135 | Folder ("test", []); 136 | File (".gitignore", gitignore config); 137 | File (".ocamlformat", ocamlformat config); 138 | File ("dune", Dune.generate_help config); 139 | File ("dune-project", Dune_project.package config); 140 | File ("LICENSE", license config); 141 | File ("README.md", readme config); 142 | File ("CONTRIBUTING.md", contributing ~promote:() config); 143 | File ("CHANGES.md", changes config); 144 | File (config.name ^ "-help.txt", bin_help_txt config); 145 | (* Empty structure here only for pretty-printing to the user *) 146 | File (config.name ^ ".opam", fun _ -> ()); 147 | ] 148 | @ if config.git_repo then [ Folder (".git", []) ] else [] ) 149 | 150 | let binary = { layout = binary; post_init = [] } 151 | 152 | let executable (config : Config.t) = 153 | let name = config.name in 154 | let toplevel_file = Utils.Utils_naming.file_of_project name in 155 | let libraries = config.dependencies in 156 | let open Contents in 157 | Folder 158 | ( config.name, 159 | [ 160 | File ("dune-project", Dune_project.minimal config); 161 | File ("dune", Dune.executable ~name ~libraries); 162 | File (toplevel_file ^ ".ml", hello_world_bin config); 163 | ] ) 164 | 165 | let executable = { layout = executable; post_init = [] } 166 | 167 | (* Work in progress *) 168 | let _ppx_deriver (config : Config.t) = 169 | let open Contents in 170 | let toplevel_file = Utils_naming.file_of_project config.name in 171 | Folder 172 | ( config.name, 173 | [ 174 | Folder 175 | ( "deriver", 176 | [ 177 | File ("dune", Dune.ppx_deriver config); 178 | File (toplevel_file ^ ".ml", src_ppx_deriver_ml config); 179 | File (toplevel_file ^ ".mli", src_ppx_deriver_mli config); 180 | ] ); 181 | Folder ("lib", [ File ("dune", Dune.ppx_deriver_lib config) ]); 182 | Folder 183 | ( "test", 184 | [ 185 | Folder 186 | ( "deriver", 187 | [ 188 | Folder ("errors", []); 189 | Folder ("passing", []); 190 | File ("dune", dune_gen_dune_rules config); 191 | File ("gen_dune_rules.ml", gen_dune_rules_ml config); 192 | ] ); 193 | ] ); 194 | File (".gitignore", gitignore config); 195 | File (".ocamlformat", ocamlformat config); 196 | File ("dune-project", Dune.library config); 197 | File ("LICENSE", license config); 198 | File ("README.md", readme_ppx config); 199 | File ("CONTRIBUTING.md", contributing config); 200 | File ("CHANGES.md", changes config); 201 | (* Empty structure here only for pretty-printing to the user *) 202 | File (config.name ^ ".opam", fun _ -> ()); 203 | ] 204 | @ if config.git_repo then [ Folder (".git", []) ] else [] ) 205 | 206 | let project_of_kind = function 207 | | `Library -> library 208 | | `Binary -> binary 209 | | `Executable -> executable 210 | 211 | let post_initialise config after = 212 | let open Config in 213 | Sys.chdir config.name; 214 | Utils_unix.sequence_commands 215 | ( ( if config.git_repo then 216 | [ 217 | (* initialise git repository *) 218 | "git init --quiet"; 219 | "git add ."; 220 | "git commit --quiet -m \"Initial commit\""; 221 | Fmt.strf "git remote add origin https://github.com/%s/%s.git" 222 | config.github_organisation config.name; 223 | ] 224 | else [] ) 225 | @ after ) 226 | 227 | let instantiate config { layout; post_init } = 228 | let rec aux root = function 229 | | Folder (name, contents) -> 230 | let path = Filename.concat root name in 231 | Logs.debug (fun m -> m "Creating folder %s" path); 232 | Utils_unix.mkdir_p path; 233 | contents |> List.iter (aux path) 234 | | File (name, printer) -> 235 | let path = Filename.concat root name in 236 | Logs.debug (fun m -> m "Creating file %s" path); 237 | Utils_unix.print_to_file path printer 238 | in 239 | aux Filename.current_dir_name (layout config); 240 | post_initialise config post_init 241 | -------------------------------------------------------------------------------- /lib/layouts.mli: -------------------------------------------------------------------------------- 1 | type file 2 | 3 | val pp_file : file Fmt.t 4 | 5 | type project 6 | 7 | val pp_project : Config.t -> project Fmt.t 8 | 9 | val library : project 10 | 11 | val binary : project 12 | 13 | val executable : project 14 | 15 | val project_of_kind : [ `Binary | `Executable | `Library ] -> project 16 | 17 | val instantiate : Config.t -> project -> (unit, [ `Msg of string ]) result 18 | -------------------------------------------------------------------------------- /lib/license.ml: -------------------------------------------------------------------------------- 1 | let mit ~year ~author ppf = 2 | Fmt.pf ppf 3 | {|The MIT License 4 | 5 | Copyright (c) %d %s 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE.|} 24 | year author 25 | 26 | let apache2 ~year ~author ppf = 27 | Fmt.pf ppf 28 | {|Copyright %d %s 29 | 30 | Licensed under the Apache License, Version 2.0 (the "License"); 31 | you may not use this file except in compliance with the License. 32 | You may obtain a copy of the License at 33 | 34 | http://www.apache.org/licenses/LICENSE-2.0 35 | 36 | Unless required by applicable law or agreed to in writing, software 37 | distributed under the License is distributed on an "AS IS" BASIS, 38 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 39 | See the License for the specific language governing permissions and 40 | limitations under the License.|} 41 | year author 42 | 43 | let bsd2 ~year ~author ppf = 44 | Fmt.pf ppf 45 | {|Copyright %d %s 46 | 47 | Redistribution and use in source and binary forms, with or without modification, 48 | are permitted provided that the following conditions are met: 49 | 50 | 1. Redistributions of source code must retain the above copyright notice, this 51 | list of conditions and the following disclaimer. 52 | 53 | 2. Redistributions in binary form must reproduce the above copyright notice, 54 | this list of conditions and the following disclaimer in the documentation and/or 55 | other materials provided with the distribution. 56 | 57 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 58 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 59 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 60 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 61 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 62 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 63 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 64 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 65 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 66 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.|} 67 | year author 68 | 69 | let bsd3 ~year ~author ppf = 70 | Fmt.pf ppf 71 | {|Copyright %d %s 72 | 73 | Redistribution and use in source and binary forms, with or without modification, 74 | are permitted provided that the following conditions are met: 75 | 76 | 1. Redistributions of source code must retain the above copyright notice, this 77 | list of conditions and the following disclaimer. 78 | 79 | 2. Redistributions in binary form must reproduce the above copyright notice, 80 | this list of conditions and the following disclaimer in the documentation and/or 81 | other materials provided with the distribution. 82 | 83 | 3. Neither the name of the copyright holder nor the names of its contributors 84 | may be used to endorse or promote products derived from this software without 85 | specific prior written permission. 86 | 87 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 88 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 89 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 90 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 91 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 92 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 93 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 94 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 95 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 96 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.|} 97 | year author 98 | 99 | let isc ~year ~author ppf = 100 | Fmt.pf ppf 101 | {|Copyright %d %s 102 | 103 | Permission to use, copy, modify, and/or distribute this software for any purpose 104 | with or without fee is hereby granted, provided that the above copyright notice 105 | and this permission notice appear in all copies. 106 | 107 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 108 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 109 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 110 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 111 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 112 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 113 | THIS SOFTWARE.|} 114 | year author 115 | 116 | type t = Apache2 | Bsd2 | Bsd3 | Isc | Mit 117 | 118 | let all = 119 | [ 120 | ("apache2", Apache2); 121 | ("bsd2", Bsd2); 122 | ("bsd3", Bsd3); 123 | ("isc", Isc); 124 | ("mit", Mit); 125 | ] 126 | 127 | let v = function 128 | | Apache2 -> apache2 129 | | Bsd2 -> bsd2 130 | | Bsd3 -> bsd3 131 | | Isc -> isc 132 | | Mit -> mit 133 | -------------------------------------------------------------------------------- /lib/license.mli: -------------------------------------------------------------------------------- 1 | type t = Apache2 | Bsd2 | Bsd3 | Isc | Mit 2 | 3 | val all : (string * t) list 4 | 5 | val v : t -> year:int -> author:string -> Format.formatter -> unit 6 | -------------------------------------------------------------------------------- /lib/opam.ml: -------------------------------------------------------------------------------- 1 | open Utils 2 | 3 | let default_opam_version = "2.0" 4 | 5 | let package_to_string = function 6 | | `Dune -> "dune" 7 | | `OCaml -> "ocaml" 8 | | `OCamlformat -> "ocamlformat" 9 | 10 | let get_last_in_line (tag, available_versions) = 11 | available_versions 12 | |> String.split_on_char ' ' 13 | |> List.rev 14 | |> function 15 | | latest :: _ -> Ok (tag, latest) 16 | | [] -> 17 | Result.errorf "No available versions for package `%s`" 18 | (package_to_string tag) 19 | 20 | type versions = 21 | (Config.versions, [ `Msg of string | `Command_not_found of string ]) result 22 | Lwt.t 23 | 24 | let get_versions (`Dune dune) (`OCaml ocaml) (`Opam opam) 25 | (`OCamlformat ocamlformat) : versions = 26 | let ( let+ ) x f = Lwt.map (Result.map f) x in 27 | let packages = 28 | (* These must be in alphabetical order, as [opam] always reports results in 29 | this order. See https://github.com/ocaml/opam/issues/4163. *) 30 | [ (`Dune, dune); (`OCaml, ocaml); (`OCamlformat, ocamlformat) ] 31 | in 32 | 33 | (* Opam queries are slow, so we only query for the latest versions of packages 34 | that aren't already specified. *) 35 | let packages_to_query = 36 | packages 37 | |> List.filter_map (fun (tag, version) -> 38 | match version with Some _ -> None | None -> Some tag) 39 | in 40 | 41 | let+ packages = 42 | let ( >>|* ) = List.Infix.( >>| ) in 43 | let open Result.Infix in 44 | match packages_to_query with 45 | (* No query necessary *) 46 | | [] -> Lwt.return (Ok (packages >>|* T2.map2 Option.get)) 47 | | _ :: _ -> 48 | packages_to_query 49 | >>|* package_to_string 50 | |> Utils_unix.execf "opam show --field=available-versions -- %a" 51 | Fmt.(list ~sep:sp string) 52 | |> Lwt.map 53 | ( Result.map (List.combine packages_to_query) 54 | >=> List.(map get_last_in_line >> sequence_result) ) 55 | in 56 | 57 | let dune = List.assoc `Dune packages 58 | and ocaml = List.assoc `OCaml packages 59 | and ocamlformat = List.assoc `OCamlformat packages 60 | and opam = Option.value ~default:default_opam_version opam in 61 | Config.{ opam; dune; ocaml; ocamlformat } 62 | -------------------------------------------------------------------------------- /lib/opam.mli: -------------------------------------------------------------------------------- 1 | val default_opam_version : string 2 | (** The default Opam version *) 3 | 4 | type versions = 5 | (Config.versions, [ `Msg of string | `Command_not_found of string ]) result 6 | Lwt.t 7 | 8 | val get_versions : 9 | [ `Dune of string option ] -> 10 | [ `OCaml of string option ] -> 11 | [ `Opam of string option ] -> 12 | [ `OCamlformat of string option ] -> 13 | versions 14 | (** Given a set of versions for packages, populate any [None] values with data 15 | from the [opam] CLI. *) 16 | -------------------------------------------------------------------------------- /lib/oskel.ml: -------------------------------------------------------------------------------- 1 | module License = License 2 | module Utils = Utils 3 | module Opam = Opam 4 | open Utils 5 | 6 | let stdout_is_tty = Unix.(isatty stdout) 7 | 8 | let progress_bar msg : (unit -> unit Lwt.t) * bool ref * bool ref = 9 | let ( >>= ) = Lwt.bind in 10 | let finished = ref false in 11 | let active = ref false in 12 | let shown_once = ref false in 13 | let rec frames = 14 | "⠋" 15 | :: "⠙" 16 | :: "⠹" 17 | :: "⠸" 18 | :: "⠼" 19 | :: "⠴" 20 | :: "⠦" 21 | :: "⠧" 22 | :: "⠇" 23 | :: "⠏" 24 | :: frames 25 | in 26 | let rec loop frames = 27 | if !finished then Lwt.return () 28 | else 29 | (* Initial space *) 30 | let () = 31 | if not !active then Printf.printf "\n"; 32 | active := true 33 | in 34 | if stdout_is_tty || not !shown_once then ( 35 | if !shown_once then Printf.printf "\r"; 36 | Printf.printf "[ %s ] %s%!" (List.hd frames) msg; 37 | shown_once := true ); 38 | Lwt_unix.sleep 0.1 39 | (* Logs_lwt.app (fun m -> m "%s %s\r" (List.hd frames) msg) *) 40 | >>= fun () -> loop (List.tl frames) 41 | in 42 | ((fun () -> loop frames), finished, active) 43 | 44 | let main ~dry_run ~project_kind config = 45 | let project = Layouts.project_of_kind project_kind in 46 | Logs.app (fun m -> m "@[@,%a@]" (Layouts.pp_project config) project); 47 | if not dry_run then 48 | match Layouts.instantiate config project with 49 | | Ok () -> 50 | Logs.app (fun m -> 51 | m "%a Created new project in `%a'" 52 | Fmt.(styled `Green string) 53 | "success" 54 | Fmt.(styled `Blue string) 55 | (Sys.getcwd ())) 56 | | Error (`Msg msg) -> 57 | Fmt.epr "%s" msg; 58 | exit 1 59 | 60 | let get_current_year () = 61 | Unix.time () |> Unix.localtime |> fun t -> t.Unix.tm_year + 1900 62 | 63 | let ask ~non_interactive ~assume_yes ?default prompt = function 64 | | Some s -> Lwt.return (Ok s) 65 | | None -> ( 66 | let ( let+ ) x f = Lwt.map f x in 67 | match (assume_yes, non_interactive, default) with 68 | | true, _, Some default -> Lwt.return (Ok default) 69 | | true, _, None -> 70 | Lwt.return (Result.errorf "No default available for `%s'" prompt) 71 | | false, true, Some _ -> 72 | Lwt.return 73 | (Result.errorf 74 | "Must set `%s', assume defaults with `--assume-yes', or enable \ 75 | interactive mode" 76 | prompt) 77 | | false, true, None -> 78 | Lwt.return 79 | (Result.errorf "Must set `%s' or enable interactive mode" prompt) 80 | | false, false, _ -> ( 81 | let pp_default = 82 | Fmt.( 83 | option 84 | (string |> using (function "" -> "" | s -> " (" ^ s ^ ")"))) 85 | in 86 | Fmt.pr "%a %s%a: %!" 87 | Fmt.(styled `Faint string) 88 | "question" prompt pp_default default; 89 | let+ line = 90 | Sys.catch_break true; 91 | let+ line = Lwt_io.read_line Lwt_io.stdin in 92 | Sys.catch_break false; 93 | line 94 | in 95 | match (line, default) with 96 | | "", Some default -> Ok default 97 | | _ -> Ok line ) ) 98 | 99 | let show_error (type a) msg : a = 100 | Logs.app (fun m -> m "\n%a %s." Fmt.(styled `Red string) "error" msg); 101 | exit 1 102 | 103 | let show_errorf fmt = Format.kasprintf show_error fmt 104 | 105 | let assert_ok = function Ok x -> x | Error (`Msg msg) -> show_error msg 106 | 107 | let adjective_animal () = 108 | let random_elt l = List.length l |> Random.int |> List.nth l in 109 | Fmt.strf "%s-%s" (random_elt Faker.adjectives) (random_elt Faker.animals) 110 | 111 | let ( let* ) = Lwt.bind 112 | 113 | and ( let+ ) x f = Lwt.map f x 114 | 115 | and ( and+ ) = Lwt.both 116 | 117 | let populate_config ~non_interactive ~assume_yes ~name ~maintainer_fullname 118 | ~maintainer_email ~github_organisation ~project_synopsis ~initial_version = 119 | let ( >|= ) x f = Lwt.map f x in 120 | let ask = ask ~non_interactive ~assume_yes in 121 | let* name = 122 | ask ~default:(adjective_animal ()) "name" name 123 | >|= fun x -> Result.bind x Validate.project |> assert_ok 124 | in 125 | let* maintainer_fullname = ask "author" maintainer_fullname >|= assert_ok in 126 | let* maintainer_email = ask "email" maintainer_email >|= assert_ok in 127 | let* github_organisation = 128 | ask "GitHub name" github_organisation >|= assert_ok 129 | in 130 | let* project_synopsis = 131 | ask ~default:"" "synopsis" project_synopsis >|= assert_ok 132 | in 133 | let* initial_version = 134 | ask ~default:"0.1.0" "version" initial_version >|= assert_ok 135 | in 136 | Lwt.return 137 | (object 138 | method name = name 139 | 140 | method maintainer_fullname = maintainer_fullname 141 | 142 | method maintainer_email = maintainer_email 143 | 144 | method github_organisation = github_organisation 145 | 146 | method project_synopsis = project_synopsis 147 | 148 | method initial_version = initial_version 149 | end) 150 | 151 | let run ~project_kind ?name ?project_synopsis ~maintainer_fullname 152 | ~maintainer_email ?github_organisation ?initial_version ?working_dir 153 | ~assume_yes ~license ~dependencies ~(versions : Opam.versions) 154 | ~ocamlformat_options ~dry_run ~non_interactive ~git_repo 155 | ?(current_year = get_current_year ()) () = 156 | let ( >>= ) = Lwt.bind and ( >>| ) x f = Lwt.map f x in 157 | Random.self_init (); 158 | (match working_dir with None -> () | Some dir -> Sys.chdir dir); 159 | let promise = 160 | let* () = 161 | Logs_lwt.app (fun m -> 162 | m "%a" Fmt.(styled `Bold string) "oskel v%%VERSION%%") 163 | in 164 | let* maintainer_fullname = maintainer_fullname in 165 | let* maintainer_email = maintainer_email in 166 | let config = 167 | populate_config ~non_interactive ~assume_yes ~name ~maintainer_fullname 168 | ~maintainer_email ~github_organisation ~project_synopsis 169 | ~initial_version 170 | in 171 | let progress, finished, progress_bar_active = 172 | progress_bar "Getting latest version numbers via `opam info`" 173 | in 174 | let ( >* ) a b = a >>= fun a -> b () >>| fun () -> a in 175 | let+ versions = 176 | versions 177 | >* Lwt.( 178 | fun () -> 179 | finished := true; 180 | if !progress_bar_active then Printf.printf "\r\n%!"; 181 | return ()) 182 | and+ c = config >* progress in 183 | let versions = 184 | versions 185 | |> function 186 | | Ok x -> x 187 | | Error (`Msg msg) -> show_error msg 188 | | Error (`Command_not_found cmd) -> 189 | show_errorf 190 | "Command `%s` not found (error code 127). Either install `%s` or \ 191 | specify versions of Dune, OCaml and OCamlformat explicitly" 192 | cmd cmd 193 | in 194 | 195 | main ~project_kind ~dry_run 196 | { 197 | name = c#name; 198 | project_synopsis = c#project_synopsis; 199 | maintainer_fullname = c#maintainer_fullname; 200 | maintainer_email = c#maintainer_email; 201 | github_organisation = c#github_organisation; 202 | initial_version = c#initial_version; 203 | license; 204 | dependencies; 205 | versions; 206 | ocamlformat_options; 207 | current_year; 208 | git_repo; 209 | }; 210 | Ok () 211 | in 212 | (try Lwt_main.run promise with Sys.Break -> Error (`Msg "Cancelled")) 213 | |> assert_ok 214 | -------------------------------------------------------------------------------- /lib/oskel.mli: -------------------------------------------------------------------------------- 1 | module License = License 2 | module Utils = Utils 3 | module Opam = Opam 4 | 5 | val show_errorf : ('a, Format.formatter, unit, 'b) format4 -> 'a 6 | 7 | val run : 8 | project_kind:[ `Library | `Binary | `Executable ] -> 9 | (* These are asked for from Stdin if not supplied *) 10 | ?name:string -> 11 | ?project_synopsis:string -> 12 | maintainer_fullname:string option Lwt.t -> 13 | maintainer_email:string option Lwt.t -> 14 | ?github_organisation:string -> 15 | ?initial_version:string -> 16 | ?working_dir:string -> 17 | assume_yes:bool -> 18 | (* *) 19 | license:License.t -> 20 | dependencies:string list -> 21 | versions:Opam.versions -> 22 | ocamlformat_options:(string * string) list -> 23 | dry_run:bool -> 24 | non_interactive:bool -> 25 | git_repo:bool -> 26 | ?current_year:int -> 27 | unit -> 28 | unit 29 | -------------------------------------------------------------------------------- /lib/utils.ml: -------------------------------------------------------------------------------- 1 | let ( >> ) f g x = g (f x) 2 | 3 | module T2 = struct 4 | let map1 f (a, c) = (f a, c) 5 | 6 | let map2 f (a, b) = (a, f b) 7 | end 8 | 9 | module T3 = struct 10 | let map f (a1, a2, a3) = (f a1, f a2, f a3) 11 | end 12 | 13 | module Option = struct 14 | type 'a t = 'a option 15 | 16 | let get = function Some o -> o | None -> raise (Invalid_argument "None") 17 | 18 | let value opt ~default = match opt with Some o -> o | None -> default 19 | end 20 | 21 | module Result = struct 22 | type ('a, 'e) t = ('a, 'e) result 23 | 24 | let map f = function Ok o -> Ok (f o) | Error _ as e -> e 25 | 26 | let bind x f = match x with Ok o -> f o | Error _ as e -> e 27 | 28 | let errorf fmt = Format.kasprintf (fun msg -> Error (`Msg msg)) fmt 29 | 30 | module Infix = struct 31 | let ( >>= ) = bind 32 | 33 | let ( >>| ) x f = map f x 34 | 35 | let ( >=> ) f g x = bind (f x) g 36 | end 37 | 38 | module Syntax = struct 39 | let ( let* ) = bind 40 | 41 | let ( let+ ) x f = map f x 42 | end 43 | end 44 | 45 | module List = struct 46 | include List 47 | 48 | let rec filter_map f = function 49 | | [] -> [] 50 | | x :: xs -> ( 51 | match f x with 52 | | Some x -> x :: filter_map f xs 53 | | None -> filter_map f xs ) 54 | 55 | module Infix = struct 56 | let ( >>| ) x f = List.map f x 57 | end 58 | 59 | let sequence_result list = 60 | let rec inner acc = function 61 | | [] -> Ok (List.rev acc) 62 | | Error e :: _ -> Error e 63 | | Ok o :: tl -> (inner [@ocaml.tailcall]) (o :: acc) tl 64 | in 65 | inner [] list 66 | end 67 | 68 | module Utils_unix = struct 69 | let ( let+ ) x f = Lwt.map f x 70 | 71 | let ( let* ) = Lwt.bind 72 | 73 | let exec cmd = 74 | let pipe_out, pipe_in = Lwt_unix.pipe () in 75 | let* process_status = 76 | Lwt_process.exec ~stderr:`Dev_null 77 | ~stdout:(`FD_move (Lwt_unix.unix_file_descr pipe_in)) 78 | (Lwt_process.shell cmd) 79 | in 80 | let+ lines = 81 | Lwt_io.(of_fd ~mode:input) pipe_out 82 | |> Lwt_io.read_lines 83 | |> Lwt_stream.to_list 84 | in 85 | match process_status with 86 | | WEXITED 0 -> Ok lines 87 | | WEXITED 127 -> 88 | Error (`Command_not_found (String.split_on_char ' ' cmd |> List.hd)) 89 | | WEXITED n -> 90 | Result.errorf "Command \"%s\" failed with return code %d" cmd n 91 | | WSIGNALED _ | WSTOPPED _ -> 92 | Result.errorf "Command \"%s\" was interrupted" cmd 93 | 94 | let execf fmt = Format.kasprintf exec ("@[" ^^ fmt ^^ "@]") 95 | 96 | let sequence_commands cmds = 97 | List.fold_left 98 | (fun ret cmd -> 99 | match ret with 100 | | Error e -> Error e 101 | | Ok () -> ( 102 | match Unix.system cmd with 103 | | WEXITED 0 -> Ok () 104 | | WEXITED n -> 105 | Result.errorf "Command \"%s\" failed with return code %d" cmd n 106 | | WSIGNALED _ | WSTOPPED _ -> 107 | Result.errorf "Command \"%s\" was interrupted" cmd )) 108 | (Ok ()) cmds 109 | 110 | let print_to_file path printer = 111 | let channel = open_out path in 112 | let formatter = Format.formatter_of_out_channel channel in 113 | printer formatter; 114 | Format.pp_print_newline formatter (); 115 | close_out channel 116 | 117 | let rec mkdir_p path = 118 | try Unix.mkdir path 0o777 with 119 | | Unix.Unix_error (EEXIST, _, _) -> () 120 | | Unix.Unix_error (ENOENT, _, _) -> 121 | let parent = Filename.dirname path in 122 | mkdir_p parent; 123 | Unix.mkdir path 0o777 124 | end 125 | 126 | module Utils_naming = struct 127 | let file_of_project = String.map (function '-' -> '_' | c -> c) 128 | 129 | let findlib_of_project = file_of_project >> String.capitalize_ascii 130 | end 131 | -------------------------------------------------------------------------------- /lib/utils.mli: -------------------------------------------------------------------------------- 1 | val ( >> ) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c 2 | (** Left-to-right function composition. *) 3 | 4 | module T2 : sig 5 | val map1 : ('a -> 'b) -> 'a * 'c -> 'b * 'c 6 | 7 | val map2 : ('b -> 'c) -> 'a * 'b -> 'a * 'c 8 | end 9 | 10 | module T3 : sig 11 | val map : ('a -> 'b) -> 'a * 'a * 'a -> 'b * 'b * 'b 12 | end 13 | 14 | module List : sig 15 | include module type of List 16 | 17 | val filter_map : ('a -> 'b option) -> 'a list -> 'b list 18 | 19 | module Infix : sig 20 | val ( >>| ) : 'a list -> ('a -> 'b) -> 'b list 21 | end 22 | 23 | val sequence_result : ('a, 'e) result list -> ('a list, 'e) result 24 | end 25 | 26 | module Option : sig 27 | type 'a t = 'a option 28 | 29 | val get : 'a t -> 'a 30 | 31 | val value : 'a option -> default:'a -> 'a 32 | end 33 | 34 | module Result : sig 35 | type ('a, 'e) t = ('a, 'e) result 36 | 37 | val map : ('a -> 'b) -> ('a, 'e) t -> ('b, 'e) t 38 | 39 | val bind : ('a, 'e) t -> ('a -> ('b, 'e) t) -> ('b, 'e) t 40 | 41 | val errorf : 42 | ('a, Format.formatter, unit, ('b, [> `Msg of string ]) result) format4 -> 'a 43 | 44 | module Infix : sig 45 | val ( >>= ) : ('a, 'e) t -> ('a -> ('b, 'e) t) -> ('b, 'e) t 46 | 47 | val ( >>| ) : ('a, 'e) t -> ('a -> 'b) -> ('b, 'e) t 48 | 49 | val ( >=> ) : ('a -> ('b, 'e) t) -> ('b -> ('c, 'e) t) -> 'a -> ('c, 'e) t 50 | end 51 | 52 | module Syntax : sig 53 | val ( let* ) : ('a, 'e) t -> ('a -> ('b, 'e) t) -> ('b, 'e) t 54 | 55 | val ( let+ ) : ('a, 'e) t -> ('a -> 'b) -> ('b, 'e) t 56 | end 57 | end 58 | 59 | module Utils_unix : sig 60 | val exec : 61 | string -> 62 | (string list, [ `Msg of string | `Command_not_found of string ]) result 63 | Lwt.t 64 | 65 | val execf : 66 | ( 'a, 67 | Format.formatter, 68 | unit, 69 | (string list, [ `Msg of string | `Command_not_found of string ]) result 70 | Lwt.t ) 71 | format4 -> 72 | 'a 73 | (** [execf] is like {!exec} but consumes a format string. *) 74 | 75 | val sequence_commands : string list -> (unit, [ `Msg of string ]) result 76 | 77 | val print_to_file : string -> (Format.formatter -> unit) -> unit 78 | 79 | val mkdir_p : string -> unit 80 | end 81 | 82 | module Utils_naming : sig 83 | val file_of_project : string -> string 84 | (** Given an opam package name, derive a conventional name for the top-level 85 | file name. Replaces all '-' characters with '_'. *) 86 | 87 | val findlib_of_project : string -> string 88 | (** Like {!file_of_project} but capitalised. *) 89 | end 90 | -------------------------------------------------------------------------------- /lib/validate.ml: -------------------------------------------------------------------------------- 1 | let project s = 2 | let s = String.trim s in 3 | if String.length s = 0 then Error (`Msg "Project name cannot be empty") 4 | else if String.contains s ' ' then 5 | Error (`Msg "Project name cannot contain spaces") 6 | else Ok s 7 | -------------------------------------------------------------------------------- /oskel-help.txt: -------------------------------------------------------------------------------- 1 | NAME 2 | oskel - Generate skeleton OCaml projects. 3 | 4 | SYNOPSIS 5 | oskel [OPTION]... [NAME] 6 | 7 | OPTIONS 8 | --color=WHEN (absent=auto) 9 | Colorize the output. WHEN must be one of `auto', `always' or 10 | `never'. 11 | 12 | --current-year=VAL (absent OSKEL_CURRENT_YEAR env) 13 | Set the current year. Useful for achieving deterministic output. 14 | 15 | --depends=VAL (absent=fmt,logs or OSKEL_DEPENDS env) 16 | Dependencies of the project in a comma-separated list. 17 | 18 | --disable-git (absent OSKEL_DISABLE_GIT env) 19 | Don't generate a git repository for the project. 20 | 21 | --dry-run 22 | Simulate the command, but don't actually perform any changes. 23 | 24 | --email=VAL (absent OSKEL_EMAIL env) 25 | Maintainer's contact email. If not specified, Oskel will attempt 26 | to read this from `git config user.email`. 27 | 28 | --full-name=VAL (absent OSKEL_FULL_NAME env) 29 | Maintainer's full name. If not specified, Oskel will attempt to 30 | read this from `git config user.name`. 31 | 32 | --github-org=VAL (absent OSKEL_GITHUB_ORG env) 33 | GitHub organisation associated with the project. 34 | 35 | --help[=FMT] (default=auto) 36 | Show this help in format FMT. The value FMT must be one of `auto', 37 | `pager', `groff' or `plain'. With `auto', the format is `pager` or 38 | `plain' whenever the TERM env var is `dumb' or undefined. 39 | 40 | --initial-version=VAL (absent OSKEL_INITIAL_VERSION env) 41 | Initial version at which to release the project. 42 | 43 | --kind=VAL (absent=library or OSKEL_KIND env) 44 | Type of project to create. One of one of `library', `binary' or 45 | `executable'. 46 | 47 | --license=VAL (absent=mit or OSKEL_LICENSE env) 48 | License to add to the project. One of one of `apache2', `bsd2', 49 | `bsd3', `isc' or `mit'. 50 | 51 | --non-interactive 52 | Do not show interactive prompts. 53 | 54 | --ocamlformat-options=VAL (absent OSKEL_OCAMLFORMAT_OPTIONS env) 55 | Options to add to the .ocamlformat file, as a comma-separated list 56 | of key-value pairs. (e.g. 57 | "parse-docstrings=true,break-infix=fit-or-vertical") 58 | 59 | -q, --quiet 60 | Be quiet. Takes over -v and --verbosity. 61 | 62 | --synopsis=VAL 63 | Synopsis of the project skeleton. 64 | 65 | -v, --verbose 66 | Increase verbosity. Repeatable, but more than twice does not bring 67 | more. 68 | 69 | --verbosity=LEVEL (absent=warning) 70 | Be more or less verbose. LEVEL must be one of `quiet', `error', 71 | `warning', `info' or `debug'. Takes over -v. 72 | 73 | --version 74 | Show version information. 75 | 76 | --version-dune=VAL (absent OSKEL_VERSION_DUNE env) 77 | Version of dune to associate with the project. 78 | 79 | --version-ocaml=VAL (absent OSKEL_VERSION_OCAML env) 80 | Version of OCaml to associate with the project. 81 | 82 | --version-ocamlformat=VAL (absent OSKEL_VERSION_OCAMLFORMAT env) 83 | Version of OCamlformat to associate with the project. 84 | 85 | --version-opam=VAL (absent OSKEL_VERSION_OPAM env) 86 | Version of opam to associate with the project. The default value 87 | is `2.0`. 88 | 89 | --working-dir=VAL 90 | Run as if Oskel was started in instead of the current 91 | working directory. 92 | 93 | --yes, --assume-yes 94 | Respond `yes' to all prompts and accept all defaults. 95 | 96 | EXIT STATUS 97 | oskel exits with the following status: 98 | 99 | 0 on success. 100 | 101 | 124 on command line parsing errors. 102 | 103 | 125 on unexpected internal errors (bugs). 104 | 105 | ENVIRONMENT 106 | These environment variables affect the execution of oskel: 107 | 108 | OSKEL_CURRENT_YEAR 109 | See option --current-year. 110 | 111 | OSKEL_DEPENDS 112 | See option --depends. 113 | 114 | OSKEL_DISABLE_GIT 115 | See option --disable-git. 116 | 117 | OSKEL_EMAIL 118 | See option --email. 119 | 120 | OSKEL_FULL_NAME 121 | See option --full-name. 122 | 123 | OSKEL_GITHUB_ORG 124 | See option --github-org. 125 | 126 | OSKEL_INITIAL_VERSION 127 | See option --initial-version. 128 | 129 | OSKEL_KIND 130 | See option --kind. 131 | 132 | OSKEL_LICENSE 133 | See option --license. 134 | 135 | OSKEL_OCAMLFORMAT_OPTIONS 136 | See option --ocamlformat-options. 137 | 138 | OSKEL_VERSION_DUNE 139 | See option --version-dune. 140 | 141 | OSKEL_VERSION_OCAML 142 | See option --version-ocaml. 143 | 144 | OSKEL_VERSION_OCAMLFORMAT 145 | See option --version-ocamlformat. 146 | 147 | OSKEL_VERSION_OPAM 148 | See option --version-opam. 149 | 150 | -------------------------------------------------------------------------------- /oskel.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "Skeleton generator for OCaml projects" 4 | description: """ 5 | Skeleton generator for OCaml projects 6 | 7 | oskel is a tool for generating stubs of OCaml projects pre-equipped with the 8 | standard boilerplate: OCamlformat config file, Alcotest scaffolding, 9 | auto-generation of [*.opam] files via [dune-project] etc. It supports generating 10 | binaries with Cmdliner, Fmt and Logs support. The resulting projects are 11 | compliant with [dune-release lint]. 12 | """ 13 | maintainer: ["Craig Ferguson "] 14 | authors: ["Craig Ferguson "] 15 | homepage: "https://github.com/CraigFe/oskel" 16 | doc: "https://CraigFe.github.io/oskel" 17 | bug-reports: "https://github.com/CraigFe/oskel/issues" 18 | depends: [ 19 | "dune" {>= "2.0"} 20 | "cmdliner" 21 | "fmt" {>= "0.8.7"} 22 | "logs" 23 | "stdlib-shims" 24 | "ocaml-syntax-shims" 25 | "lwt" 26 | ] 27 | build: [ 28 | ["dune" "subst"] {pinned} 29 | [ 30 | "dune" 31 | "build" 32 | "-p" 33 | name 34 | "-j" 35 | jobs 36 | "@install" 37 | "@runtest" {with-test} 38 | "@doc" {with-doc} 39 | ] 40 | ] 41 | dev-repo: "git+https://github.com/CraigFe/oskel.git" 42 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | proseWrap: "always" 3 | }; 4 | --------------------------------------------------------------------------------