All files / backend/src/utils format-provider-resolver.ts

69.76% Statements 30/43
43.75% Branches 7/16
64.28% Functions 9/14
76.31% Lines 29/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149                                              26x 26x             26x                                           9x 9x           9x 9x 9x   9x 9x 9x 9x       9x       9x                   10x   10x 30x 30x 10x                   38x 38x 38x                                   9x 9x 9x                 47x 47x 47x             55x                
/**
 * Format Provider Resolver
 *
 * Resolves the appropriate format provider (YAML, TOML, JSON) based on
 * file extension, file content, or first line of frontmatter.
 */
 
import fs from 'fs-extra';
import path from 'path';
import type { FormatProvider } from './format-providers/types.js';
import { JsonFormatProvider } from './format-providers/json-format-provider.js';
import { TomlFormatProvider } from './format-providers/toml-format-provider.js';
import { YamlFormatProvider } from './format-providers/yaml-format-provider.js';
 
/**
 * FormatProviderResolver manages format providers and resolves the correct one
 * based on various inputs (file path, content, first line, etc.)
 */
export class FormatProviderResolver {
  private _formats: Record<string, FormatProvider>;
  private _exts: string[];
 
  constructor() {
    const yaml = new YamlFormatProvider();
    this._formats = {
      json: new JsonFormatProvider(),
      toml: new TomlFormatProvider(),
      yaml: yaml,
      yml: yaml, // yml is an alias for yaml
    };
 
    this._exts = Object.keys(this._formats);
  }
 
  /**
   * Get the default format provider (YAML)
   */
  getDefaultFormat(): FormatProvider {
    return this._formats[this.getDefaultFormatExt()];
  }
 
  /**
   * Get the default format extension
   */
  getDefaultFormatExt(): string {
    return 'yaml';
  }
 
  /**
   * Read a specific line from a file (async)
   * @private
   */
  private _getFileLinePromise(filename: string, line_no: number): Promise<string | undefined> {
    return new Promise((resolve, reject) => {
      const stream = fs.createReadStream(filename, {
        flags: 'r',
        encoding: 'utf8',
        mode: 0o666,
      });
 
      let fileData = '';
      stream.on('data', (data: string | Buffer) => {
        fileData += data.toString();
        // The next lines should be improved
        const lines = fileData.split('\n');
        Eif (lines.length >= line_no + 1) {
          stream.destroy();
          resolve(lines[line_no]);
        }
      });
 
      stream.on('error', (e) => {
        reject(e);
      });
 
      stream.on('end', () => {
        resolve(undefined);
      });
    });
  }
 
  /**
   * Resolve format provider by matching the first line of frontmatter
   */
  resolveForMdFirstLine(line: string | undefined): FormatProvider | undefined {
    Iif (line === undefined) return undefined;
 
    for (let i = 0; i < this._exts.length; i++) {
      const f = this._formats[this._exts[i]];
      if (f.matchContentFirstLine(line)) {
        return f;
      }
    }
    return undefined;
  }
 
  /**
   * Resolve format provider based on file path extension
   */
  resolveForFilePath(filePath: string | undefined): FormatProvider | undefined {
    Iif (filePath === undefined) return undefined;
    const ext = path.extname(filePath).replace('.', '');
    return this.resolveForExtension(ext);
  }
 
  /**
   * Resolve format provider by analyzing file content string
   */
  resolveForMdFileString(fileContent: string | undefined): FormatProvider | null {
    if (fileContent === undefined) return null;
    const firstLine = fileContent.split('\n', 1)[0];
    return this.resolveForMdFirstLine(firstLine) ?? null;
  }
 
  /**
   * Resolve format provider by reading file and checking first line
   */
  async resolveForMdFilePromise(
    filePath: string
  ): Promise<FormatProvider | null | undefined> {
    const line = await this._getFileLinePromise(filePath, 0);
    Eif (line != null) {
      return this.resolveForMdFirstLine(line);
    }
    return null;
  }
 
  /**
   * Resolve format provider by file extension
   */
  resolveForExtension(ext: string | undefined): FormatProvider | undefined {
    Iif (ext === undefined) return undefined;
    ext = ext.toLowerCase();
    return this._formats[ext];
  }
 
  /**
   * Get all supported format extensions
   */
  allFormatsExt(): string[] {
    return this._exts;
  }
}
 
/**
 * Default singleton instance
 */
export default new FormatProviderResolver();