shudd3r / skeletons
Template engine for package skeletons
Installs: 1 337
Dependents: 3
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/shudd3r/skeletons
Requires
- php: ^7.4 || ^8.0
Requires (Dev)
- phpunit/phpunit: ^9.6.22
README
Template engine for package skeletons
Feels like you're repeating yourself every time you start a new project?
This package might help you automate some stuff!
Skeleton packages are used to maintain consistent structure of document (LICENSE, README
etc.) & dev environment files (tool configs, workflows, scripts) across multiple packages & projects.
This library allows building skeleton package scripts capable of:
- Generating file structure for deployed package & local dev environment from skeleton files with built-in placeholder replacements or customized template processing scripts
- Verifying synchronization of existing project with chosen template as a part of continuous integration (CI) process
- Synchronizing mismatched & missing files in existing project built with prepared skeleton script and its template files
- Updating template placeholder values used in existing package files
Basic Usage
Neither applications nor libraries will use this package directly, but as a command line tool of a skeleton package they were built with - either tool application or project's composer dev binary.
To avoid conflicts it is released as a standalone package that doesn't use any production dependencies, and php version compatibility is the only limitation.
Before diving into details you can learn some basics about skeleton scripts by browsing files or playing with skeleton example package.
Skeleton package
Here's a list of main steps to create and use skeleton package - following sections will cover template & script files in more details:
- Install this library as a dependency of your skeleton package using
Composer command:
composer require shudd3r/skeletons
- Create template directory with your skeleton template files
- Add CLI executable script file and
"bin"directive incomposer.jsonpointing to that file:{ "bin": ["my-skeleton-script"] } - Publish skeleton package and require it as a dev dependency in your projects
- Use skeleton as their desired file structure through CLI commands
Executable script file
Entire script that uses this library might look like
example-skeleton
file in shudd3er/skeleton-example repository.
Scripting features:
- Template placeholders are chosen by skeleton creators.
- Each placeholder is assigned to
Replacementabstraction that manages user input, validation and resolving placeholder's (default) value. - Replacement may also provide subtypes for defined placeholder (e.g. escaped slashes for
{namespace}). GenericReplacementfor simple replacements and fluent builder interface for easier instantiation.- Placeholders for
{original.content}with optional default mockup value. - Possibility to customize template handling for individual files through
Templateabstraction. - Synchronization that allows regenerating missing & divergent files.
- Safe file operations - overwritten files are copied into backup directory.
- Filename extension directives that allow:
- including (deactivating) files that could affect deployed skeleton files (e.g.
.gitignore), - handling "local" files, that are not (or cannot be) part of published package,
- using initial "mockup" files that can be removed or developed without breaking skeleton synchronization,
- including (deactivating) files that could affect deployed skeleton files (e.g.
- Directories without specified files required by skeleton are managed by
.gitkeepdummy files.
Setup steps:
-
Instantiate input arguments and application:
namespace Shudd3r\Skeletons; use Shudd3r\Skeletons\Environment\Files\Directory\LocalDirectory; $args = new InputArgs($argv); $package = new LocalDirectory(get_cwd()); $template = new LocalDirectory(__DIR__ . '/template'); $app = new Application($package, $template);
-
Add
Replacementdefinitions for placeholders used in templates:use Shudd3r\Skeletons\Replacements\Replacement; $app->replacement('package.name')->add(new Replacement\PackageName()); $app->replacement('repository.name')->add(new Replacement\RepositoryName('package.name')); $app->replacement('package.description')->add(new Replacement\PackageDescription('package.name')); $app->replacement('namespace.src')->add(new Replacement\SrcNamespace('package.name'));
Simple replacement definitions don't need to be implemented -
GenericReplacementcan be used instead:use Shudd3r\Skeletons\Replacements\Replacement\GenericReplacement; $default = fn () => 'default@example.com'; $validate = fn (string $value) => $value === filter_var($value, FILTER_VALIDATE_EMAIL); $app->replacement('author.email') ->add(new GenericReplacement($default, $validate, null, 'Your email address', 'email'));
It can also be built using fluent builder invoked with
build()method:$app->replacement('author.email') ->build(fn () => 'default@example.com') ->argumentName('email') ->inputPrompt('Your email address') ->validate(fn (string $value) => $value === filter_var($value, FILTER_VALIDATE_EMAIL));
-
Setup custom
Templates\Templateinstantiation for selected template files*:use Shudd3r\Skeletons\Templates\Contents; use Shudd3r\Skeletons\Templates\Template; $app->template('composer.json')->createWith( fn (Contents $contents) => new Template\MergedJsonTemplate( new Template\BasicTemplate($contents->template()), $contents->package(), $args->command() === 'update' ) );
*Template that defines dynamic keys for
MergedJsonTemplate(in case ofcomposer.jsonit would usually benamespaceplaceholder) requires different merging algorithm for updates. If template doesn't use dynamic keys, its third (boolean flag) parameter is not required. -
Run application with
InputArgs:$exitCode = $app->run($args); exit($exitCode);
Command line usage
vendor\bin\script-name <command> [<options>] [<argument>=<value>]
Available <command> values:
init: generates file structure from skeleton template (with backup on overwrite)check: verifies project synchronization with used skeletonupdate: changes current placeholder values (synchronized package required)sync: generates missing & mismatched skeleton files (with backup on overwrite)help: displays help similar to this section
Application <options>:
-i,--interactive: allows providing placeholder values forinitorupdatecommand via interactive shell. When no<argument>=<value>is provided interactive mode is implicitly switched on.-l,--local: may be used to include files not deployed to remote repository if skeleton defines them (like git hooks or IDE settings). This option may be used for all file operations: initialization, validation, synchronization and updates. See.sk_localtemplate filename suffix in Directive suffixes.
Available <argument> names depend on placeholders configured in application. Currently, built-in
placeholders can receive their values from following arguments:
package: package name (Packagist)repo: remote (GitHub) repository namedesc: package descriptionns: project's main namespace
Values that contain spaces should be surrounded with double quotes. For example following command
for example-skeleton
script would update package description:
vendor/bin/example-skeleton update desc="New package description"
When both --interactive option and placeholder arguments are provided, valid argument values will
become default for empty input.
Template files
Project file structure controlled by skeleton will reflect template directory, and placeholders within its files will be replaced. Check out template directory in example-template or one in skeleton example package.
Directive suffixes
Behavior of some template files can be modified by adding a suffix to their names:
.sk_init- files generated at initialization only, not verified. Usually used for example files..sk_local- untracked, local dev environment files. Generated & updated, but not verified on remote environments..sk_file- deactivates files processed by remote workflows. For example.gitignorefile cannot be safely deployed as a part of skeleton, because it might exclude some of its files..sk_dir- similar to.sk_filein context of directories. For example.gitdirectory cannot be deployed. Such directory is expected to contain.sk_localor.sk_initfiles.
Empty directories
Empty directories cannot be committed, but by convention .gitkeep dummy files may be used to
enforce directory structure when no files are specified. Dummy files should be removed when other
files in required directory are present.
Skeleton script will create essential or remove redundant .gitkeep files with init, update
& sync command, and package with redundant or missing dummy files will be marked as invalid
by check operation. Contents of these files are not validated and
placeholders are not replaced.
In example-template
srcandtestsdirectories are required, but on initializationsrc/Example.phpandtests/ExampleTest.phpfiles will be created and.gitkeepfile will be ignored, but when these files are removed (and no other file is added).gitkeepwill become necessary andsynccommand should be used to keep package compliant with the template.
Placeholders
Placeholder consists of its name surrounded by curly braces. Script defines what kind of replacement
given placeholder represents. For example, application configured the following way will replace
{namespace.src} with given namespace value:
$app->replacement('namespace.src')->add(new Replacement\SrcNamespace());
Placeholder subtypes
Replacement, beside direct value may define its subtypes. For example
SrcNamespace replacement defined above will also
replace {namespace.src.esc} with escaped backslashes used in composer.json file, and
PackageName has {*.composer} subtype that gives
normalized packagist package name in lowercase.
Original Content placeholder
{original.content} is a special built-in placeholder that represents places where project specific
text might appear. It's useful especially for README files, where skeleton cannot dictate its entire
content. Template can also define initial/default value for it, and single template file can use
this placeholder multiple times. For example README file that is expected to contain concrete
sections might be defined as follows:
 # {package.name} ### {package.desc} {original.content>>>...Extended description here...<<<original.content} ### Installation composer require {package.name.composer} {original.content} ### Basic Usage {original.content>>>...<<<original.content}
Custom Template processing
Some files that change dynamically throughout project lifetime cannot be handled in a simple,
text expanding way like, for example,README.md. This is where custom templates
might be used.
This package comes with Merged Json Template
as the only one file-based template.
Merged Json Template
This custom template can handle normalization & merging of generated .json template files with
corresponding files existing in developed package like composer.json. In short, skeleton file
can define order of keys, but doesn't have to specify their values. It will also ensure same order
of keys in repeating (list) structures. Adding key in the wrong position will require synchronization
which will merge & normalize skeleton with existing file (and create backup copy). For details
check out MergedJsonTemplateTest file.