-1

Hello

I Need to create a template, which is "dynamic", and i'll explain the meanning of "Dynamic":

  • I need to have a template, that is rendered into text files (c++ code, to be exact).

  • the user will be able to change some things in the generated files.

  • After a while, a process is run to update the generated files, i'll be able to "spot" where were the templates regions and update them accordingly.

My Effort

currently, i use a "T4 Template" to create the initial render, and in the Template, I implant C++ style comments over the regions i need to recognize later.

and another code that find that regions, and regenerates what should go between those "Comment blocks".

the problem is that it is not the same code that generate the boiler-plate, and updates the regions which costs me a lot of headache and buggy features.

it is not very intuitive to write, and the users (the ones who use the generated code) need to know Not to touch the "Comment Blocks".

Questions

  • How can i recognize location/blocks in a generated file without "littering" the file with "comment"/"unimportant" text?
  • How can i unify the code that generate the "templated blocks" both for "Generation" and "Update"
  • Later on, How can i make it work on Non-Code files too,

Edit

I guess I wasn't clear at what i am doing,
I am writing a tool in C#, that generates C++ code.
Also, T4 is just what i used, but any tool/library can be used while C# libraries are prefered

Any idea will be highly appreciated,
Thanks.

Tomer W
  • 3,395
  • 2
  • 29
  • 44
  • First of all, C++ has 'templates' of its own, and "T4 Template" is a totally different Microsoft C# thingy. Please edit the title and the text to make it clear that you DONT mean the C++ templates. Second thing, why did you use tags C# and C++? I don't see any reference in the text to those languages **being used**. You even specifically say that you want to extend the generated content from just cpp files into any file. It seems that C++ is irreleant for this question. – quetzalcoatl Mar 01 '17 at 07:54
  • I'd suggest changing "templates" tag into "t4" tag (yes, there is such one) and removing "c++" tag completely. I think you should also elaborate a bit more the part about "dynamic template". It took me 3 readings to get that you simply want a change to generated content to cause automatic "reverse-update" to the templates. If it is what you meant of course. – quetzalcoatl Mar 01 '17 at 07:57
  • @quetzalcoatl thanks for feedback, I edited to emphasize I am working on a C# project which task is to generate and maintain C++ code. – Tomer W Mar 01 '17 at 09:33

2 Answers2

1

I think you are going about it the wrong way. You have a XY problem here. Allowing your users to modify only part of the generated file and then trying to detect that part it's a lot of headache as you have seen.

Instead, the better solution is to leave the generated file completely unmodifiable and have some configuration available. For instance you can have a config file where users can add their own data members, initializers for them, etc.

This way you have a clear separation of the parts of your system. The modifications done by the users are now trivially carried to the next iteration and you can easily always re-generate the output.

+------------------+
| Input: Template  |  ------
+------------------+        \
                             |
+------------------+         |   Generator code        +-------------------------+
| Input: Config    |  -------+---------------------->  | Output: Generated code  |
+------------------+         |                         |-------------------------+
                             |
+------------------+         |
| Input: Config    | --------/
+------------------+

This system can be used to generate non-code also.

Community
  • 1
  • 1
bolov
  • 72,283
  • 15
  • 145
  • 224
  • This is a good hint in cases where the changes performed by 'configuration' are minimal. To show the opposite case, look at the T4 templates themselves. Why do they have the markup mixed with the content? I understand that by configuration you meant the "data" not the "markup", but markup also is some form of configuration. It is embedded in the template content, because it's more natural and easier to detect and map to content, and because it's assumed that there will there may be many and complex changes which would be a pain to think about if they were specified in external resource. – quetzalcoatl Mar 01 '17 at 09:05
  • What I want to say is, ignoring "markup", pulling the "data" part into external resource is actually not a good thing **sometimes**. As the provider of the tool, you're guessing. Users will fight with it. Another problem is that such XY assumes certain workflow. What if the users are not able to get and edit the template nor config nor generator toolset, what if users only receive the generated products, and we get edited products back from them? There are more corner cases like that, I just want to show that the OP is, while interesting, is really.. too broad and too vague to really answer. – quetzalcoatl Mar 01 '17 at 09:10
  • We Thought about it before, by creating a Class hierarchy where BaseClass is template, and you can extend it using your own code, but this was torn down because it wasn't flexible enough and required too many tweaks to the base templates. – Tomer W Mar 01 '17 at 09:52
  • @quetzalcoatl Correct. The answer is always **depends**. I am actually not familiar with T4 templates. I just wanted to offer another perspective of the problem. – bolov Mar 01 '17 at 10:49
1

Now, I believe your question is totally ["open" and "opinion based"] on one side, and ["why is this code not working" without showing the code] on the other side.. but I want to try pointing some problem with the idea of "improvement" you have now.

Q2: How can I unify the code that generate the "templated blocks" both for "Generation" and "Update"

I'm strongly convinced that you should not, at least not now. Here's why:

  • 'generate' and 'update' are happening in different directions; first is t4template->content, second is content->t4template
  • those two directions form different functionality
  • at least one of these directions requires complex logic not present in the other one
  • 'generate' is based on T4 Engine, while 'update' will probably not be able to use it at all
  • ..and probably many other reasons, but that's enough

Q3: Later on, How can i make it work on Non-Code files too

T4 Engine has no idea that what you generate now is a C++ file. T4 works only on a layer of "text files". If the process you have works now, you should be able to "generate" any text file already right now. The "update" part is a bit more tricky, because it depends on how you implemented it. If you assumed/used any correlaction to C++ syntax, you've got a problem. (guess why T4 Templates are called 'text templating engine', agnostic to the actual generated code language) If you kept it clean and worked as if on a free-form text file, then you're already safe to work on, well, free-form text files.

Q1: How can I recognize location/blocks in a generated file without "littering" the file with "comment"/"unimportant" text?

Well, basically, you can't and/or shouldn't. Consider a smart idea of keeping a hidden database that remembers text locations for every file. For every comment that you would put in the file, you put a row in the database, saying file: BAR\FOO.CPP | FROM: line 120 char 1 | TO: line 131 char 15 | XXX: yyy | ZZZ: aaa. That's almost no difference to having comments in the file, all information is preserved, and the file is clean now, right?

Nope. And that's because you want to detect what has changed. Let's take a highly contrived example, here's the generated file with such invisible markers that are managed by database. Each @ character denotes a marker, be it start/stop/metainfo nevermind:

class FooBar : public @BaseClass@
{
public:
    @void Blargh(Whizz& output);@
    @int GetAge() const;@
private:
    int @shoeSize@;
    @
};

Those @ are of course invisible, it's just an information held elsewhere, the user sees a clean file. Now, he edits it to this:

class FooBar : public BaseClass
{
public:
    template<T>
    void Yeeek(T& output);
    int GetAge() const;
private:
    int shoeSize;
};

Please note how "template" was added and method renamed to "Yeeek". There were some markers out there, I didn't show them intentionally, just look on the "template<>" line. What if it was accidentally placed a line or a byte too far or too early, so one marker too many was skipped or included? Now the detector and updater may accidentally skip "template<>", and it will be totally happy to just rename the method. This is not a problem with the detector or updater. This is a problem of markers not being visible, so the user was not able to see where should he place his edit.

That's probably the most important point. But, let's see something more algorithmic/technical. Let's try an even simpler edit. User edits the file to:

class FooBarize : publ@ic BaseCl@ass
{
    int goat;
 @   string cheese;          @
p@ublic:             @
    void Blargh(Whizz& output);
    i@nt GetA@ge() const;
p@rivate:
    int shoeSize;
};

I overlaid those invisible markes from 'the external database of markers' back onto this edited file. What has happened? Simple. User has added two lines more in an odd place (he doesnt see the markes, right?), and the database remembers old places (i.e. 'line:char', but could be 'byte', or really whatever). Now of course, database may (and should!) also remember old shape of the file, so it can see that i.e. the first @ was after ":public" and the process can try to map it onto the new file.. but then, you already have a highly complex problem, and this edit was trivial. Of course, you can require the user to enter some information on how to update the markers.. but hey, he don't see them, how can he do it? And since we wanted to hide the markers from him, we probably don't want to ask him about updating them as well..

How about editing the file to:

struct FooBar : One,Two,Three,Four
{
    void OhNoes();
};

I didn't care to overlay the markers, because it's utter nonsense. Now, how to map it back to the template? Is OhNoes mappable to GetAge (const removed) or to Blargh (parameters removed)? How the template base class should be updated? Which one of the new bases is the true base? Or maybe all of them? Neither you nor I can decide it, even with our combined human intelligence, not mentioning an automated process.

Of course, you can leave it as a corner case, you can emit an error to the user and inform them that their edit went to far and is unanalyzable and so on. But The complexity of reverse-mapping a change back to the model text is still there.

What I want to show you by these contrived examples is, that if you want to detect and map changes back to the original template, you should keep these markers in the generated content. Having these markers in the code allows you quickly and reliably detect:

  • which sections changed? (-> content between markers has changed)
  • which sections were offsetted by edits? (->markers are now at different position than before)
  • were any sections deleted? (-> both markers and content between removed)
  • (..)

It also allows the user to see which parts are special so he can place his edits in a reasonable way, which allows you to ignore and not support more corner cases than in the "invisible markers" case.

Finally, let's take a read-world example which you already know. T4 Template. All those ugly <%!@!#^$^!%@ littering your precious template text. Couldn't they be removed? Couldn't these be kept in a separate file that describes the transformation? Or at least at the beginning or end of the file? Yes, it could. But it would make the editing a real pain - we're back to 'invisible markers' problem: each your edit to the content may require you to manually update locations of some invisible markers.

Keep the markers in the generated content.
Keep your users aware of the generation and detection and special regions.

If it's too complex for them, change the users to a more technical group, or train your userbase to be more technical. Or prevent them from editing the file. Given them some partial access so they can edit a part of the file, as an excerpt, not as a whole file. Limit their editing power to absolute minimum. Maybe it will allow you to limit the number of visible markers, maybe even down to zero, maybe at the cost of splitting and downsizing editable fragments.

quetzalcoatl
  • 32,194
  • 8
  • 68
  • 107
  • Thanks for a very elaborate answer, Happily, this assures me I am on the right track, and I have to annotate my generated files. The user base is already a skilled set of developers which can and already are aware of the "comment-blocks", but I wanted to make it "Magic-Like" – Tomer W Mar 01 '17 at 09:47