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 variableany directory
./share/drom/skeletons/
in one of the ancestor directories (.
,..
,../..
, etc.). This is used to test new skeletons directly fromdrom
sources.$OPAMROOT/plugins/opam-drom/skeletons/
, where$OPAMROOT
defaults to$HOME/.opam/
if undefined. This is used whendrom
is installed as anopam
plugin, but can also be used when installed globally by the user.$OPAM_SWITCH_PREFIX/share/drom/skeletons/
. This is used whendrom
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 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,dune
files, etc)skips
: a list of tags that can be used in thedrom.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 ingit
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 indrom
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 thedrom.toml
, or for a package, in the[fields]
section of thepackage.toml
or in the[package.fields]
section of the package in thedrom.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 dateThere are many other substitutions, for example specific to Dune files (like
!{dune-packages}
for thedune-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 thepack
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 rootdune
files 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 generateddune
file!(dune-stanzas)
: a string added in the(executable ...)
or(library ...)
stanza of the generateddune
file!(dune-trailer)
: a string added at the end of the generateddune
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 conditionskip:TAG
: true if the tag should be skippedgen:TAG
: true if the tag should not be skippedtrue
andfalse
: 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
,program
orvirtual
)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