LogoCyanPrint
ProcessorsExplanation

Read and Write Directories

Understanding the directory mechanics in processors

Read and Write Directories

Processors work with two directories: readDir for input and writeDir for output. Understanding these is key to correct processor implementation.

Directory Overview

DirectoryPathPurposeAccess
readDir/workspace/cyanprint/Source filesRead-only
writeDir/workspace/output/Generated filesWrite-only

The paths shown above are typical examples. Actual values depend on your container or deployment configuration.

readDir

Contains files from the template's blob image. This is your source material.

Typical Value

/workspace/cyanprint/

Contents

Files from the template that match the processor's globs:

/workspace/cyanprint/
├── templates/
│ ├── README.md
│ ├── package.json
│ └── src/
│ └── index.ts
└── config/
└── settings.json

Accessing Files

Use CyanFileHelper - never access directly:

processor.ts
StartProcessorWithLambda(async (input, fileHelper) => {
// ✅ Correct: Use fileHelper
const files = fileHelper.resolveAll();
// ❌ Wrong: Direct filesystem access
// const content = fs.readFileSync(`${input.readDir}/README.md`);
return { directory: input.writeDir };
});
processor.py
def start_processor_with_fn(input, file_helper):
# ✅ Correct: Use file_helper
files = file_helper.resolve_all()
# ❌ Wrong: Direct filesystem access
# content = open(f'{input.read_dir}/README.md').read()
return {'directory': input.write_dir}
Processor.cs
[ProcessorMain]
public async Task<ProcessorResult> RunAsync(ProcessorInput input, CyanFileHelper fileHelper)
{
// ✅ Correct: Use fileHelper
var files = fileHelper.ResolveAll();
// ❌ Wrong: Direct filesystem access
// var content = File.ReadAllText($"{input.ReadDir}/README.md");
return new ProcessorResult { Directory = input.WriteDir };
}

Never access readDir directly with fs operations. Always use CyanFileHelper APIs.

writeDir

Where processed files are written. This is your output location.

Typical Value

/workspace/output/

Writing Files

Use writeFile() on file objects:

processor.ts
StartProcessorWithLambda(async (input, fileHelper) => {
const files = fileHelper.resolveAll();
files.forEach(file => {
// Transform content
file.content = transform(file.content);
// Write to writeDir
file.writeFile();
});
return { directory: input.writeDir };
});
processor.py
def start_processor_with_fn(input, file_helper):
files = file_helper.resolve_all()
for file in files:
# Transform content
file.content = transform(file.content)
# Write to write_dir
file.write_file()
return {'directory': input.write_dir}
Processor.cs
[ProcessorMain]
public async Task<ProcessorResult> RunAsync(ProcessorInput input, CyanFileHelper fileHelper)
{
var files = fileHelper.ResolveAll();
foreach (var file in files)
{
// Transform content
file.Content = Transform(file.Content);
// Write to WriteDir
file.WriteFile();
}
return new ProcessorResult { Directory = input.WriteDir };
}

VirtualFile Paths

The relative property is the path between read and write:

processor.ts
// Input: /workspace/cyanprint/templates/README.md
// file.relative = 'templates/README.md'
// Output: /workspace/output/templates/README.md
processor.py
# Input: /workspace/cyanprint/templates/README.md
# file.relative = 'templates/README.md'
# Output: /workspace/output/templates/README.md
Processor.cs
// Input: /workspace/cyanprint/templates/README.md
// file.Relative = "templates/README.md"
// Output: /workspace/output/templates/README.md

Path Resolution

How Paths Work

processor.ts
// Full input path
const inputPath = `${input.readDir}/${file.relative}`;
// Example: /workspace/cyanprint/templates/README.md
// Full output path
const outputPath = `${input.writeDir}/${file.relative}`;
// Example: /workspace/output/templates/README.md
processor.py
# Full input path
input_path = f'{input.read_dir}/{file.relative}'
# Example: /workspace/cyanprint/templates/README.md
# Full output path
output_path = f'{input.write_dir}/{file.relative}'
# Example: /workspace/output/templates/README.md
Processor.cs
// Full input path
var inputPath = $"{input.ReadDir}/{file.Relative}";
// Example: /workspace/cyanprint/templates/README.md
// Full output path
var outputPath = $"{input.WriteDir}/{file.Relative}";
// Example: /workspace/output/templates/README.md

Modifying Output Paths

You can change where files are written:

processor.ts
StartProcessorWithLambda(async (input, fileHelper) => {
const files = fileHelper.resolveAll();
files.forEach(file => {
// Transform content
file.content = transform(file.content);
// Optionally change output path
if (file.relative.endsWith('.template')) {
// Remove .template extension
file.relative = file.relative.replace('.template', '');
}
file.writeFile();
});
return { directory: input.writeDir };
});
processor.py
def start_processor_with_fn(input, file_helper):
files = file_helper.resolve_all()
for file in files:
# Transform content
file.content = transform(file.content)
# Optionally change output path
if file.relative.endswith('.template'):
# Remove .template extension
file.relative = file.relative.replace('.template', '')
file.write_file()
return {'directory': input.write_dir}
Processor.cs
[ProcessorMain]
public async Task<ProcessorResult> RunAsync(ProcessorInput input, CyanFileHelper fileHelper)
{
var files = fileHelper.ResolveAll();
foreach (var file in files)
{
// Transform content
file.Content = Transform(file.Content);
// Optionally change output path
if (file.Relative.EndsWith(".template"))
{
// Remove .template extension
file.Relative = file.Relative.Replace(".template", "");
}
file.WriteFile();
}
return new ProcessorResult { Directory = input.WriteDir };
}

Important Rules

Rule 1: Always Return writeDir

processor.ts
// ✅ Correct
return { directory: input.writeDir };
// ❌ Wrong - never change this
// return { directory: '/some/other/path' };
processor.py
# ✅ Correct
return {'directory': input.write_dir}
# ❌ Wrong - never change this
# return {'directory': '/some/other/path'}
Processor.cs
// ✅ Correct
return new ProcessorResult { Directory = input.WriteDir };
// ❌ Wrong - never change this
// return new ProcessorResult { Directory = "/some/other/path" };

Rule 2: Use fileHelper for All File Operations

processor.ts
// ✅ Correct
const files = fileHelper.resolveAll();
files.forEach(f => f.writeFile());
// ❌ Wrong
// fs.readdirSync(input.readDir);
// fs.writeFileSync(`${input.writeDir}/file.txt`, content);
processor.py
# ✅ Correct
files = file_helper.resolve_all()
for f in files:
f.write_file()
# ❌ Wrong
# os.listdir(input.read_dir)
# open(f'{input.write_dir}/file.txt', 'w').write(content)
Processor.cs
// ✅ Correct
var files = fileHelper.ResolveAll();
foreach (var f in files) f.WriteFile();
// ❌ Wrong
// Directory.GetFiles(input.ReadDir);
// File.WriteAllText($"{input.WriteDir}/file.txt", content);

Rule 3: Preserve Directory Structure (Usually)

processor.ts
// Usually you want to maintain the same structure
files.forEach(file => {
// file.relative is preserved
file.writeFile();
});
// Output mirrors input structure
processor.py
# Usually you want to maintain the same structure
for file in files:
# file.relative is preserved
file.write_file()
# Output mirrors input structure
Processor.cs
// Usually you want to maintain the same structure
foreach (var file in files)
{
// file.Relative is preserved
file.WriteFile();
}
// Output mirrors input structure

Rule 4: Handle Missing Writes

Files not written are not included in output:

Note: Copy-type files (matched by copy globs in cyan.yaml) are automatically written during resolveAll(). Template-type files require explicit writeFile() calls. If you don't call writeFile() on a template file, it won't be included in the output.

processor.ts
files.forEach(file => {
if (shouldInclude(file)) {
file.writeFile(); // Included
}
// Template files without writeFile() are excluded
// Copy-type files are already handled by resolveAll()
});
processor.py
for file in files:
if should_include(file):
file.write_file() # Included
# Template files without write_file() are excluded
# Copy-type files are already handled by resolve_all()
Processor.cs
foreach (var file in files)
{
if (ShouldInclude(file))
{
file.WriteFile(); // Included
}
// Template files without WriteFile() are excluded
// Copy-type files are already handled by ResolveAll()
}

Example: Full Path Flow

StartProcessorWithLambda(async (input, fileHelper) => {
console.log('Reading from:', input.readDir);
// /workspace/cyanprint/
console.log('Writing to:', input.writeDir);
// /workspace/output/
// resolveAll() copies Copy-type files automatically, returns Template-type files
const files = fileHelper.resolveAll();
files.forEach(file => {
console.log('Processing:', file.relative);
// templates/README.md
// Full paths (conceptual - don't access directly)
// Input: /workspace/cyanprint/templates/README.md
// Output: /workspace/output/templates/README.md
file.content = transform(file.content);
file.writeFile();
});
return { directory: input.writeDir };
});
def start_processor_with_fn(input, file_helper):
print('Reading from:', input.read_dir)
# /workspace/cyanprint/
print('Writing to:', input.write_dir)
# /workspace/output/
# resolve_all() copies Copy-type files automatically, returns Template-type files
files = file_helper.resolve_all()
for file in files:
print('Processing:', file.relative)
# templates/README.md
# Full paths (conceptual - don't access directly)
# Input: /workspace/cyanprint/templates/README.md
# Output: /workspace/output/templates/README.md
file.content = transform(file.content)
file.write_file()
return {'directory': input.write_dir}
[ProcessorMain]
public async Task<ProcessorResult> RunAsync(ProcessorInput input, CyanFileHelper fileHelper)
{
Console.WriteLine($"Reading from: {input.ReadDir}");
// /workspace/cyanprint/
Console.WriteLine($"Writing to: {input.WriteDir}");
// /workspace/output/
// ResolveAll() copies Copy-type files automatically, returns Template-type files
var files = fileHelper.ResolveAll();
foreach (var file in files)
{
Console.WriteLine($"Processing: {file.Relative}");
// templates/README.md
// Full paths (conceptual - don't access directly)
// Input: /workspace/cyanprint/templates/README.md
// Output: /workspace/output/templates/README.md
file.Content = Transform(file.Content);
file.WriteFile();
}
return new ProcessorResult { Directory = input.WriteDir };
}