Plugins ======= - [Why plugins ?](#why-plugins-) - [Capitalize on your custom entity types](#capitalize-on-your-custom-entity-types) - [Functional code growing](#functional-code-growing) - [Need of very specific post actions developed in Ruby](#need-of-very-specific-post-actions-developed-in-ruby) - [Enhance `power_stencil` command line](#enhance-power_stencil-command-line) - [What are plugins ?](#what-are-plugins-) - [Creating plugin local to the project](#creating-plugin-local-to-the-project) - [Plugin creation](#plugin-creation) - [Adding new subcommands to `PowerStencil`](#adding-new-subcommands-to-powerstencil) - [Providing entity types and templates](#providing-entity-types-and-templates) - [Providing custom build process](#providing-custom-build-process) - [Plugin capabilities and structure](#plugin-capabilities-and-structure) - [Using plugins available as gems](#using-plugins-available-as-gems) - [Conclusion](#conclusion) [:back:][Documentation root] # Why plugins ? If you read everything about [entities], [templates] and [builds], you may wonder why you may ever need to create plugins. There are nevertheless good reasons to build real plugins. ## Capitalize on your custom entity types You have designed super nicely crafted entity types that you may want to **re-use** in other projects. ## Functional code growing If you start developing a lot of code around your entity types, it may become a bad idea to keep everything within your entity types code and you may want to **structure** your code, add tests to it etc... ## Need of very specific post actions developed in Ruby If calling an executable after the [build][builds] process completed is not enough and you want some custom Ruby to be run right after the [build][builds] completion. ## Enhance `power_stencil` command line If you want to have new custom sub-commands or options available. # What are plugins ? Plugins are actually Ruby Gems with a specific structure. Plugins can be part of the project or provided as a separated stand-alone Gem. The normal process would be to begin with a plugin within the project and once you're ok with its features you may release it as a standalone Ruby Gem. - Plugins local to the project are automatically taken in account. - To use plugins provided as Gems you have to set the `:plugins:` array property in the `.ps_project/versioned-config.yaml` :warning: Plugins provided as standalone Gems are not thoroughly tested, for the time being, you may use plugins local to the project. # Creating plugin local to the project ## Plugin creation There is a command provided for that, that will create a whole plugin skeleton. ```shell $ power_stencil plugin --create myplugin Generated new plugin 'myplugin' $ ll .ps_project/plugins/myplugin total 60 drwxrwxr-x 6 laurent laurent 4096 août 28 14:38 ./ drwxrwxr-x 3 laurent laurent 4096 août 28 14:38 ../ drwxrwxr-x 2 laurent laurent 4096 août 28 14:38 bin/ -rw-rw-r-- 1 laurent laurent 3228 août 28 14:38 CODE_OF_CONDUCT.md drwxrwxr-x 3 laurent laurent 4096 août 28 14:38 etc/ -rw-rw-r-- 1 laurent laurent 163 août 28 14:38 Gemfile -rw-rw-r-- 1 laurent laurent 113 août 28 14:38 .gitignore drwxrwxr-x 3 laurent laurent 4096 août 28 14:38 lib/ -rw-rw-r-- 1 laurent laurent 1077 août 28 14:38 LICENSE.txt -rw-rw-r-- 1 laurent laurent 1491 août 28 14:38 psplugin_myplugin.gemspec -rw-rw-r-- 1 laurent laurent 117 août 28 14:38 Rakefile -rw-rw-r-- 1 laurent laurent 1770 août 28 14:38 README.md -rw-rw-r-- 1 laurent laurent 53 août 28 14:38 .rspec drwxrwxr-x 2 laurent laurent 4096 août 28 14:38 spec/ -rw-rw-r-- 1 laurent laurent 88 août 28 14:38 .travis.yml ``` You can see that it looks a lot like a Ruby Gem, and it's normal because it is ;-)... So what was the impact on the project. If you do a `power_stencil info` you see a new part appeared in the report ``` -------------------------------------------------------------------------------- Plugins: --> Plugin 'myplugin' has following capabilities: - config: true - command_line: true - processors: true - build: true - dsl: true - entity_definitions: true - templates: true ``` Each of the lines correspond to what is called a _plugin capability_. You can get the same information by issuing: $ power_stencil plugin --list ``` 1 plugin found to be used in this project. - myplugin (in '/tmp/tst project/.ps_project/plugins/myplugin/lib/myplugin.rb') ``` Or with the capabilities information: $ power_stencil plugin --list -v ``` 1 plugin found to be used in this project. - myplugin (in '/tmp/demo/.ps_project/plugins/myplugin/lib/myplugin.rb') config: true command_line: true processors: true build: true dsl: true entity_definitions: true templates: true ``` ## Adding new subcommands to `PowerStencil` Obviously by default the plugin does nothing very useful, yet it defined some placeholders and demo entity type. For example in the output here above it says `command_line: true`. What could it mean ? Let's try to see to help: ```shell $ power_stencil --help PowerStencil is the Swiss-army knife templating workflow for developers and ops. -- Options --------------------------------------------------------------------- -v, --verbose Displays extra runtime information. -h, --help Displays this help. --program-version, -V, --version Displays program version. --simulate Will not perform actual actions --debug Debug mode --debug-on-err, --debug-on-stderr Sends debugging to SDTERR --log-level Defines the level of logging (0 to 5) --log-file Specifies a file to log into --truncate-log-file Truncates the log file (appends by default) --project-path Specifies a startup path to use instead of '.' --auto Bypasses command-line confirmations to the user -------------------------------------------------------------------------------- Following subcommands exist too: For more information you can always issue sub_command_name --help... -------------------------------------------------------------------------------- * init: Initializes a PowerStencil repository ... * info: Generic information about the repository ... * plugin: Manipulates plugins ... * get: Query entities from repository ... * shell: Opens a shell to interact with entities ... * check: Check repository entities consistency ... * create: Creates entities in the repository ... * edit: Edit entities from repository ... * delete: Delete entities from repository ... * build: Builds entities ... * myplugin: Does nothing useful Added by plugin myplugin ``` You can see that the plugin brought a new sub-command `myplugin`. Let's try it: ```shell $ power_stencil myplugin MYPLUGIN PLUGIN WAZ HERE !! ``` Wow it worked ! Useless, but worked ! But it's a good demo on how to create your own subcommands in `PowerStencil`. ## Providing entity types and templates Of course you can provide new entity types and templates templates within plugin. The plugin created contains a demo entity type which brings its own template template (and even a custom build, but we will see that in next paragraph). You can see in the output of `power_stencil info` (in _availaible entity types_) that a new entity type has been added to the project by the plugin: `myplugin_entity` ``` - Type 'myplugin_entity' --> Myplugin::EntityDefinitions::MypluginEntity (template-template path: '/tmp/demo/.ps_project/plugins/myplugin/etc/templates/myplugin_entity') ``` and you can see as well that it provides a template template. So let's create a new entity of this type... ``` $ power_stencil create myplugin_entity/test -v Creating new entity 'myplugin_entity/test' Entities analysis completed. Created 'myplugin_entity/test' ``` We can check the directory `myplugin_entity/test`, but actually a new entity has been created and provided some templates. we can check using `power_stencil check myplugin_entity/test`: ``` $ power_stencil check myplugin_entity/test 'myplugin_entity/test': - Storage path : '/tmp/demo/.ps_project/entities/myplugin_entity/test.yaml' - Templates path : '/tmp/demo/myplugin_entity/test' - Status : Valid - Buildable : true ``` So it used the template templates to generate templates in `myplugin_entity/test` So far so good, we can provide entity types and templates from a plugin... :+1: ## Providing custom build process As we can see in the previous output of `power_stencil check myplugin_entity/test`, the entity is said to be `buildable`. But to demonstrate the possiblity to define custom builds within a plugin this entity is defined to be buildable by the plugin, meaning that after the templates are rendered, the plugin is involved to perform some post process actions. How is it achieved ? First lets have a look at the entity type definition for `myplugin_entity`. `power_stencil info` shows that the class of the entity type is `Myplugin::EntityDefinitions::MypluginEntity` and we can find it in `.ps_project/plugins/myplugin/lib/myplugin/entity_definitions/myplugin_entity.rb`: ```ruby module Myplugin module EntityDefinitions class MypluginEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity entity_type :myplugin_entity buildable_by :myplugin end end end ``` Here we see a standard entity type definition how we already saw previously except that instead of the standard `buildable` directive, we find a `buildable_by :myplugin` directive. This is simply saying that the build for this entity type is delegated to the plugin `myplugin`. **:information_source: Entity types provided by plugins do not have to be built necessarily by the plugin they belong to !** Here the idea is just demonstrate the possibility to create custom builds. As a reminder the build process is the following: ![simple-flow-image] When we talk about custom builds we actually talk about the last step. The core `PowerStencil` engine provides only one `buildable` entity which is the `simple_exec` entity type. And its [post-process] action was simply to call the script (main.sh) it generated. Here to demonstrate the possibility to create a custom build, the post-process action will simply display the template attached to `myplugin_entity` entity type. If we have a look in `myplugin_entity/test` we see it contains a template named `message.txt`, and its content is: ```erb This message has been generated on the <%= Time.now %> This is an example of what you can do, and how much PowerStencil is customizable ! This text is the result of a full build process of the entity '<%= build_target.as_path %>' by the plugin '<%= build_target.buildable_by %>'. ``` Ok, knowing that, let's finally build the `myplugin_entity/test` we created in the previous paragraph: ``` $ power_stencil build myplugin_entity/test This message has been generated on the 2019-10-24 18:37:46 +0200 This is an example of what you can do, and how much PowerStencil is customizable ! This text is the result of a full build process of the entity 'myplugin_entity/test' by the plugin 'myplugin'. - 'myplugin_entity/test' has been correctly built ``` The last line is actually displayed by the build process, but see above that building the entity resulted in displaying the result of the _detemplatization_ ... So a post process actually occurred. Where does it come from ? We can find that in the main file of the plugin: `.ps_project/plugins/myplugin/lib/myplugin.rb`: ```ruby require 'myplugin/version' require 'myplugin/plugin_helper' require 'myplugin/myplugin_processor' require 'myplugin/dsl/myplugin_dsl' module Myplugin def self.post_build_hook(built_entity, generated_files_path) # This is an example of what you could do after files are generated, ie pretty much anything... case built_entity.type when :myplugin_entity generated_file = File.join generated_files_path, 'message.txt' puts File.readlines(generated_file) else raise PowerStencil::Error, 'Plugin myplugin doesnt know how handle the build of a myplugin_entity entity type !' end end end ``` This is pretty self explanatory. It contains only one method named `self.post_build_hook` and it receives as parameter: * The entity you are building. * The path to where the templates have been _detemplatized_. :information_source: So it's really easy to implement whatever you want there and use information from both the entity itself and the generated files to implement something as complew as you want. Here the code simply reads the generated `message.txt` file and displays it... # Plugin capabilities and structure So the `power_stencil plugin --create` generated a fully working plugin, implementing all the features (actually for the time being only custom dsl is not yet implemented) you could define in a template. The generated plugin has the structure of a legit ruby gem, and this is not by chance, this is to prepare the fact that you may want to create a stand-alone plugin gem that you you may want to re-use in different projects. But this is not mandatory. Actually the only mandatory file for a plugin to be valid is the file `etc/plugin_capabilities.yaml` in the plugin directory. This file declares what the plugin is able to do. For the `myplugin` we just created it contains: ```yaml --- # This file defines the capabilities exposed by the plugin. This file is mandatory # for a plugin to be considered as valid. # Define the main module of the plugin. In this module you can define the build method # if the plugin exposes a build (actually a post build hook occurring after the files # are "detemplatized") :plugin_module: Myplugin # In the processors defined in the hereunder hash you map a subcommand with the processor # that will handle it. A plugin can define as many subcommands as needed, or none... # Hash keys should match sub-commands declared in the `command_line.yaml` file and processor # classes should expose an `execute` method. :processors: myplugin: Myplugin::Processor # This is the name of the method called after the files are detemplatized. This method has # to be a module method in the `plugin_module` module. :build: post_build_hook # If a dsl module is declared it will be injected in the DSL available in the shell or templates :dsl: - Myplugin::Dsl::MypluginDsl # These are the files (array) defining entity types. These files should not be already # required by the plugin as they will be required by the framework when needed. :entity_definitions: - myplugin/entity_definitions/myplugin_entity # A list of root directories where you can find templates in directories named from the # entity types they are attached to. :templates: - etc/templates ``` As it is quite self-documented, I won't too much elaborate on the meaning of each of the fields. So you see that the plugin generated by the `power_stencil plugin --create` command is actually fully featured, providing all possible extras, in order to demonstrate the possibilities as well as ease your job, as you just have to fill the blanks or remove things you don't wan't.create But a plugin could also actually provide ... nothing. Let's create a plugin fully manually instead of using the `power_stencil plugin --create`. From the root of the project, let's create the directory: $ mkdir -p .ps_project/plugins/minimal/etc (of course the command to create the directory may be different on your system). Then here let's create a `plugin_capabilities.yaml` file with the following content: ```yaml --- :plugin_module: null :processors: null :build: null :dsl: null :entity_definitions: null :templates: null ``` Bam ! We just created a more than useless yet valid plugin ! You can check that in the output of `power_stencil info`: ``` --> Plugin 'minimal' has following capabilities: - config: false - command_line: false - processors: false - build: false - dsl: true - entity_definitions: false - templates: false ``` All the code you may write has to be within the `lib` subdirectory, so let's create it: $ mkdir -p .ps_project/plugins/minimal/lib And create there a file named `minimal_entity.rb` with the following content: ```ruby class Minimal < PowerStencil::SystemEntityDefinitions::ProjectEntity entity_type :minimal end ``` Then modify the `plugin_capabilities.yaml` file with the following content: ```yaml --- :plugin_module: null :processors: null :build: null :dsl: null :entity_definitions: - minimal_entity.rb :templates: null ``` _Et voilà_, we have a plugin just providing new entity type, and it is fully working. We can create a new entity of this type: ``` $ power_stencil create minimal/test Created 'minimal/test' $ power_stencil get minimal/test --- MinimalPlugin::Minimal: :type: :minimal :fields: :name: test ``` All of this to demonstrate that a plugin can be almost anything, even something very minimalistic, but nevertheless you should keep on using the `power_stencil plugin --create` command and benefit from the structure it brings, as well as the possibility to become a stand-alone plugin gem... # Using plugins available as gems Any plugin you have created using the `power_stencil plugin --create` command is directly eligible to become a gem. **If you release a plugin you created within a project as a gem (by doing `bundle exec rake release` from within the plugin directory, like for any standard gem :+1:), you can then re-use your plugin from any other `PowerStencil` project !** All you have to do for that is to declare it in the `.ps_project/versioned-config.yaml` config file by adding an array `:project_plugins`: ```yaml ... :project_plugins: - my_awesome_plugin1 - my_awesome_plugin2 - my_awesome_plugin3 ... ``` Now if you try to run power_stencil you may face an error if the gem is not locally installed: ``` ▶ power_stencil info -------------------------------------------------------------------------------- PROJECT REPORT -------------------------------------------------------------------------------- General information: Program aborted with message: 'Cannot find plugin 'my_awesome_plugin1'. Try 'power_stencil plugin --install''. Use --debug option for more detail (see --help). ``` Here you have the choice to either manually install each of your plugins manually by using the regular gem program: $ gem install my_awesome_plugin1 my_awesome_plugin2 my_awesome_plugin3 But `PowerStencil` provides a convenient command to install/update your plugins: ``` $ power_stencil plugin --install Fetching: my_awesome_plugin1-0.1.0.gem (100%) Fetching: my_awesome_plugin2-0.1.1.gem (100%) Fetching: my_awesome_plugin3-1.2.3.gem (100%) Installed plugin 'my_awesome_plugin1' (version: 0.1.0) Installed plugin 'my_awesome_plugin2' (version: 0.1.1) Installed plugin 'my_awesome_plugin3' (version: 1.2.3) ``` **:star2: ::+1: You can now verify using `power_stencil info` or `power_stencil plugin --list`, that the plugins have been installed and that any feature they provide is now available to your project.** # Conclusion The documentation for plugins is not fully complete, so you are encouraged to read the code. Some official plugins are under development, and the documentation will be improved along their development... **:warning: As opposed to the rest of `PowerStencil`, the functionnality is nevertheless not completely frozen. This will be the case once `PowerStencil` turns 1.0.0.** but plugins are close to their final release, and anything above version 0.6 is already really usable. [:back:][Documentation root] [Documentation root]: ../README.md "Back to documentation root" [entities]: entities.md "Entities in PowerStencil" [builds]: builds.md "Builds in PowerStencil" [templates]: templates.md "Templates in PowerStencil" [post-process]: builds.md#post-process-actions "post-process actions in builds" [simple-flow-image]: images/power-stencil-simple-flow.svg