01 Introduction - oldgreydog/CodeGenerator GitHub Wiki

Introduction

I built this code generator because I couldn't find anything that would generate whatever code I wanted the way that I wanted. Even now, more than seventeen years later, most of the generators you will find are only able to output very specific files for very specific uses. This generator, on the other hand, should be thought of more as text generator than just a code generator.

To be honest, though, there is a downside. To take advantage of code generation, you will have to learn what is something like a markup/programming language. The silver lining is that the "language" is pretty small.

Template markup tags

This is a quick listing of the tags to give you an idea what order you might want to focus on them. I've divided them into tiers from "always used" to "maybe?".

Here are the first tier, core tags:

  • header
  • text
  • file
  • "config value" (the only tag that uses a config value name instead of a tag name like the rest of the tags)
  • forEach
  • file
  • if-elseif-else

The second tier tags are:

  • customCode
  • first-else
  • typeConvert
  • tabSettings
  • tabMarker
  • tabStop
  • variable block
  • counter
  • counterVariable
  • include

The third tier (i.e. rarely used) tags are:

  • --counter (decrement)
  • ++counter (increment)
  • outerContext
  • outerContextEval

And finally, the totally optional text formatting tags:

  • camelCase
  • firstLetterToLowerCase

The first tier tags are the ones that you will absolutely have to use to get started creating templates. The second tier tags will give you much more control over the code generation. You may never need the the third tier or optional tags.

The basic documentation for each tag can be found in the javadoc files that are included in the release *_javadoc.zip file. This wiki will try to explain the higher-level concepts about using the tags to build templates. The examples will let you see working usage of most, if not all, of the tags that you can modify and regenerate to see things change.

Generator inputs: config file and templates

The generator requires two inputs: the template files and their matching config values file. One is meaningless without the other. The config values have multiple aspects: some are directly substituted into the template output, some serve as flag values to control generation, and overall, the config values have a structure that mimics what they are describing. For example, a config value file describing a database will have table nodes, the table nodes will have column nodes and the column nodes will have flags such as "isPrimaryKey" and sub-nodes like "foreignKey" that describe relationships to other tables. An API config will have function nodes, function nodes will have parameter nodes, etc.

The template files are a collection of tags and, in the case of "file" templates, text that are parsed into a tree of executable objects that traverse the config value tree to generate its outputs. The forEach tag is the central player in that traversal, so you should pay particular attention to where it appears in the examples.

Each of these file types has its own section in the wiki, so look there for more high-level information and in the javadocs for tag-specific documentation.

Generated files and diffing

By diffing, I mean using a diff tool to compare files to see what changes have been made. By that, I mean that I generate into a separate tree from my work tree and diff the changes into the work tree by hand. While I use it for all of my work-flow, it makes template editing, in particular, much easier and safer. I've been using Beyond Compare (https://scootersoftware.com) since about 2002 and I swear by it. It is an excellent diff tool and it works on Linux, Windows and Mac.

Regardless of whether you go the diffing route, you can control the root output path with the same trick I use myself. If you look at one of the config values files in the examples you will notice that I put the output path in a value under "global". Then you can use it in your file tag's destDir attribute like this:

<%file template=ddl.template filename="<%root.global.databaseName%>.ddl" destDir="<%root.global.outputPath%>/ddl" %>

This lets you send the generated output to any directory you want, not just the current directory. I always generate to a separate location and then diff that tree against my "work" tree. I would highly recommend that you also do this, especially when you are changing the templates or developing your own tag handlers. There are plenty of ways to cause the generation to fail in the middle of a file and if you are generating over the top of your working code directly, you can loose un-checked-in changes and you will always destroy any custom changes you make outside of custom code tags. Once you've stabilized your templates, then you can switch to generating directly in your work folders if you desire.

We should talk a little more about making changes outside of custom code blocks. I have occasionally run across situations where I need to alter the behavior of generated code, but the instance was totally unique or so rare that I didn't want to add one or more config flag values and add complexity to the template(s) to handle such a special case. In that situation, I make the change outside of the custom code tags but I am forever more locked into generating into a separate tree from my work tree and using diffing to merge over changes in the generated code by hand so that don't blow away any of those special-case customizations.

You don't necessarily have to use this work flow. Many (most?) use cases can be handled with simple custom code tag placement. In other cases, if you are willing to add more complexity to your templates, you can add config flag values and then use them to change the generated code in a way that handles the need for customization (including optionally inserting custom code tags along with altered code).

I have thought for years that I wouldn't try to push people to generate into a tree that was separate from their "work" tree, but I recently changed my mind. Then it occurred to me that I should just put in a file tag option to generate to a temp file that, on failure, is deleted and on success is used to replace the original file. This should eliminate most of the potential problems with regenerating into your working tree as long as you don't have customizations that you've made to generated code. As I said above, if you change generated code outside of the custom code tags, you can never generate directly into your work folder - you will have to generate to a separate tree and hand merge.

A final note

In case it's not obvious, the generator doesn't care if you generate into directories that have other files in them. That means that you can generate, for example, into your work tree without worrying about blowing away any non-generated files. It will only overwrite files that have the generated names, so you can only screw up if you happen to create a config/template combo that generates one or more files with the same names as non-generated files. If you do that, then the non-generated files will be gone.