format utils for printing
This commit is contained in:
parent
5fde9f667b
commit
b949d4a6c6
125
server/src/printer/__tests__/format-utils.test.ts
Normal file
125
server/src/printer/__tests__/format-utils.test.ts
Normal 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',
|
||||||
|
''
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
50
server/src/printer/format-utils.ts
Normal file
50
server/src/printer/format-utils.ts
Normal 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,
|
||||||
|
'',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
};
|
@ -4,6 +4,7 @@ import { Task, Step, Printer as PrinterInterface } from '@shared/index';
|
|||||||
import { StepRepository, PrintHistoryRepository } from '../db/repositories';
|
import { StepRepository, PrintHistoryRepository } from '../db/repositories';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
import { formatUtils } from './format-utils';
|
||||||
|
|
||||||
export class SerialPrinter implements PrinterInterface {
|
export class SerialPrinter implements PrinterInterface {
|
||||||
private device: USB | null = null;
|
private device: USB | null = null;
|
||||||
@ -57,8 +58,8 @@ export class SerialPrinter implements PrinterInterface {
|
|||||||
.align('ct')
|
.align('ct')
|
||||||
.style('b')
|
.style('b')
|
||||||
.size(1, 1) // Normal size (0.08 x 2.13 mm)
|
.size(1, 1) // Normal size (0.08 x 2.13 mm)
|
||||||
.text(`[ ] Task: ${task.name}`)
|
.text(formatUtils.formatCheckbox(`Task: ${task.name}`))
|
||||||
.text('='.repeat(32))
|
.text(formatUtils.createBanner('=', 32))
|
||||||
.text('')
|
.text('')
|
||||||
.align('lt');
|
.align('lt');
|
||||||
|
|
||||||
@ -71,12 +72,14 @@ export class SerialPrinter implements PrinterInterface {
|
|||||||
// Print steps
|
// Print steps
|
||||||
for (let i = 0; i < taskSteps.length; i++) {
|
for (let i = 0; i < taskSteps.length; i++) {
|
||||||
const step = taskSteps[i];
|
const step = taskSteps[i];
|
||||||
|
const stepSection = formatUtils.formatSection(`Step ${i + 1}: ${step.name}`, step.instructions, '-');
|
||||||
|
|
||||||
await this.printer
|
await this.printer
|
||||||
.size(1, 1) // Normal size for step header
|
.size(1, 1) // Normal size for step header
|
||||||
.text(`[ ] Step ${i + 1}: ${step.name}`)
|
.text(stepSection[0]) // Header with checkbox
|
||||||
.text('-'.repeat(32))
|
.text(stepSection[1]) // Banner
|
||||||
.size(0, 0) // Smaller size for instructions (0.08 x 2.13 mm)
|
.size(0, 0) // Smaller size for instructions
|
||||||
.text(step.instructions)
|
.text(stepSection[3]) // Instructions
|
||||||
.text('');
|
.text('');
|
||||||
|
|
||||||
// Print step ID as barcode
|
// Print step ID as barcode
|
||||||
@ -110,17 +113,19 @@ export class SerialPrinter implements PrinterInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const stepSection = formatUtils.formatSection(`Step: ${step.name}`, step.instructions);
|
||||||
|
|
||||||
await this.printer
|
await this.printer
|
||||||
.font('a')
|
.font('a')
|
||||||
.align('ct')
|
.align('ct')
|
||||||
.style('b')
|
.style('b')
|
||||||
.size(1, 1) // Normal size (0.08 x 2.13 mm)
|
.size(1, 1) // Normal size (0.08 x 2.13 mm)
|
||||||
.text(`[ ] Step: ${step.name}`)
|
.text(stepSection[0]) // Header with checkbox
|
||||||
.text('='.repeat(32))
|
.text(stepSection[1]) // Banner
|
||||||
.text('')
|
.text('')
|
||||||
.align('lt')
|
.align('lt')
|
||||||
.size(0, 0) // Smaller size for instructions
|
.size(0, 0) // Smaller size for instructions
|
||||||
.text(step.instructions)
|
.text(stepSection[3]) // Instructions
|
||||||
.text('')
|
.text('')
|
||||||
.text('');
|
.text('');
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { Task, Step, Printer } from '@shared/index';
|
|||||||
import { StepRepository, PrintHistoryRepository } from '../db/repositories';
|
import { StepRepository, PrintHistoryRepository } from '../db/repositories';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
import { formatUtils } from './format-utils';
|
||||||
|
|
||||||
export class TestPrinter implements Printer {
|
export class TestPrinter implements Printer {
|
||||||
private readonly outputDir: string;
|
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 filename = path.join(this.outputDir, `task-${task.id}-${timestamp}.txt`);
|
||||||
|
|
||||||
const content = [
|
const content = [
|
||||||
`[ ] Task: ${task.name}`,
|
...formatUtils.formatSection(`Task: ${task.name}`, ''),
|
||||||
'='.repeat(40),
|
...taskSteps.map((step, index) =>
|
||||||
'',
|
formatUtils.formatSection(`Step ${index + 1}: ${step.name}`, step.instructions, '-')
|
||||||
...taskSteps.map((step, index) => [
|
).flat(),
|
||||||
`[ ] Step ${index + 1}: ${step.name}`,
|
|
||||||
'-'.repeat(40),
|
|
||||||
step.instructions,
|
|
||||||
'',
|
|
||||||
]).flat(),
|
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
await fs.writeFile(filename, content);
|
await fs.writeFile(filename, content);
|
||||||
@ -61,13 +57,7 @@ export class TestPrinter implements Printer {
|
|||||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
const filename = path.join(this.outputDir, `step-${step.id}-${timestamp}.txt`);
|
const filename = path.join(this.outputDir, `step-${step.id}-${timestamp}.txt`);
|
||||||
|
|
||||||
const content = [
|
const content = formatUtils.formatSection(`Step: ${step.name}`, step.instructions).join('\n');
|
||||||
`[ ] Step: ${step.name}`,
|
|
||||||
'='.repeat(40),
|
|
||||||
'',
|
|
||||||
step.instructions,
|
|
||||||
'',
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
await fs.writeFile(filename, content);
|
await fs.writeFile(filename, content);
|
||||||
logger.info(`Printed step ${step.id} to ${filename}`);
|
logger.info(`Printed step ${step.id} to ${filename}`);
|
||||||
|
Loading…
Reference in New Issue
Block a user