The input to C can be a function that defines files based on the wildcard for the output from C.
You could write a function that decides what the input to C should therefore be.
When you say that "sometimes I would need X to take as input the output from B and then C to take the output from X", do you mean that the optional use of X is decided for a given sample within your workflow (sample 1 might pass through X, but sample 2 might not need to), or that the whole workflow should optionally use X based on some config/argument (for a given experiment, choose to run all samples through X)
Something like this
# optionally use X for every sample passing through the workflow
def input_for_c(wildcards):
# requires a config containing switches for the whole workflow
if config["Use X"]:
return "./data/X/{}".format(wildcards["sample_id"])
else:
return "./data/B/{}".format(wildcards["sample_id"])
# optionally use X for the current sample
def input_for_c(wildcards):
# requires a `sample_config` containing switches for each separate sample
sample_id = wildcards["sample_id"]
if sample_config[sample_id]["Use X"]:
return "./data/X/{}".format(wildcards.sample_id)
else:
return "./data/B/{}".format(wildcards.sample_id)
rule all:
input: expand("./data/C/{sample_id}", sample_id = SAMPLES)
rule B:
output: "./data/B/{sample_id}"
...
rule X:
output: "./data/X/{sample_id}"
...
rule C:
input: input_for_c
output: "./data/C/{sample_id}"
...