balbuf/wasp

Write simple YAML to produce working WordPress code!

v0.11 2018-06-20 01:22 UTC

This package is auto-updated.

Last update: 2024-11-29 05:39:18 UTC


README

Wasp is a tool for turning simple YAML configuration files into working WordPress code. Using a variety of "handlers", wasp parses your configuration file and produces the necessary code to register post types, taxonomies, menus, and much more! Wasp reduces the tedious nature of this work by leveraging sensible defaults and accepting user-defined defaults for complete flexibility.

Wasp is best employed as part of your build process, which means only the YAML file(s) would be stored in your version control system. In the course of a build process, wasp parses the configuration file and generates a PHP file that can act as the functions.php file of a theme, the main plugin file of a plugin, the entirety of an mu-plugin, or a file that is included by any of the former. However, wasp can also be used to generate code that you can use as a starting point for your project, allowing you to save and modify it from there. As such, if at any point you want to ditch wasp, you'd simply need to generate your code, save it, and remove wasp from your project. Wasp gives you the benefit of a theme or plugin framework without ever having to commit!

Installing

The preferred way to install wasp is via Composer:

$ composer require balbuf/wasp --dev

Wasp will now be available in composer's "bin dir", which is located in vendor/bin by default. You should be able to run wasp via:

$ vendor/bin/wasp

Wasp doesn't come with any handlers out of the box, so that you can leverage only the features you want and nothing more. In order to get started, you'll need to install one or more wasp plugins that contain handlers. All of the essential handlers for basic WordPress configuration can be found in balbuf/wasp-core:

$ composer require balbuf/wasp-core --dev

Creating a Config YAML File

The config YAML file can be named anything you'd like and stored anywhere, too. When you run the generate command, you will specify the location of your config file. The only stipulation is that the file contain valid YAML and data that is properly formatted for each of your handlers.

The properties you set inside of your YAML file will depend upon the handlers you intend to use. Typically, one top-level property will be used by a single handler, but sometimes multiple handlers will act upon the same property for different purposes, or a handler may reference properties outside of the main one it handles to obtain additional information.

All config files should contain an wasp property, which defines details about the file itself, e.g.:

wasp:
  # a unique prefix used to differentiate this project from others
  prefix: wasp_example

  # location of this file relative to the file root of the project
  dir: inc

  # how wasp should determine the public URL that corresponds to the file root of the project
  url_context: plugin

Additional properties will depend on the handlers you wish to use, and you should consult the documentation for the handlers' respective wasp plugin(s).

Generating Code

Generating code requires an input file path (to your YAML config file) and an output file path (to the generated PHP code), e.g.:

$ vendor/bin/wasp generate config.yml themes/my-theme/functions.php

The input file path and/or output file path can be replaced by - to read data from STDIN or write data to STDOUT, respectively.

Some handlers that work with additional files in your project (e.g. handlers that help with including files, enqueing front-end assets, etc.) will need to know the path to your project's file root. The file root, for example, would be the directory that contains your functions.php file for a theme or the main plugin file for a plugin. Knowing the location of the file root on the filesystem and the relative location of your generated file within (specified via the wasp.dir property of your config file) allows handlers to analyze files on the filesystem and properly reference them from the generated PHP file.

In most cases, the file root can be discerned based on the output file path provided via the command line, in combination with the wasp.dir property specified in the config file. However, in the case of writing to STDOUT, explicitly specifying the project's file root may be required. For instance, the example above could be rewritten as:

$ vendor/bin/wasp generate config.yml - --root=themes/my-theme > themes/my-theme/functions.php

If not specified or discernable from the output path, the file root defaults to the current working directory.

Adding Handlers

Handlers are typically added to your project by installing additional wasp plugins via composer. When wasp runs, it analyzes your project's composer.lock file to identify and execute all wasp plugins.

Considering all plugins/handlers installed in your project are activated by default, at times you may wish to disable certain plugins and/or handlers when generating your code.

  • To disable a plugin, use the --disable-plugin= option along with the composer package name for the plugin
  • To disable a handler, use the --skip-handler= option along with the handler's machine name

Either option may be repeated to disable multiple plugins and/or handlers.

Wasp Config Properties

The wasp top-level property contains information about the project and controls how code is generated.

Advanced Config

Wasp uses Twig to process its config values, allowing the user to reference other properties within the config and perform some basic value processing via Twig's filters, functions, etc. In order to use Twig in your config property values, you must enclose some or all of the value in Twig template delimeters: {{ }} for expressions and {% %} for more complex control structures (loops, logic, etc.). (Note that when a property value starts with a Twig template, the value must be quoted, otherwise the YAML parser will get confused and interpret the value as an inline object, e.g. property: '{{ template }}' instead of the invalid property: {{ template }}.)

Special Variables

Within a config property template, there are a number of special variables which allow access to surrounding property values, the current property chain, etc.

Because Twig is meant to be a templating engine which ultimately produces strings, special handling is necessary to produce a non-string value for a property. An additional variable called output is provided which has a setValue() method to override the resolved value of the property template, e.g. {% do output.setValue(["this", "produces", "an", "array"]) %}. Similarly, the special this, top, and vars variables return strings for other property values by default. To access a property value of another type, the property must be appended with .getValue(), e.g. this.sibling_prop.getValue(); as a shortcut, the property name can be appended with () to access the raw value, e.g. this.sibling_prop().

User Defaults

For many handlers whose property structure is an associative array of associative arrays, wasp provides a convenient way to provide default values to fill in those second-level associative arrays. To illustrate this type of property structure:

handler_property:

  thing1:
    name: Thing 1
    color: red

  thing2:
    name: Thing 2

Notice how the handler's property is an associative array where the key (e.g. thing1) represents a slug (machine friendly identifier) for the item and the value of the item is another associative array containing the item's properties and values (e.g. name, color, etc.). When the item arrays have many possible properties, it's not uncommon to find yourself repeating the same value again and again for each item.

In order to reduce repetition, you can add a default item whose properties are filled in for all the other items when not explicitly set, e.g.:

handler_property:

  default:
    color: blue
    name: Untitled

  thing1:
    name: Thing 1
    color: red

  thing2:
    name: Thing 2

Since thing2 did not specify a color property, it inherits the color value from the default item (blue).

Self-referential Property Templates

Another feature of the default item is the ability to reference the same property in the main items when the items' property values are resolved. Instead of just providing default values, the default item can also alter the values of the properties in each main item. To make a self-referential template, the this keyword can be used. In the following example, we use the default post_type property to add a prefix to the post type slug:

post_types:

  default:
    post_type: wasp_{{ this }}

  event:
    post_type: event

  testimonial:
    post_type: testimonial

When this config is resolved, the post_type property for our items will be wasp_event and wasp_testimonial, respectively.

Recipes

The advanced config using Twig can be a little complicated, so here are some example "recipes" to help illustrate the potential and allow you to get the most out of your config file with the least amount of work.

Conditional Prefix

The prefixed-post-type example above is great, but there are instances where you might need to use the post_type wasp handler to modify a built-in post type or one provided by a plugin. In these instances, adding a prefix to the post type value would be incorrect. We can make use of the env object to turn off the prefix for certain items, e.g.:

post_types:

  default:
    post_type: '{% if not env.noPrefix %}wasp_{{ this }}{% endif %}'

  event:
    post_type: event

  testimonial:
    post_type: testimonial

  post:
    post_type: post{% do env.set('noPrefix', true) %}

Now the resolved post_type properties will be wasp_event, wasp_testimonial, and post.

This could also be achieved by adding a custom property to the item:

post_types:

  default:
    post_type: '{% if not this.no_prefix %}wasp_{{ this }}{% endif %}'

  event:
    post_type: event

  testimonial:
    post_type: testimonial

  post:
    post_type: post
    no_prefix: true

This can make the config look a little cleaner, but the possible disadvantage is that the no_prefix value will be passed on to any handlers. In most cases the handler will ignore any properties it isn't expecting, but the custom property could cause unintended consequences with some handlers.

Suffix with Default Value

Suppose you want to leverage a self-referential template while still providing a default value in case the property was not set in one of the main items. This is possible, too! Take this imaginary images handler, where we want to have a .jpeg file extension automatically added to the file property value, while still providing a fallback file value:

images:

  default:
    file: '{{ ( this ?: "placeholder" ) ~ ".jpeg" }}'

  pic1:
    file: kittens
    alt: Kittens playing!

  pic2:
    file: puppies
    alt: Puppies playing!

  pic3:
    alt: Image coming soon!

When the config is resolved, the values of the file property will be kittens.jpeg, puppies.jpeg, and placeholder.jpeg.

Default Array Values

When specifying script dependencies, it's not uncommon for many or all to require jquery. We can set this script (and any others) as the default base dependencies with the use of a default object and a custom property. The scripts handler will look for the dependencies property, so we will leverage that property in the default item and a custom property (additional_dependencies) to merge our base script dependencies with any additional dependencies for each item:

scripts:

  default:
    dependencies: '{% set deps = ["jquery"] %}{% do output.setValue(this ?: (this.additional_dependencies() is iterable ? this.additional_dependencies() | merge(deps) : deps)) %}'

  main:
    additional_dependencies:
      - jquery-ui

  navigation:
    dependencies:
      - underscore

For the main item, it declares additional dependencies of jquery-ui, causing it to use the default base dependencies and producing ['jquery', 'jquery-ui']. For the navigation item, we don't need jquery, so we use the dependencies property directly; the logic in our default dependencies property respects this and produces ['underscore'] for this item.

Extending

TODO: creating plugins, creating handlers, creating compilables, using compilables, including files, etc.