civicrm/composer-compile-lib

Small library of compilation helpers

v0.7 2022-12-19 21:16 UTC

This package is auto-updated.

Last update: 2024-04-20 00:07:31 UTC


README

This package provides a handful of small tasks and helpers for use with composer-compile-plugin.

Design guidelines for this package:

  • To ensure easy operation in a new/unbooted system:
    • Use basic functions and static methods
    • Use primitive data sources (such as static JSON files)
  • To ensure that compilation steps report errors:
    • Every task/function must throw an exception if it doesn't work.
  • To allow pithy tasks:
    • If a task is outputting to a folder, and if the folder doesn't exist, then it should auto-create the folder.

The primary purpose here is demonstrative - to provide examples. Consequently, it is fairly minimal / lightweight / loosely-coupled. There is no dependency on CiviCRM. Conversely, CiviCRM packages may define other tasks which are not in this library.

Require the library

All the examples below require the civicrm/composer-compile-lib package. Load via CLI:

composer require civicrm/composer-compile-lib:'~0.2'

Or via composer.json:

  "require": {
    "civicrm/composer-compile-lib": "~0.2"
  }

Task: SCSS

In this example, we generate a file dist/sandwich.css by reading scss/sandwich.scss. The file may @import mixins and variables from the ./scss/ folder.

{
  "extra": {
    "compile": [
      {
        "title": "Prepare CSS (<comment>sandwich.css</comment>, <comment>salad.css</comment>)",
        "run": "@php-method \\CCL\\Tasks::scss",
        "watch-files": ["scss"],
        "scss-files": {
          "dist/sandwich.css": "scss/sandwich.scss",
          "dist/salad.css": "scss/salad.scss"
        },
        "scss-imports": ["scss"]
        "scss-import-prefixes": {"LOGICAL_PREFIX/": "physical/folder/"}
      }
    ]
  }
}

Note that a "task" simply calls a static PHP method (@php-method \\CCL\\Tasks::scss) with the JSON data as input. You can also call the method in a PHP script. For example, we could define a task based on a script:

{
  "extra": {
    "compile": [
      {
        "title": "Prepare CSS (<comment>sandwich.css</comment>, <comment>salad.css</comment>)",
        "run": "@php-script bin/compile-scss"
      }
    ]
  }
}

The following script generalizes the example from before -- it maps any SCSS files (scss/*.scss) to corresponding CSS files (dist/#1.css). This file-list is passed into \CCL\Tasks::scss for processing:

\CCL::assertTask();

$files = \CCL::globMap('scss/*.scss', 'dist/#1.css', 1);
\CCL\Tasks::scss([
  'scss-files' => $files,
  'scss-imports' => ['scss']
  'scss-import-prefixes' => ['LOGICAL_PREFIX/' => 'physical/folder/']
]);

Note that this implementation of \CCL\Tasks::scss() is fairly opinionated - it combines scssphp with php-autoprefixer. The output is written as two files, a larger files (*.css) and a smaller file (*.min.css).

Task: PHP Template

In this example, we use a PHP template to generate another PHP file. Specifically, we create Sandwich.php using the specification from Sandwich.json and EntityTemplate.php:

{
  "extra": {
    "compile": [
      {
        "title": "Sandwich (<comment>src/Sandwich.php</comment>)",
        "run": "@php-method \\CCL\\Tasks::template",
        "watch-files": ["src/Entity"],
        "tpl-items": [
          "src/Entity/Sandwich.php": "src/Entity/Sandwich.json",
          "src/Entity/Salad.php": "src/Entity/Salad.json"
        ],
        "tpl-file": "src/Entity/EntityTemplate.php"
      }
    ]
  }
}

As in the previous example, the task is simply a PHP method (@php-method \\CCL\\Tasks::template), so it can be used from a PHP script. The following script would extend the pattern, mapping any JSON files (src/Entity/*.json) to corresponding PHP files (src/Entity/#1.php):

$files = \CCL::globMap('src/Entity/*.json', 'src/Entity/#1.php', 1);
\CCL\Tasks::template([
  "tpl-file" => "src/Entity/EntityTemplate.php",
  "tpl-items" => $files,
]);

Functions

PHP's standard library has a lot of functions that would work for basic file manipulation (copy(), rename(), chdir(), etc). The problem is error-signaling -- you have to explicitly check error-output, and this grows cumbersome for improvised glue code. It's more convenient to have a default stop-on-error behavior, e.g. throwing exceptions.

symfony/filesystem provides wrappers which throw exceptions. But it puts them into a class Filesystem which, which requires more boilerplate.

For the most part, CCL simply mirrors symfony/filesystem using static methods in the CCL class. Compare:

// PHP Standard Library
if (!copy('old', 'new')) {
  throw new \Exception("...");
}

// Symfony Filesystem
$fs = new \Symfony\Component\Filesystem\Filesystem();
$fs->copy('old', 'new');

// Composer Compile Library
\CCL::copy('old', 'new');

This is more convenient for scripting one-liners. For example, the following tasks do simple file operations. If anything goes wrong, they raise an exception and stop the compilation process.

{
  "extra": {
    "compile": [
      {
        "title": "Smorgasboard of random helpers",
        "run": [
          // Create files and folders
          "@php-eval \\CCL::dumpFile('dist/timestamp.txt', date('Y-m-d H:i:s'));",
          "@php-eval \\CCL::mkdir('some/other/place');",

          // Concatenate a few files
          "@php-eval \\CCL::dumpFile('dist/bundle.js', \\CCL::cat(glob('js/*.js'));",
          "@php-eval \\CCL::chdir('css'); \\CCL::dumpFile('all.css', ['colors.css', 'layouts.css']);",

          // If you need reference material from another package...
          "@export TWBS={{pkg:twbs/bootstrap}}",
          "@php-eval \\CCL::copy(getenv('TWBS') .'/dist/bootstrap.css', 'web/main.css')"
        ]
      }
    ]
  }
}

The full function list:

// CCL wrapper functions

function chdir(string $dir);
function glob($pat, $flags = null);

// CCL distinct functions

function cat($files);
function mapkv($array, $func);
function globMap($globPat, $mapPat, $flip = false);

// Symfony wrapper functions

function appendToFile($filename, $content);
function dumpFile($filename, $content);
function mkdir($dirs, $mode = 511);
function touch($files, $time = null, $atime = null);

function copy($originFile, $targetFile, $overwriteNewerFiles = true);
function mirror($originDir, $targetDir, $iterator = null, $options = []);
function remove($files);
function rename($origin, $target, $overwrite = false);

function chgrp($files, $group, $recursive = false);
function chmod($files, $mode, $umask = 0, $recursive = false);
function chown($files, $user, $recursive = false);

function hardlink($originFile, $targetFiles);
function readlink($path, $canonicalize = false);
function symlink($originDir, $targetDir, $copyOnWindows = false);

function exists($files);

function tempnam($dir, $prefix);

For more details about each function, see CCL\Functions and symfony/filesystem.