http://www.perlmonks.org?node_id=678202

Plake is a tool that allows you to maintain sections within a single file (usually, variations of the same code/markup/content) and then assemble variations of that file according to which target you call. It was inspired by Make, used in conjunction with Make (by me), and written in Perl, hence the name "Plake".

It uses a very simple grammar, and allows you to define sections within a given text file, then define targets that assemble the sections you want, and optionally, specify a path where the assembled section will be written to disk as a new file.

Plake runs in two modes.

1. "Top-down/lazy", the default mode, which includes all sections for a given target plus lines from the file that do not have a defined section. The output is assembled top-down. This is useful for when you only need small variations of a file to be made, and want to keep Plake to a minimum.

2. "Stickler", which means that any text not attributed to a named section is ignored, and sections are assembled in the order that they are passed into target(). This means you have specific ideas about how the output will be assembled and don't want Plake to try and be intelligent about it.

NAME

Plake - Target based file assembler

SYNOPSIS

Here's roughly what a Plake file would look like:
!plake: # Denote "code" section, where we setup targets # This is all just Perl code that gets eval'd... my $START = "header base_section"; target( default, # Target name "$START section1 footer", # Sections to include 'path/to/output/file.pl' # Target output path ); target( section2, "$START section2 footer", '' # Output to STDOUT ); !plake. # End section This text will appear if "lazy/top-down" behavior is invoked -- that is, any lines not specifically part of a section are allowed to be output. !plake header HEADER: One liner section !plake base_section: ... included in all assemblages ... ... etc ... !plake section1: ... something, code, markup, comments, whatever ... ... more ... ... etc ... !plake section2: # Start of this section denotes end of previous ... stuff ... ... more ... ... etc ... !plake footer: (c) 2999
And here's generally how you would use the Plake.pm package to process a Plake file:
use Plake qw(target my_builder); # ... Open file... # Parse while(<F>) { Plake::parse($_); } # Setup targets (and whatever else) eval $Plake::CODE; # Build a target. $src is the assembled content. $target is # the path to the output file that should be created, if one # is defined. $stickler_mode = 'on'; my ($target, $src) = Plake::build('target_name', $stickler_mode); # ... Now, do something with $target and $src ...

Plake grammar

Code section
Use !plake: to indicate the start of a ``code'' section, where you declare your targets with the target() method, and maybe even override the default ``build'' method with your own using my_builder().
Standard section
!plake \w+:, e.g. !plake section1:, indicates the start of a multi-line, standard section. The start of a new section flags the end of a previous section.
One-line section
Use !plake \w+, e.g. !plake oneliner (followed immediately by your line) to generate a single line section.
Section ending
Use !plake. to end a section explicitly. You can use it to end standard sections as well as code sections.

I use Plake in a particular software project to generate variations of a Config.pm file for different implementation scenarios.

For instance, I have a demo online, and it uses a real live SQL password I can't include with the default distro. For the default distro, I use "password" for the password. I also have verbose logging turned off for my demo and the default distro, but in my own testing environment, I want it on even though it generates lots of log activity.

These are all minor variations of the single Config.pm file for my project. With Plake, I add a target to my Makefile like

configfiles: plake.pl file=/path/to/Config.plk target=default plake.pl file=/path/to/Config.plk target=demo

and simply "make configfiles" to get the variations of Config.pm for uploading to the demo, adding to the distro, etc. Meanwhile, every orientation is very accessible within the single Config.plk file.

Please don't think that Plake was designed merely for overriding default values in config files (670323). There are certainly better ways for doing this. I see it primarily as a development tool. Here's a few places where I thought it might be useful:

- Setting variations for builds, like what I describe above for the config file. A convenience for me since I have yet to implement a more complex (i.e. overrides) configuration system, but still have to make subtle changes (usually, by hand-editing) for various implementations at various stages of development.

- Assemble C/C++ files for specific platforms, in the stead of #ifdef, etc. The resulting .c/.cpp/.h file would be assembled dynamically when the project was make'd for a given platform, just prior to compilation. The code generated for that platform would be a bit simpler to review, since it only includes code that a person cares about in that build.

- Remove experimental features, stubs, or extra debugging from code prior to generating distros, i.e. "Cleanup".

- Branching, like what source control does. You could keep some client or "branch" specific features out of a specific build, but still maintain it in a single file.

- Template variations, like letter writing. Instead of a single boiler plate template, you have targets like "standard_greeting", "enthusiastic_greeting", "familiar_greeting", etc.

- Target-based programming for Perl. Sort of a side-effect, and one I don't see all the ramifications of, but you could use Plake to assemble code targets wholly or partially independent of each other by storing Perl code in a Plake file and doing an eval against the assembled content for a given target. (Just think -- you could keep your entire project of hundreds of modules and code files all in one single, massive text file! I can see everyone lining up now... :)

Plake file:
!plake: target('helowrld', "helowrld", ''); target('oneplus', "oneplus", ''); target('both', "helowrld oneplus", ''); !plake helowrld print "helowrld\n"; !plake oneplus: # Add value to one print 1+3.14, "\n";
Eval'ing code from targets:
perl t\plakeval.pl file="t/plakeval.plk" target="helowrld" helowrld perl t\plakeval.pl file="t/plakeval.plk" target="oneplus" 4.14 perl t\plakeval.pl file="t/plakeval.plk" target="both" helowrld 4.14

If you've made it this far, you might want to check it out yourself:

http://www.arbingersys.com/dnlds/Plake.zip

One thing I'm still pondering: Should a target be able to include other targets? This would be very Make-like, with real dependencies, but I don't know how useful it would actually be.

A blog among millions.

Replies are listed 'Best First'.
Re: RFC: Plake, a target-based file assembler
by dragonchild (Archbishop) on Apr 03, 2008 at 17:58 UTC
    One thing I'm still pondering: Should a target be able to include other targets? This would be very Make-like, with real dependencies, but I don't know how useful it would actually be.

    Add it. Remember - dependency-chaining allows you to say "Only work when that other thing works." That's not useful?


    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

      The reason I wasn't sure is because Plake, used as a simple file assembler, is only assembling static sections. So there is no "working" in this sense, i.e. nothing really can fail. The sections just get stuck together.

      If Plake gets used in the "target-based programming" way then I can see that dependency-chaining would become more valuable.

      Really, it wouldn't be hard to add, outside of the problem that would arise when a target and a section share the same name. Thanks.

      A blog among millions.
A reply falls below the community's threshold of quality. You may see it by logging in.