or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

date-operations.mdexpression-parsing.mdfield-system.mdfile-parsing.mdindex.mdschedule-iteration.md
tile.json

file-parsing.mddocs/

File Parsing

Parse complete crontab files with variable extraction, expression parsing, and error handling for system administration tasks and cron file management.

Capabilities

Asynchronous File Parsing

Parse crontab files asynchronously with Promise-based API.

/**
 * Parse a crontab file asynchronously
 * @param filePath - Path to the crontab file to parse
 * @returns Promise resolving to parse results containing variables, expressions, and errors
 * @throws Error if file cannot be read or doesn't exist
 */
static parseFile(filePath: string): Promise<CronFileParserResult>;

interface CronFileParserResult {
  /** Environment variables extracted from the file */
  variables: { [key: string]: string };
  /** Successfully parsed cron expressions */
  expressions: CronExpression[];
  /** Parsing errors keyed by the problematic line */
  errors: { [key: string]: unknown };
}

Usage Examples:

import { CronFileParser } from "cron-parser";

// Parse a crontab file asynchronously
async function parseCrontabFile() {
  try {
    const result = await CronFileParser.parseFile('/etc/crontab');
    
    console.log('Environment variables:');
    Object.entries(result.variables).forEach(([key, value]) => {
      console.log(`  ${key}=${value}`);
    });
    
    console.log('\nCron expressions:');
    result.expressions.forEach((expr, index) => {
      console.log(`  ${index + 1}: ${expr.toString()}`);
      console.log(`     Next run: ${expr.next().toString()}`);
    });
    
    if (Object.keys(result.errors).length > 0) {
      console.log('\nParsing errors:');
      Object.entries(result.errors).forEach(([line, error]) => {
        console.log(`  Line "${line}": ${error}`);
      });
    }
    
  } catch (error) {
    console.error('Failed to read crontab file:', error.message);
  }
}

// Parse user-specific crontab
async function parseUserCrontab(username: string) {
  const crontabPath = `/var/spool/cron/crontabs/${username}`;
  
  try {
    const result = await CronFileParser.parseFile(crontabPath);
    
    console.log(`Crontab for user ${username}:`);
    console.log(`Found ${result.expressions.length} cron jobs`);
    console.log(`Found ${Object.keys(result.variables).length} environment variables`);
    
    return result;
  } catch (error) {
    if (error.code === 'ENOENT') {
      console.log(`No crontab found for user ${username}`);
    } else {
      console.error(`Error reading crontab for ${username}:`, error.message);
    }
    return null;
  }
}

await parseCrontabFile();
const userResult = await parseUserCrontab('www-data');

Synchronous File Parsing

Parse crontab files synchronously for simpler control flow.

/**
 * Parse a crontab file synchronously
 * @param filePath - Path to the crontab file to parse
 * @returns Parse results containing variables, expressions, and errors
 * @throws Error if file cannot be read or doesn't exist
 */
static parseFileSync(filePath: string): CronFileParserResult;

Usage Examples:

import { CronFileParser } from "cron-parser";
import * as fs from 'fs';

// Parse synchronously for simple cases
function loadSystemCrontab() {
  try {
    const result = CronFileParser.parseFileSync('/etc/crontab');
    
    console.log('System crontab loaded successfully');
    console.log(`Variables: ${Object.keys(result.variables).length}`);
    console.log(`Expressions: ${result.expressions.length}`);
    console.log(`Errors: ${Object.keys(result.errors).length}`);
    
    return result;
  } catch (error) {
    console.error('Failed to load system crontab:', error.message);
    return null;
  }
}

// Compare multiple crontab files
function compareCrontabs(paths: string[]) {
  const results: { [path: string]: CronFileParserResult | null } = {};
  
  paths.forEach(path => {
    try {
      results[path] = CronFileParser.parseFileSync(path);
    } catch (error) {
      console.warn(`Skipping ${path}: ${error.message}`);
      results[path] = null;
    }
  });
  
  // Analyze results
  Object.entries(results).forEach(([path, result]) => {
    if (result) {
      console.log(`${path}: ${result.expressions.length} jobs, ${Object.keys(result.errors).length} errors`);
    }
  });
  
  return results;
}

const systemResult = loadSystemCrontab();
const comparison = compareCrontabs([
  '/etc/crontab',
  '/var/spool/cron/crontabs/root',
  '/var/spool/cron/crontabs/www-data'
]);

Crontab File Format

Understanding the crontab file format and parsing behavior.

// Crontab file format elements parsed by CronFileParser:

interface CrontabFileFormat {
  /** Empty lines - ignored during parsing */
  emptyLines: '';
  
  /** Comment lines - ignored during parsing (start with #) */
  comments: '# This is a comment';
  
  /** Environment variable assignments */
  variables: 'VARIABLE_NAME=value' | 'PATH=/usr/bin:/bin' | 'SHELL=/bin/bash';
  
  /** Cron job entries - 5 fields for timing + command */
  cronEntries: 'minute hour dayOfMonth month dayOfWeek command';
  
  /** User-specific entries (system crontab) - 6 fields with user */
  systemEntries: 'minute hour dayOfMonth month dayOfWeek user command';
}

File Content Examples:

# Example /etc/crontab content that would be parsed:

# Environment variables
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# Empty lines and comments are ignored

# m h dom mon dow   command
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )

# Invalid entries would be captured in errors
99 25 32 13 8   root    invalid-cron-entry

Usage Examples:

// Create a test crontab content
const crontabContent = `
# Test crontab file
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=admin@example.com

# Run backup every day at 2 AM  
0 2 * * * /usr/local/bin/backup.sh

# Run reports every weekday at 9 AM
0 9 * * 1-5 /usr/local/bin/generate-reports.sh

# Invalid entry for testing
99 25 * * * invalid-command

# Run maintenance on first day of month
0 3 1 * * /usr/local/bin/monthly-maintenance.sh
`;

// Write test file and parse it
import * as fs from 'fs';
import * as path from 'path';

const testFile = path.join('/tmp', 'test-crontab');
fs.writeFileSync(testFile, crontabContent);

try {
  const result = CronFileParser.parseFileSync(testFile);
  
  console.log('Parsed variables:');
  Object.entries(result.variables).forEach(([key, value]) => {
    console.log(`  ${key} = ${value}`);
  });
  /* Output:
    SHELL = /bin/bash
    PATH = /usr/local/bin:/usr/bin:/bin  
    MAILTO = admin@example.com
  */
  
  console.log('\nParsed expressions:');
  result.expressions.forEach((expr, i) => {
    console.log(`  ${i + 1}: ${expr.stringify()} - Next: ${expr.next().toString()}`);
  });
  /* Output:
    1: 0 2 * * * - Next: [next 2 AM]
    2: 0 9 * * 1-5 - Next: [next 9 AM weekday]
    3: 0 3 1 * * - Next: [next 3 AM on 1st of month]
  */
  
  console.log('\nParsing errors:');
  Object.entries(result.errors).forEach(([line, error]) => {
    console.log(`  "${line}": ${error}`);
  });
  /* Output:
    "99 25 * * * invalid-command": Constraint error, got value 99 expected range 0-59
  */
  
} finally {
  // Clean up test file
  fs.unlinkSync(testFile);
}

Error Handling

Understanding and handling parsing errors for robust crontab processing.

// Error types that may appear in CronFileParserResult.errors

interface CronFileParsingErrors {
  /** Invalid cron expression syntax */
  SyntaxError: "Invalid cron expression";
  
  /** Field values outside allowed ranges */
  ConstraintError: "Constraint error, got value [value] expected range [min]-[max]";
  
  /** Invalid characters in cron fields */
  CharacterError: "Invalid characters, got value: [value]";
  
  /** Strict mode validation failures */
  StrictModeError: "Cannot use both dayOfMonth and dayOfWeek together in strict mode!";
  
  /** General parsing failures */
  GeneralError: Error; // Any other parsing error
}

Usage Examples:

// Robust crontab parsing with comprehensive error handling
async function robustCrontabParse(filePath: string, options = {}) {
  try {
    const result = await CronFileParser.parseFile(filePath);
    
    const stats = {
      totalLines: 0,
      variableLines: Object.keys(result.variables).length,
      expressionLines: result.expressions.length,
      errorLines: Object.keys(result.errors).length,
      commentLines: 0 // Would need to count manually from file content
    };
    
    console.log(`Parsing statistics for ${filePath}:`);
    console.log(`  Variables: ${stats.variableLines}`);
    console.log(`  Valid expressions: ${stats.expressionLines}`);
    console.log(`  Errors: ${stats.errorLines}`);
    
    // Categorize errors
    const errorTypes = {
      constraint: 0,
      syntax: 0,
      character: 0,
      other: 0
    };
    
    Object.values(result.errors).forEach(error => {
      const errorMsg = error.toString().toLowerCase();
      if (errorMsg.includes('constraint')) {
        errorTypes.constraint++;
      } else if (errorMsg.includes('invalid cron')) {
        errorTypes.syntax++;
      } else if (errorMsg.includes('invalid characters')) {
        errorTypes.character++;
      } else {
        errorTypes.other++;
      }
    });
    
    if (stats.errorLines > 0) {
      console.log('  Error breakdown:');
      Object.entries(errorTypes).forEach(([type, count]) => {
        if (count > 0) {
          console.log(`    ${type}: ${count}`);
        }
      });
    }
    
    // Validate parsed expressions
    const validatedExpressions = result.expressions.filter(expr => {
      try {
        // Test if expression can generate next date
        expr.hasNext();
        return true;
      } catch (error) {
        console.warn(`Expression validation failed: ${expr.toString()} - ${error.message}`);
        return false;
      }
    });
    
    console.log(`  Validated expressions: ${validatedExpressions.length}/${result.expressions.length}`);
    
    return {
      ...result,
      stats,
      errorTypes,
      validatedExpressions
    };
    
  } catch (fileError) {
    console.error(`File access error for ${filePath}:`, fileError.message);
    return null;
  }
}

// Batch process multiple crontab files with error recovery
async function batchParseCrontabs(filePaths: string[]) {
  const results = await Promise.allSettled(
    filePaths.map(path => robustCrontabParse(path))
  );
  
  const summary = {
    successful: 0,
    failed: 0,
    totalExpressions: 0,
    totalErrors: 0
  };
  
  results.forEach((result, index) => {
    const filePath = filePaths[index];
    
    if (result.status === 'fulfilled' && result.value) {
      summary.successful++;
      summary.totalExpressions += result.value.expressions.length;
      summary.totalErrors += Object.keys(result.value.errors).length;
    } else {
      summary.failed++;
      console.error(`Failed to parse ${filePath}:`, 
        result.status === 'rejected' ? result.reason : 'Unknown error');
    }
  });
  
  console.log('\nBatch parsing summary:');
  console.log(`  Successful files: ${summary.successful}`);
  console.log(`  Failed files: ${summary.failed}`);
  console.log(`  Total expressions: ${summary.totalExpressions}`);
  console.log(`  Total errors: ${summary.totalErrors}`);
  
  return summary;
}

// Example usage
const crontabFiles = [
  '/etc/crontab',
  '/var/spool/cron/crontabs/root',
  '/var/spool/cron/crontabs/www-data',
  '/etc/cron.d/custom-jobs'
];

const batchResult = await batchParseCrontabs(crontabFiles);

Advanced Usage Patterns

Common patterns for working with parsed crontab data.

// Common utility patterns for working with CronFileParserResult

interface CrontabAnalysis {
  /** Find all jobs running at specific times */
  findJobsByTime(hour: number, minute?: number): CronExpression[];
  
  /** Find all jobs running on specific days */
  findJobsByDay(dayOfWeek: number): CronExpression[];
  
  /** Get all unique schedules */
  getUniqueSchedules(): string[];
  
  /** Find potentially conflicting jobs */
  findConflicts(): { time: string, expressions: CronExpression[] }[];
  
  /** Validate all expressions against current time */
  validateExpressions(): { valid: CronExpression[], invalid: { expr: CronExpression, error: string }[] };
}

Usage Examples:

// Advanced crontab analysis utilities
class CrontabAnalyzer {
  constructor(private result: CronFileParserResult) {}
  
  // Find all jobs that run at a specific time
  findJobsByTime(hour: number, minute: number = 0): CronExpression[] {
    return this.result.expressions.filter(expr => {
      const testDate = new Date();
      testDate.setHours(hour, minute, 0, 0);
      
      try {
        return expr.includesDate(testDate);
      } catch {
        return false;
      }
    });
  }
  
  // Find jobs running on specific days
  findJobsByDay(dayOfWeek: number): CronExpression[] {
    return this.result.expressions.filter(expr => {
      return expr.fields.dayOfWeek.values.includes(dayOfWeek);
    });
  }
  
  // Get schedule frequency analysis
  getScheduleAnalysis() {
    const frequencies = {
      minutely: 0,
      hourly: 0,
      daily: 0,
      weekly: 0,
      monthly: 0,
      yearly: 0,
      custom: 0
    };
    
    this.result.expressions.forEach(expr => {
      const schedule = expr.stringify();
      
      if (schedule.startsWith('*')) frequencies.minutely++;
      else if (schedule.includes('* * * *')) frequencies.hourly++;
      else if (schedule.includes('* * *') && !schedule.includes('* * * *')) frequencies.daily++;
      else if (schedule.includes('* * 0') || schedule.includes('* * 7')) frequencies.weekly++;
      else if (schedule.includes('1 * *')) frequencies.monthly++;
      else if (schedule.includes('1 1 *')) frequencies.yearly++;
      else frequencies.custom++;
    });
    
    return frequencies;
  }
  
  // Find next execution times for all jobs
  getUpcomingExecutions(limit: number = 5): { expression: string, nextRun: Date }[] {
    const upcoming: { expression: string, nextRun: Date, cronExpr: CronExpression }[] = [];
    
    this.result.expressions.forEach(expr => {
      try {
        const next = expr.next();
        upcoming.push({
          expression: expr.stringify(),
          nextRun: new Date(next.getTime()),
          cronExpr: expr
        });
        expr.reset(); // Reset to avoid affecting original
      } catch (error) {
        // Skip expressions that can't generate next execution
      }
    });
    
    // Sort by next execution time and return top N
    return upcoming
      .sort((a, b) => a.nextRun.getTime() - b.nextRun.getTime())
      .slice(0, limit)
      .map(({ expression, nextRun }) => ({ expression, nextRun }));
  }
}

// Example usage
async function analyzeCrontab(filePath: string) {
  try {
    const result = await CronFileParser.parseFile(filePath);
    const analyzer = new CrontabAnalyzer(result);
    
    console.log(`\nAnalyzing ${filePath}:`);
    
    // Schedule frequency analysis
    const frequencies = analyzer.getScheduleAnalysis();
    console.log('Schedule frequencies:', frequencies);
    
    // Find jobs running at specific times
    const jobsAt2AM = analyzer.findJobsByTime(2, 0);
    console.log(`Jobs running at 2:00 AM: ${jobsAt2AM.length}`);
    
    const weekendJobs = analyzer.findJobsByDay(0).concat(analyzer.findJobsByDay(6));
    console.log(`Weekend jobs: ${weekendJobs.length}`);
    
    // Upcoming executions
    const upcoming = analyzer.getUpcomingExecutions(10);
    console.log('\nNext 10 executions:');
    upcoming.forEach(({ expression, nextRun }, i) => {
      console.log(`  ${i + 1}. ${expression} - ${nextRun.toLocaleString()}`);
    });
    
  } catch (error) {
    console.error(`Analysis failed for ${filePath}:`, error.message);
  }
}

await analyzeCrontab('/etc/crontab');