GithubHelp home page GithubHelp logo

tevelee / codegenerator Goto Github PK

View Code? Open in Web Editor NEW
7.0 1.0 0.0 574 KB

An attempt to generate source-code easily using a template engine.

License: MIT License

Objective-C 67.22% Swift 26.76% PHP 5.93% Shell 0.09%

codegenerator's Introduction

Easy Code Generation

This repository contains a lightweight - yet powerful - approach to deal with language-independent code generation. As I'm an iOS engineer, I primarily work with Objective-C and Swift code in my day-to-day work, so most of the examples are iOS related snippets.

Table of contents

  1. Why templates?
  2. Motivation
  3. About metaprogramming
  4. Usage
  5. Examples
  6. Future plans
  7. License

Why templates?

Why use a template engine when you have a bunch of professional generators like Remodel from Facebook or json2swift or SwiftGen or ... (the list is long).

The answer in short: the ability to customize the output.

What I like about templates is that you are the one who write them. If you don't like something you can easily modify anything, and cusomtize the generated code in a way that it matches your own style, your company coding-style and principles, just about the way you want them to look.

It gives you freedom and an easy way to extend or modify or tweak the most important thing: the output.

Motivation

My primary interest was around metaprogramming: how can I programmatically alter the compiler to extend my codebase without me adding those extensions repetatively. The main focus was to make the life of the developer easier with added useful code snippets in a way that it doesn't alter the runtime or the performance of the program in any ways, so doing the work between the code-writing and the compilation.

First I was thinking of the most basic additions like model hashing, copying operations and equality checks based on the simplest inputs: their properties. Maybe some model parsing from JSON objects, or later on generating their lens property setters.

I had several iterations on the problem, tried extending the compiler, organizing the code in a way to support generic extensions so I could somewhat imitate the feeling of metaprogramming. Finally I realized that code generation seems to be the most lightweight and powerful solution. Sometime the simplest solution is the easiest one (not always tho).

As a workaround to the model-property problem, previously I wrote some runtime magic placed in a model baseclass for equality, hashing, copying and similar operations which read the active properties and with a for it loop on them it did the trick. This turned out to be a very expensive trick, slowed down the runtime most than I expected them to do. For this reason I decided to leave the runtime out of the solution, doing all the heavy work at compile-time.

About metaprogramming

I realized that the iOS dev environment is full of metaprogramming solutions already. Turned out I wasn't the only one.

Probably @synthesize is the most obvious example, it generates getters and setters for your instance variables you defined as @properties with pre-defined options (copying, atomicity, weakness, etc.). Just to mention some more of them: key-value coding, @dynamic properties, Swift generics or protocol extensions are all somewhat extending your code without you explicitely writing the repetative work yourself.

Usage

Installation

To install the only dependency (Twig) for this project, I started using the populat PHP dependency-management tool, composer.

You have mutiple options, the easiest one is to use the one pushed in the repo and simply run php composer.phar install while you're cd'd in the root of the repo. Or you're free to download it yourself from here. Or if you wish to leave the dependency mangement tool out, you can simply close twig into the vendor lib using git clone https://github.com/twigphp/Twig.git ./vendor.

Usage

For the first try the easiest option is to use the example templates provided in the repo. To start the generation, simply run php index.php form the root of the cloned repo. This will override the model files in output/swift and output/objc.

If you wish to experiment more, please read further. After you created your own template, you can run your template in index.php as echo $twig->render('your.template');, unless you modified the existing *.model files, as those are automatically getting parsed.

For the templates I used the excellent Twig template engine. I was looking for something that is a language-independent templating system, and the closest I found was Twig. It's largely optimized for web and be used in php environments, but turning off a couple of optimizations, like caching, made my life much easier.

To start the generation, simply run php index.php. The output will tell you the names of the generated files.

As for the syntax of the templates, I'm solely using the syntax provided by Twig, with one little addition. I added a custom file tag (see the examples) which takes the output and saves it into a text file. This makes the code generation persistent, that not only you have the output in the console, but you have it persisted on your disk as well.

Input/Output examples

Model generation example

As I mentioned my primary interest was in code generation. And to validate the idea, I started up with the obvious DTO and data-model generators. These models are generated from templates like to following. If you place a template in any folder with a .model extension, it will get picked up and parsed by the compiler, generating the necessary files next to the descriptor file in the same folder.

Let's see the template below

{% import "templates/objc.template" as objc %}
{{ objc.Model("Address", [
	{"name": "postalCode", "type": "NSNumber*"},
	{"name": "streetAddress", "type": "NSString*"},
	{"name": "number", "type": "NSInteger"}
], features) }}

and its output here:

Another model using the newly created Address above:

{{ objc.Model("Person", [
	{"name": "firstName", "type": "NSString*"},
	{"name": "lastName", "type": "NSString*"},
	{"name": "nickName", "type": "NSString*"},
	{"name": "age", "type": "NSInteger"},
	{"name": "address", "type": "Address*"}
], features, ["\"Address.h\""]) }}

and the result:

As you minght noticed, I included a features property in each of the model factories. I only have those to separate all the different options of model generation, like hashing, equality, initialisers, immutable properties, NSCopying, NSCoding and so on. The options are limitless. So actually to work you'll need the features defined something as

{% set features = [
	"ImmutableProperties", 
	"Initializer", 
	"NSCoding", 
	"NSCopying", 
	"Description", 
	"Equality", 
	"Hashing", 
	"Builder", 
	"Lenses", 
	"JSONCoding"
] %}

So actually if you included a builder feature, you'll have additional builder classes generated:

Swift

I included another language to generate model files in: Swift. So a similar definition

{% import "templates/swift.template" as swift %}
{{ swift.Model("Address", [
	{"name": "postalCode", "type": "Int"},
	{"name": "streetAddress", "type": "String"},
	{"name": "number", "type": "Int"}
], features) }}

{{ swift.Model("Person", [
	{"name": "firstName", "type": "String"},
	{"name": "lastName", "type": "String"},
	{"name": "nickName", "type": "String"},
	{"name": "age", "type": "Int"},
	{"name": "address", "type": "Address", "custom": true}
], features) }}

{{ swift.Model("Record", [
	{"name": "name", "type": "String"},
	{"name": "creator", "type": "Person", "custom": true},
	{"name": "date", "type": "Date"}
], features) }}

generates the following:

If you enabled the builder feature, then additionally

or with the lenses feature

output files as well.

Details

If you're interested in the actual templates that create the classes themselves (not just the "high-level" model definition interface), you're free to look at swift.template and objc.template. Here you can also customize the output as you like as these are just simple text files, there's nothing to compile. Again, the syntax is provided by the template engine Twig. Check out its docs here. These files are really just a few hundred lines long, and contain all the necessary information to generate all the example models above. Really concise, extremely powerful.

You'll see simple things like

{% macro ImplementationFile(name, imports, properties, content, global) %}

	{% for import in imports %}
	#import {{import|raw}}
	{% endfor %}
	
	{{global}}
	
	@implementation {{name}}
	
		{{content}}
	
	@end

{% endmacro %}

or

{% macro EqualityExtension(name, properties) %}

	func ==(lhs: {{name}}, rhs: {{name}}) -> Bool {
	    return {% for property in properties %}lhs.{{property.name}} == rhs.{{property.name}}{% if not loop.last %} && {% endif %}{% endfor %}
	}
	
	extension {{name}} : Equatable {
	}
	
{% endmacro %}

It's easy to wrap your head around.

Future plans

For the model generation templates there are countless options and features to pack, like JSON parsing, paraterization, different immutable setters, or some cool features from Remodel.

A couple of other areas that I had in mind are client-side SDK generation, advanced enumeration classes (like string-enums in Objective-C), basically any repetative task from our day-to-day developer work.

I found a couple of useful examples since and used them already for code/text generation, like different forms of serialization or repetative text creation as well.

License

This repository was brought to life by László Teveli. The main engine behind the scenes is Twig, created by SensioLabs.

Any form of modification or redistribution is free and allowed by the author (MIT license). If you happen to use it for something interesting, I'd appreciate an e-mail though ;-) The repo contains only an experiment to overcome the repetative tasks in my work. I'm sharing it because someone else might also find it useful.

If you like the idea or have further improvements in your mind, feel free to contact me at [email protected] or submit an issue or pull request and I'll be happy to take it from there.

codegenerator's People

Contributors

tevelee avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.