Contributing Your Own Skeletons

Overview

There are 2 kinds of skeletons used to create and update drom project:

  • project skeletons define a whole project composed of one or more packages

  • package skeletons define only one package, corresponding to every opam package generated in the project

drom comes with a set of predefined skeletons. Users can contribute their own skeletons, either by adding them to their local configuration, or by submitting them to the drom project.

Skeleton Locations

When building its set of skeletons, drom will search them in two directories:

  • A system directory. It is the first existing one in the following list:

    • $DROM_SHARE_DIR/skeletons/, i.e. using the $DROM_SHARED_DIR environment variable

    • any directory ./share/drom/skeletons/ in one of the ancestor directories (., .., ../.., etc.). This is used to test new skeletons directly from drom sources.

    • $OPAMROOT/plugins/opam-drom/skeletons/, where $OPAMROOT defaults to $HOME/.opam/ if undefined. This is used when drom is installed as an opam plugin, but can also be used when installed globally by the user.

    • $OPAM_SWITCH_PREFIX/share/drom/skeletons/. This is used when drom is installed in the local switch only.

    • ${share-dir}/skeletons, where ${share-dir} is the value of that variable in the user configuration file ($HOME/.config/drom/config)

  • A user directory: $HOME/.config/drom/skeletons/

If a skeleton exists in both directories, the one in the user directory overwrites the one in the system directory. drom will then complain about it.

The Skeleton Format

The skeletons/ folder is divided in two directories projects/ and packages/ for the two kinds of skeletons.

Each directory contains one directory per skeleton, containing the following files:

  • skeleton.toml: some information on the skeleton (name, inheritance, additionnal information on files)

  • files/: the template files, that will be instantiated for that skeleton. For a project skeleton, the files are expected to be instantiated at the root of the project (drom.toml location). For a package skeleton, the files are expected to be instantiated in the package directory (package.toml location)

  • project.toml: for project skeletons, this file contains an initial configuration for the project variables and packages. If the skeleton is inherited from another one, the ancester configuration is completed with the child configuration, with priority to the child configuration, and so recursively. Note that this file contains also the configuration of the packages, since no package configuration is read from package skeletons for now.

  • package.toml: this file is not used.

Let’s take an excerpt of a skeleton.toml file:

[skeleton]
name = "program"
inherits = "virtual"

[file]
"Makefile" = { skips = [ "make" ] }
"dune-project_" = { file = "dune-project", skips = [ "dune" ] }
...

The file contains the following fields:

  • name (mandatory): the name of the skeleton

  • inherits (optional): the name of a skeleton from which this skeleton inherits

  • [file] section (optional): a set of fields to configure the use of one of the files in the files/ directory. Currently, the following fields are availble:

    • file: the name under which the file should be instantiated. This is useful if you cannot use the real name of the file in the skeleton, for example because the name would conflict with tools used in the project (dot files, dune files, etc)

    • skips: a list of tags that can be used in the drom.toml skip option or --skip argument to skip this file.

    • create: whether the file should only be created if it doesn’t exist yet (true) or every time (false, default).

    • subst: whether substitutions should happen on the file (true, default) or not (false). Useful for binary files for example to avoid accidental substitutions.

    • skip: whether the file should be always skipped (true) or not (false, default). Useful for a documentation file in the skeleton files.

    • record: whether the file should recorded in git files and in the .drom state file (true, default). Useful for documentation files that are only to help the user, not to remain in the project.

The Skeleton Substitution Language

Substitutions are performed on files in skeletons if the subst field is not set to false in the skeleton.toml file (see above).

There are 3 kinds of substitutions happening:

  • Brace substitutions (!{xxx}): these substitutions are hardcoded in drom to generate specific information for the project or package.

  • Paren substitutions (!(xxx)): these substitutions replace the variable with its value, respectively, for a project, in the [fields] section of the drom.toml, or for a package, in the [fields] section of the package.toml or in the [package.fields] section of the package in the drom.toml file.

  • Bracket substitutions (![xxx]): these substitutions always return an empty string. They are used for side-effects on the substitutions (conditional substitutions, setting flags, etc.)

Brace and paren substitutions can be combined with encodings by using a :ENCODING extension. For example, !{name:upp} is replaced by the name of the project/package in uppercase.

The following encodings are available: * html: html encoding (& is replaced by &, etc.) * cap: set the first char to uppercase * uncap: set the first char to lowercase * low: lowercase * uncap: uppercase * alpha: replace all non-alpha chars by underscores

For package substitutions, brace and paren substitutions for the project are used if no substitution was found for the package. It is possible to force project substitution in a package using the project- prefix (!{project-name} for example in a package file for the name substitution of the project). Reciprocally, it is possible to use a package- prefix in a Paren (field) substitution to prevent a project substitution if the field is not defined in the package.

Brace Substitutions

The ultimate source of information for Brace substitutions is the subst.ml module in the project_brace and package_brace fuctions.

Currently, the following project substitutions are available:

  • !{escape:true}!{escape:false}: makes \!{ be replaced by !{ instead of starting a substitution.

  • !{name}: the project name

  • !{synopsis}: the project synopsis

  • !{description}: the project description

  • !{version}: the project version

  • !{edition}: the default OCaml version to use

  • !{min-edition}: the minimal OCaml version

  • !{github-organization}: the current github-organization

  • !{authors-as-strings}: the list of authors

  • !{authors-for-toml}: the list of authors in TOML

  • !{authors-ampersand}: the authors separated by ampersands

  • !{copyright}: the project copyright

  • !{license}: the project full license file

  • !{license-name}: the SPDF name of the project license

  • !{header-ml}: the header for an ML file (also MLL file)

  • !{header-c}: the header for a C file (also MLY file)

  • !{year}, !{month}, !{day}: current date

  • There are many other substitutions, for example specific to Dune files (like !{dune-packages} for the dune-project file). Some of them will disappear because they can be generated using conditionals (![if:...], see bracket substitutions), some of them will be documented later (TODO).

Currently, the following package substitutions are available:

  • !{name}: the package name

  • !{dir}: the package dir

  • !{skeleton}: the package skeleton

  • !{library-name}: the package library name (uncapitalized version of the pack option or underscorified version of the package name)

  • !{library-module}: the package module name (pack option or capitalized version of the package name)

Paren Substitutions (project and package fields)

Paren substitutions are substituted by the [fields] variables in the project/package description files. If the field is not defined, the substitution is replaced by the empty string. Such fields are often used for trailers in generated files, to keep using drom to update them while still adding specific information by hand.

Currently, the following paren substitutions are used in project skeletons:

  • !(dot-gitignore-trailer): a trailer added at the end of the .gitignore file

  • !(dune-dirs): a string added in the (dirs ...) stanza of the root dune files to specify which dirs should be scanned.

  • !(makefile-trailer): a trailer added at the end of the generated Makefile

Currently, the following paren substitutions are used in package skeletons:

  • !(dune-libraries): a string added in the (libraries ...) stanza of the generated dune file

  • !(dune-stanzas): a string added in the (executable ...) or (library ...) stanza of the generated dune file

  • !(dune-trailer): a string added at the end of the generated dune file

Bracket Substitutions

The following bracket substitutions are currently available:

  • ![if:CONDITION]![else]![fi]: depending on the condition, replace by the first or second part. The condition can be:

    • not:CONDITION: negation of a condition

    • skip:TAG: true if the tag should be skipped

    • gen:TAG: true if the tag should not be skipped

    • true and false: constants

    • skeleton:is:SKELETON: true is the current skeleton is the argument

    • kind:is:KIND: true if the kind of the package is the argument (kind is one of library, program or virtual)

    • pack: true if the package should be packed

    • windows-ci: true if the project should run Windows CI

    • github-organization, homepage, copyright, bug-reports, dev-repo, doc-gen, doc-api, sphinx-target, profile: true if the corresponding value is defined for the project

    • project:CONDITION: in a package, check if the condition is true for the project instead