Skip to content
/ fashion Public
forked from braddillman/fashion

A utility for simple model transformation and source code generation.

License

Notifications You must be signed in to change notification settings

zwcdp/fashion

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fashion

Fashion is a command line utility for simple model transformation and source code generation, aimed at a target audience of developers.

The problem

It only takes a few lines of code and a template library to throw together an ad hoc code generator. Many languages are suitable for writing transforms and generators. Models need not be complicated, or in strict formats like UML or MOF, be graphical, or even be usable by non-developers.

The problem is managing a growing number ad hoc models, transformations and code generators - and their dependencies.

The fashion solution

The three-legged stool of fashion:

  • A model is just a structured input file.
  • A transform is just a simple script.
  • A generator just substitutes values from a model into a template.

Fashion introduces a little structure, convention and metadata to make transformations and generators more managable. Python was selected as the language for transformation due to its' popularity and that no compilation step is required. Rather than introduce a new language for transformation, just learn python.

Models

A model in fashion contains structured data, but no structure is prescribed. Typically a model resembles a key:value map like a python dictionary, but many structures are possible. A fashion model is not limited to UML or MOF-like formats. Fashion models are intended to be simple for developers to create, edit, diff and version control; they aren't intended to be used by non-developers.

Transforms

A transform:

  • reads 0 or more input models,
  • produces 0 or more output models, (transformation)
  • generates 0 or more output files using 0 or more templates. (generation)

"0 or more" means there is no requirement for a transform to either read input produce output. "1 or more" is more typical.

A single transform could do any combination of both transformation and generation. It is up to you to keep models and transform scripts at a size you decide is managable.

Templates

A template is a Mako template, which could look very similar to a source code file written in some programming language, with placeholders and looping driectives (in Mako syntax).

Hierarchy of models

Note that templates are easier to read and contain less logic if the model they use is simple, and designed specifically for that template. A design intent of fashion is to use a hierarchy of simple models from very abstract, through more specific, to the final model which is specific to one template.

Installation

Fashion is a straightforward python 3.5 application.

prerequisites

  • install python 3.5+

install from pip

Fashion is available from PyPi under the name fashionModel (fashion was already taken).

$ pip install fashionModel

I got caught by the local cache and couldn't get a newer version. The solution was:

$ pip install --upgrade --no-cache-dir fashionModel

Getting started

Create a new directory for your target project.

$ mkdir greet
$ cd greet

Set up fashion in this directory.

$ fashion init

This creates the subdirectory fashion and an empty fashion project.

A fashion model is just a structured data file, typically in yaml format. Other formats are possible (e.g. json, xml, csv, ini, etc.). Any format is possible - as long as the transforms which use the model can parse it.

Create a simple model of kind 'greeting' in yaml format.

$ fashion create-model hellos.yaml greeting

This creates the file fashion/model/hellos.yaml.

Edit the new model file. Initially it looks like:

# default empty yaml file

Add a list of greetings similar to hello, so that hellos.yaml looks like:

# List of words meaning hello
- Hello
- Hallo
- Bonjour

Transforms are just python modules which strictly follow certain conventions, such as having functions with specific, pre-determined names. Otherwise, transforms can contain any legal python code, and dependencies (assuming the dependencies are properly installed and located, documentation coming soon).

Now create a transform to change this model into an output source file.

$ fashion create-xform greetWorld

The empty transform should look like this:

'''
greetWorld xform.
'''

import logging

from fashion.xformUtil import readModels
from fashion.xformUtil import generate

def outputKinds():
    '''Outputs generated by this xform'''
    return [ 'fashion_gen' ]

def police():
    '''Validate input combinations.'''
    pass

def xform():
    '''Generate many source files from 2 input models.'''
    logging.debug("greetWorld xform running")
    
    model = {  }
    
    generate(model, "myTemplate", "myTarget")    

Edit the transform to look like this:

'''
greetWorld xform.
'''

import logging

from fashion.xformUtil import readModels
from fashion.xformUtil import generate

def outputKinds():
    '''Output kinds generated by this xform'''
    return [ 'fashion_gen' ]

def police(greeting):
    '''Validate input combinations.'''
    pass

# xform argument names must match input kinds exactly, but order not significant 
def xform(greeting):
    '''Generate source files input models.'''
    logging.debug("greetWorld xform running")
    
    # read all the greeting input models
    # since each input model is a list, flatten=True 
    # changes list of lists [[],[], ...] into a flat list []
    greetings = readModels(greeting, flatten=True)
    # greetings should be ['Hello', 'Hallo', 'Bonjour']

    # create the model handed to the template
    model = { 'greetings': greetings }
    
    # generate the file from the template and the model
    generate(model, "greetWorld_template.c", "greetWorld.c")

Fashion automatically recognizes the named arguments of police() and xform(), so the variable names must match the model kinds.

Templates are implemented by Mako. For details of template syntax, refer to the Mako documentation.

Create the template file fashion/template/greetWorld_template.c

/* greetWorld.c */

#include <stdio.h>

main() {
% for greet in greetings:
    printf("${greet} world!\n");
% endfor
}

Now run the fashion build command, which executes all the transforms.

$ fashion build

This should produce the file greetWorld.c which looks like;

/* greetWorld.c */

#include <stdio.h>

main() {
    printf("Hello world!\n");
    printf("Hallo world!\n");
    printf("Bonjour world!\n");
}

nab command

Next, let's try to reverse engineer an existing file into a transform and template using the fashion 'nab' command.

If you were introducing fashion into an existing project, you'd expect there would already be existing source files. We'll create a file, and pretend this file already existed before we did fashion init.

Create HelloWorld.java with the content below:

public class HelloWorld {

	public static void main(String[] args) {
		System.out.println("Hello, world!");
	}
}

Now make that into a generated file:

$ fashion nab HelloWorld.java

This creates a template fashion/template/HelloWorld.java and a transform fashion/xform/HelloWorld.py. The new template is identical to the generation target HelloWorld.java, just waiting for template placeholders to be added. Now we can modify the transform and template to finish our generation work.

Modify the template to look like:

public class HelloWorld {

	public static void main(String[] args) {
% for greet in greetings:
		System.out.println("${greet}, world!");
% endfor
	}
}

Modify the transform to look like:

'''
HelloWorld xform.
'''

import logging

from fashion.xformUtil import readModels
from fashion.xformUtil import generate

def outputKinds():
    '''Output kinds generated by this xform'''
    return [ 'fashion_gen' ]

def police(greeting):
    '''Validate input combinations.'''
    pass

def xform(greeting):
    '''Generate source files input models.'''
    logging.debug("HelloWorld xform running")
    
    # read all the greeting input models
    # since each input model is a list, flatten=True 
    # changes list of lists [[],[], ...] into a flat list []
    greetings = readModels(greeting, flatten=True)
    # greetings should be ['Hello', 'Hallo', 'Bonjour']

    # create the model handed to the template
    model = { 'greetings': greetings }
    
    # generate the file from the template and the model
    generate(model, "HelloWorld.java", "HelloWorld.java")

Test the new transform.

$ fashion build

The output file HelloWorld.java should look like:

public class HelloWorld {

	public static void main(String[] args) {
		System.out.println("Hello, world!");
		System.out.println("Hallo, world!");
		System.out.println("Bonjour, world!");
	}
}

So now we have source files in two languages generated from a common model.

Fashion police

Fashion police help track down bad input. Write a police function to verify our greeting models:

def police(greeting):
    '''Check there are greetings and none are zero-length'''
    if len(greeting) == 0:
        logging.warn("no greeting inputs")
        return True
    for f in greeting:
        m = f.loadFile()
        for word in m:
            if not word or len(word) == 0:
                logging.error("found a zero-length greeting word: {0}".format(f.filename))
                return True

Now try introducing an error by adding an empty greeting item. Modify the model to look like this:

# List of words meaning hello
- Hello
- Hallo
- Bonjour
-

And then fashion build to test the new police(greetings) function. After you've tested it, fix the model by removing the empty item.

Generated documentation

Fashion generates documentation to explain what happened during a build to aid in debugging build problems.

build.html is written to the fashion directory. This is produced by the built-in transform fashion_doc. So far there is little output, but in time all the fashion internal data will be exposed as models so very rich documentation will become possible.

Directory structure

Fashion creates a directory for itself under your project directory. Fashion is design to work within your existing project directory (as long as you don't already have a subdirectory named 'fashion'). The transformation and generation assets and metadata are stored under this directory.

fashion/
+ bak/      # backup files (not yet implemented)
+ mirror/   # copies of generated files to detect external modification
+ model/    # model files go here
+ template/ # template files go here
+ tmplMods/ # mako compiles templates into python modules here
+ xform/    # xforms (python modules) go here
+ fashion.yaml  # model for this fashion project
+ library.yaml  # library model for this fashion project
+ fashion.db    # sqlite3 database for fashions' file metadata

version control

It is recommended to store your transformation and generation assets in your source code version control system, but not everything under fashion/ needs to be controlled.

Control these:

fashion/
+ model/    # model files go here
+ template/ # template files go here
+ xform/    # xforms (python modules) go here
+ fashion.yaml  # model for this fashion project
+ library.yaml  # library model for this fashion project

Don't control these:

fashion/
+ bak/      # backup files (not yet implemented)
+ mirror/   # copies of generated files to detect external modification
+ tmplMods/ # mako compiles templates into python modules here
+ fashion.db    # sqlite3 database for fashions' file metadata

The project model

A sample fashion.yaml is shown below:

bakPath: /home/bdillman/greet/fashion/bak
fashionDbPath: /home/bdillman/greet/fashion/fashion.db
fashionPath: /home/bdillman/greet/fashion
libraries:
- /home/bdillman/greet/fashion/library.yaml
- /home/bdillman/myFashionHome/fashion/fashion/library.yaml
mirrorPath: /home/bdillman/greet/fashion/mirror
projectPath: /home/bdillman/greet
tmplMods: /home/bdillman/greet/fashion/tmplMods

These entries record where the local fashion project locates various paths.

The libraries: item contains a list of library.yaml files. The order is significant because the libraries are searched from first to last. Typically, the first library is the local library, where commands like create-model will create new models. Also the home library is usually last, which contains the built-in templates and transforms.

Users can add library entries from other fashion projects, making it easy to share models, templates and transforms between multiple projects.

Libraries

A sample library.yaml is shown below.

- fileFormat: yaml
  glob: ./model/
  kind: fashion_unknown
  recursive: false
  role: 3
- fileFormat: python3
  glob: ./xform/
  kind: fashion_unknown
  recursive: false
  role: 4
- fileFormat: mako
  glob: ./template/
  kind: fashion_unknown
  recursive: false
  role: 2
- fileFormat: python3
  glob: ./xform/**/*.py
  kind: fashion_unknown
  recursive: true
  role: 4
- fileFormat: yaml
  glob: /home/bdillman/greet/fashion/model/greetWorlds.yaml
  kind: greetings
  recursive: false
  role: 3

This is a list of library entries. Each entry associates some metadata with a specified group of files.

  • glob: is a unix glob filespec which identifies the file(s).
  • role: is an integer which identifies the nature of the file(s).
    1. reserved
    2. template
    3. model
    4. transform
  • kind: is the kind of a model file.
  • fileFormat: describes how to read the file (currently only applies to models)
  • recursive: if true means recurse into directories using the ** in glob, if any.

Commands like create-model manipulate the local library file to add or remove entries as needed. Other than that, it is normal that users can edit these library files directly.

Dependencies

The following python libraries are used:

The name

Think of "fashion" as a verb, e.g. "I fashioned this code from this simple model."

About

A utility for simple model transformation and source code generation.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Python 90.4%
  • Makefile 4.8%
  • Batchfile 4.8%