makefilemaintainabilitycode-maintainability

code management: generate source files with slight variations of various rules


I have a source file in a declarative language (twolc, actually) that I need to write many variations on: a normative version and many non-normative versions, each with one or more variations from the norm. For example, say the normative file has three rules:

Rule A:
    Do something A-ish

Rule B:
    Do something B-ish

Rule C:
    Do something C-ish

Then one variation might have the exact same rules as the norm for A and C, but a different rule for B, which I will call B-1:

Rule A:
    Do something A-ish

Rule B-1:
    Do something B-ish, but with a flourish

Rule C:
    Do something C-ish

Imagine that you have many different subtle variations on many different rules, and you have my situation. The problem I am worried about is code maintainability. If, later on, I decide that Rule A needs to be refactored somehow, then I will have 50+ files that need to have the exact same rule edited by hand.

My idea is to have separate files for each rule and concatenate them into variations using cat: cat A.twolc B.twolc C.twolc > norm.twolc, cat A.twolc B-1.twolc C.twolc > not-norm.twolc, etc.

Are there any tools designed to manage this kind of problem? Is there a better approach than the one I have in mind? Does my proposed solution have weaknesses I should watch out for?


Solution

  • As you added the makefile tag, here is a GNU-make-based (and Gnu make only) solution:

    # Edit this
    RULES       := A B B-1 C
    VARIATIONS  := norm not-norm
    norm-rules  := A B C
    not-norm-rules  := A B-1 C
    # Do not edit below this line
    
    VARIATIONSTWOLC := $(patsubst %,%.twolc,$(VARIATIONS))
    
    all: $(VARIATIONSTWOLC)
    
    define GEN_rules
    $(1).twolc: $$(patsubst %,%.twolc,$$($(1)-rules))
        cat $$^ > $$@
    endef
    $(foreach v,$(VARIATIONS),$(eval $(call GEN_rules,$(v))))
    
    clean:
        rm -f $(VARIATIONSTWOLC)
    

    patsubst is straightforward. The foreach-eval-call is a bit more tricky. Long story short: it loops over all variations (foreach). For each variation v, it expands (call) GEN_rules by replacing $(1) by $(v) (the current variation) and $$ by $. Each expansion result is then instantiated (eval) as a normal make rule. Example: for v=norm, the GEN_rules expansion produces:

    norm.twolc: $(patsubst %,%.twolc,$(norm-rules))
        cat $^ > $@
    

    which is in turn expanded as (step-by-step):

    step1:

    norm.twolc: $(patsubst %,%.twolc,A B C)
        cat $^ > $@
    

    step2:

    norm.twolc: A.twolc B.twolc C.twolc
        cat $^ > $@
    

    step3:

    norm.twolc: A.twolc B.twolc C.twolc
        cat A.twolc B.twolc C.twolc > norm.twolc
    

    which does what you want: if norm.twolc does not exist or if any of A.twolc, B.twolc, C.twolc is more recent than norm.twolc, the recipe is executed.