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_DIRenvironment variableany directory
./share/drom/skeletons/in one of the ancestor directories (.,..,../.., etc.). This is used to test new skeletons directly fromdromsources.$OPAMROOT/plugins/opam-drom/skeletons/, where$OPAMROOTdefaults to$HOME/.opam/if undefined. This is used whendromis installed as anopamplugin, but can also be used when installed globally by the user.$OPAM_SWITCH_PREFIX/share/drom/skeletons/. This is used whendromis 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.tomllocation). For a package skeleton, the files are expected to be instantiated in the package directory (package.tomllocation)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 skeletoninherits(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 thefiles/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,dunefiles, etc)skips: a list of tags that can be used in thedrom.tomlskipoption or--skipargument 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 ingitfiles and in the.dromstate 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 indromto 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 thedrom.toml, or for a package, in the[fields]section of thepackage.tomlor in the[package.fields]section of the package in thedrom.tomlfile.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 dateThere are many other substitutions, for example specific to Dune files (like
!{dune-packages}for thedune-projectfile). 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 thepackoption or underscorified version of the package name)!{library-module}: the package module name (packoption 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.gitignorefile!(dune-dirs): a string added in the(dirs ...)stanza of the rootdunefiles to specify which dirs should be scanned.!(makefile-trailer): a trailer added at the end of the generatedMakefile
Currently, the following paren substitutions are used in package skeletons:
!(dune-libraries): a string added in the(libraries ...)stanza of the generateddunefile!(dune-stanzas): a string added in the(executable ...)or(library ...)stanza of the generateddunefile!(dune-trailer): a string added at the end of the generateddunefile
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 conditionskip:TAG: true if the tag should be skippedgen:TAG: true if the tag should not be skippedtrueandfalse: constantsskeleton:is:SKELETON: true is the current skeleton is the argumentkind:is:KIND: true if the kind of the package is the argument (kind is one oflibrary,programorvirtual)pack: true if the package should be packedwindows-ci: true if the project should run Windows CIgithub-organization,homepage,copyright,bug-reports,dev-repo,doc-gen,doc-api,sphinx-target,profile: true if the corresponding value is defined for the projectproject:CONDITION: in a package, check if the condition is true for the project instead