format utils for printing

This commit is contained in:
Sean Sube 2025-06-14 20:24:42 -05:00
parent 5fde9f667b
commit b949d4a6c6
No known key found for this signature in database
GPG Key ID: 3EED7B957D362AF1
4 changed files with 195 additions and 25 deletions

View File

@ -0,0 +1,125 @@
import { formatUtils } from '../format-utils';
describe('formatUtils', () => {
describe('createBanner', () => {
it('should create a banner with default length', () => {
const banner = formatUtils.createBanner('=');
expect(banner).toBe('='.repeat(40));
});
it('should create a banner with custom length', () => {
const banner = formatUtils.createBanner('-', 32);
expect(banner).toBe('-'.repeat(32));
});
it('should handle empty character', () => {
const banner = formatUtils.createBanner('', 10);
expect(banner).toBe('');
});
it('should handle zero length', () => {
const banner = formatUtils.createBanner('*', 0);
expect(banner).toBe('');
});
});
describe('formatCheckbox', () => {
it('should format text with checkbox', () => {
const formatted = formatUtils.formatCheckbox('Test Item');
expect(formatted).toBe('[ ] Test Item');
});
it('should handle empty text', () => {
const formatted = formatUtils.formatCheckbox('');
expect(formatted).toBe('[ ] ');
});
it('should handle text with special characters', () => {
const formatted = formatUtils.formatCheckbox('Test: Item (123)');
expect(formatted).toBe('[ ] Test: Item (123)');
});
});
describe('formatList', () => {
it('should format list without numbering', () => {
const items = ['Item 1', 'Item 2', 'Item 3'];
const formatted = formatUtils.formatList(items);
expect(formatted).toEqual([
'[ ] Item 1',
'[ ] Item 2',
'[ ] Item 3'
]);
});
it('should format list with numbering', () => {
const items = ['Item 1', 'Item 2', 'Item 3'];
const formatted = formatUtils.formatList(items, 1);
expect(formatted).toEqual([
'[ ] 1: Item 1',
'[ ] 2: Item 2',
'[ ] 3: Item 3'
]);
});
it('should handle empty list', () => {
const formatted = formatUtils.formatList([]);
expect(formatted).toEqual([]);
});
it('should handle list with empty items', () => {
const items = ['', 'Item 2', ''];
const formatted = formatUtils.formatList(items);
expect(formatted).toEqual([
'[ ] ',
'[ ] Item 2',
'[ ] '
]);
});
});
describe('formatSection', () => {
it('should format section with default banner', () => {
const section = formatUtils.formatSection('Header', 'Content');
expect(section).toEqual([
'[ ] Header',
'='.repeat(40),
'',
'Content',
''
]);
});
it('should format section with custom banner', () => {
const section = formatUtils.formatSection('Header', 'Content', '-');
expect(section).toEqual([
'[ ] Header',
'-'.repeat(40),
'',
'Content',
''
]);
});
it('should handle empty content', () => {
const section = formatUtils.formatSection('Header', '');
expect(section).toEqual([
'[ ] Header',
'='.repeat(40),
'',
'',
''
]);
});
it('should handle empty header', () => {
const section = formatUtils.formatSection('', 'Content');
expect(section).toEqual([
'[ ] ',
'='.repeat(40),
'',
'Content',
''
]);
});
});
});

View File

@ -0,0 +1,50 @@
export const formatUtils = {
/**
* Creates a banner line with the specified character
* @param char Character to repeat
* @param length Length of the banner
* @returns Formatted banner string
*/
createBanner(char: string, length: number = 40): string {
return char.repeat(length);
},
/**
* Formats a checkbox item with the given text
* @param text Text to display after the checkbox
* @returns Formatted checkbox string
*/
formatCheckbox(text: string): string {
return `[ ] ${text}`;
},
/**
* Formats a list of items into lines with optional numbering
* @param items List of items to format
* @param startIndex Starting index for numbering (0 for no numbers)
* @returns Array of formatted lines
*/
formatList(items: string[], startIndex: number = 0): string[] {
return items.map((item, index) => {
const num = startIndex > 0 ? `${index + startIndex}: ` : '';
return this.formatCheckbox(`${num}${item}`);
});
},
/**
* Formats a section with a header and content
* @param header Section header
* @param content Section content
* @param bannerChar Character to use for the banner
* @returns Array of formatted lines
*/
formatSection(header: string, content: string, bannerChar: string = '='): string[] {
return [
this.formatCheckbox(header),
this.createBanner(bannerChar),
'',
content,
'',
];
}
};

View File

@ -4,6 +4,7 @@ import { Task, Step, Printer as PrinterInterface } from '@shared/index';
import { StepRepository, PrintHistoryRepository } from '../db/repositories';
import { Knex } from 'knex';
import logger from '../logger';
import { formatUtils } from './format-utils';
export class SerialPrinter implements PrinterInterface {
private device: USB | null = null;
@ -57,8 +58,8 @@ export class SerialPrinter implements PrinterInterface {
.align('ct')
.style('b')
.size(1, 1) // Normal size (0.08 x 2.13 mm)
.text(`[ ] Task: ${task.name}`)
.text('='.repeat(32))
.text(formatUtils.formatCheckbox(`Task: ${task.name}`))
.text(formatUtils.createBanner('=', 32))
.text('')
.align('lt');
@ -71,12 +72,14 @@ export class SerialPrinter implements PrinterInterface {
// Print steps
for (let i = 0; i < taskSteps.length; i++) {
const step = taskSteps[i];
const stepSection = formatUtils.formatSection(`Step ${i + 1}: ${step.name}`, step.instructions, '-');
await this.printer
.size(1, 1) // Normal size for step header
.text(`[ ] Step ${i + 1}: ${step.name}`)
.text('-'.repeat(32))
.size(0, 0) // Smaller size for instructions (0.08 x 2.13 mm)
.text(step.instructions)
.text(stepSection[0]) // Header with checkbox
.text(stepSection[1]) // Banner
.size(0, 0) // Smaller size for instructions
.text(stepSection[3]) // Instructions
.text('');
// Print step ID as barcode
@ -110,17 +113,19 @@ export class SerialPrinter implements PrinterInterface {
}
try {
const stepSection = formatUtils.formatSection(`Step: ${step.name}`, step.instructions);
await this.printer
.font('a')
.align('ct')
.style('b')
.size(1, 1) // Normal size (0.08 x 2.13 mm)
.text(`[ ] Step: ${step.name}`)
.text('='.repeat(32))
.text(stepSection[0]) // Header with checkbox
.text(stepSection[1]) // Banner
.text('')
.align('lt')
.size(0, 0) // Smaller size for instructions
.text(step.instructions)
.text(stepSection[3]) // Instructions
.text('')
.text('');

View File

@ -4,6 +4,7 @@ import { Task, Step, Printer } from '@shared/index';
import { StepRepository, PrintHistoryRepository } from '../db/repositories';
import { Knex } from 'knex';
import logger from '../logger';
import { formatUtils } from './format-utils';
export class TestPrinter implements Printer {
private readonly outputDir: string;
@ -35,15 +36,10 @@ export class TestPrinter implements Printer {
const filename = path.join(this.outputDir, `task-${task.id}-${timestamp}.txt`);
const content = [
`[ ] Task: ${task.name}`,
'='.repeat(40),
'',
...taskSteps.map((step, index) => [
`[ ] Step ${index + 1}: ${step.name}`,
'-'.repeat(40),
step.instructions,
'',
]).flat(),
...formatUtils.formatSection(`Task: ${task.name}`, ''),
...taskSteps.map((step, index) =>
formatUtils.formatSection(`Step ${index + 1}: ${step.name}`, step.instructions, '-')
).flat(),
].join('\n');
await fs.writeFile(filename, content);
@ -61,13 +57,7 @@ export class TestPrinter implements Printer {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = path.join(this.outputDir, `step-${step.id}-${timestamp}.txt`);
const content = [
`[ ] Step: ${step.name}`,
'='.repeat(40),
'',
step.instructions,
'',
].join('\n');
const content = formatUtils.formatSection(`Step: ${step.name}`, step.instructions).join('\n');
await fs.writeFile(filename, content);
logger.info(`Printed step ${step.id} to ${filename}`);