The Combo transform is the swiss army knife of transforms and is often being used without even realizing it.

Whenever you author a node in the transform tree without specifying type: x, you're actually using Combo.

A Combo combines the behaviours of Include, Exclude, Merge, Chain and UniquePath (and even Let) in a way that feels natural.

Syntax Reference

The full syntax of Combo is the following:

type: Combo                  # This can be omitted as Combo is the default transform type
let:                        # See Let
  - name: <string>
    expression: <SpEL expression>
  - name: <string>
    expression: <SpEL expression>
condition: <SpEL expression>
include: [<ant pattern>]    # See Include
exclude: [<ant pattern>]    # See Exclude
merge:                      # See Merge
  - <m1-transform>
  - <m2-transform>
  - ...
chain:                     # See Chain
  - <c1-transform>
  - <c2-transform>
  - ...
onConflict: <conflict resolution> # See UniquePath

Behaviour

All properties of the Combo are optional, as long as at least one of them is used (i.e. they all have defaults, but you need to use at least one. An empty, unconfigured Combo, like any other transform, would not make sense).

When it is configured with all properties, the Combo behaves like this:

  1. Apply the include as if it was the first element of a Chain. The default value beeing ['**'], if not present, all files are retained.

  2. Apply the exclude as if it was the second element of the chain. The default value being [], no files are excluded if not present. Hence, at this point of the chain only files that match the include but are not excluded by the exclude remain,

  3. Feed all those files as input to all transforms declared in the merge property, exactly as Merge does. The result of that Merge (which is the third transform in the big chain) is thus another set of files. If there are no elements in merge, the previous result is directly fed to the next step.

  4. The result of the merge step, because it is prone to generate duplicate entries for the same path, is implicitly forwarded to a UniquePath check, configured with the onConflict strategy. The default policy is to retain files appearing later, so in particular results of transform appearing later in the merge block "win" against ones appearing earlier.

  5. Pass that result as the input to the Chain defined by the chain property (or you can consider that the chain mentioned above is prolonged with the elements defined in chain). Again, if there are no elements in chain, it's as if the previous result was used directly.

  6. Additionally, if the let property is defined in the Combo, then the whole execution is wrapped inside a Let that exposes its derived symbols.

So, to recap in pseudo code, a giant Combo would behave like this:

Let(symbols, in:
    Chain(
        include,
        exclude,
        Chain(Merge(<m1-transform>, <m2-transform>, ...), UniquePath(onConflict)),
        Chain(<c1-transform>, <c2-transform>, ...)
    )
)

Of course, one rarely uses all the bells and whistles that combo has to offer at the same time. As a matter of fact, Combo is actually a good way to author other common building blocks, but without having to write their type: x in full.

Thus,

include: ['**/*.txt']

is a perfectly valid way to achieve the same effect as

type: Include
patterns: ['**/*.txt']

Similarly,

chain: 
  - type: T1
    ...
  - type: T2
    ...

is often preferred over the more verbose

type: Chain
transformations: 
  - type: T1
    ...
  - type: T2
    ...

Of course, as with other transforms, the order of declaration of properties has no impact. We've used a convention that mimics the actual behaviour for clarity, but

merge:
  - type: T1
  - type: T2
include: ["*.yaml"]

... applies T1 and T2 on all .yaml files even though we have placed the include section after the merge section. In other words Combo applies include filters before merge irrespective of the physical order of the keys in yaml text. It is thus a good practice to place the include key before the merge key to make the accelerator definition more readable, but this has no effect on its execution order.

Examples

Typical use cases for Combo are documented below:

To apply separate transformations to separate set of files, for example all .yaml files on the one hand and all .xml files on the other hand:

merge:                   # this uses the Merge syntax in a first Combo
  - include: ['*.yaml']      # this actually nests a second Combo inside the first
    chain:
      - type: T1
      - type: T2
  - include: ['*.yaml']      # here comes a third Combo, used as the 2nd child inside the first
    chain:
      - type: T3
      - type: T4

To apply T1 then T2 on all .yaml files that are not in any secret directory:

include: ['**/*.yaml']
exclude: ['**/secret/**']
chain:
  - type: T1
    ..
  - type: T2
    ..
check-circle-line exclamation-circle-line close-line
Scroll to top icon