/ bin / index.js
index.js
 1  #! /usr/bin/env node
 2  
 3  /* global process */
 4  
 5  import { program } from 'commander';
 6  import { streamParse, Context } from 'bablr';
 7  import { embeddedSourceFrom, readFromStream, stripTrailingNewline } from '@bablr/helpers/source';
 8  import { debugEnhancers } from '@bablr/helpers/enhancers';
 9  import colorSupport from 'color-support';
10  import { evaluateIO } from '@bablr/io-vm-node';
11  import { generateCSTML } from '../lib/syntax.js';
12  import {
13    buildBasicNodeMatcher,
14    buildOpenNodeMatcher,
15    buildPropertyMatcher,
16  } from '@bablr/helpers/builders';
17  import { buildEmbeddedMatcher, evaluateReturnAsync } from '@bablr/agast-helpers/tree';
18  
19  program
20    .name('bablr')
21    .option('-l, --language [URL]', 'The URL of the top BABLR language')
22    .option('-p, --production [type]', 'The name of the top production type')
23    .option('-f, --format', 'Pretty-format CSTML output', true)
24    .option('-g, --gaps', 'The source and resulting tree may contain gaps')
25    .option('-F, --no-format')
26    .option('-v, --verbose', 'Prints debugging information to stderr')
27    .option(
28      '-c, --color [WHEN]',
29      'When to use ANSI escape colors \n  WHEN: "auto" | "always" | "never"',
30      'auto',
31    )
32    .option('-e, --embedded', 'Requires quoted input but enables gap parsing')
33    .parse(process.argv);
34  
35  const programOpts = program.opts();
36  
37  if (programOpts.color && !['auto', 'always', 'never'].includes(programOpts.color.toLowerCase())) {
38    throw new Error('invalid value for --color');
39  }
40  
41  const options = {
42    ...programOpts,
43    color:
44      (programOpts.color.toLowerCase() === 'auto' && colorSupport.hasBasic) ||
45      programOpts.color.toLowerCase() === 'always',
46  };
47  
48  const language = await import(options.language);
49  
50  const matcher = buildEmbeddedMatcher(
51    buildPropertyMatcher(
52      null,
53      buildBasicNodeMatcher(
54        buildOpenNodeMatcher({ hasGap: options.gaps }, language.canonicalURL, options.production),
55      ),
56    ),
57  );
58  
59  const logStderr = (...args) => {
60    process.stderr.write(args.join(' ') + '\n');
61  };
62  
63  const enhancers = options.verbose ? { ...debugEnhancers, agast: null } : {};
64  
65  const ctx = Context.from(language, enhancers.bablrProduction);
66  
67  const rawStream = process.stdin.setEncoding('utf-8');
68  
69  const output = evaluateIO(() =>
70    generateCSTML(
71      streamParse(
72        ctx,
73        matcher,
74        options.embedded
75          ? embeddedSourceFrom(readFromStream(rawStream))
76          : stripTrailingNewline(readFromStream(rawStream)),
77        {},
78        { enhancers, emitEffects: true },
79      ),
80      {
81        ctx,
82        color: options.color,
83        format: options.format,
84        emitEffects: true,
85      },
86    ),
87  );
88  
89  await evaluateReturnAsync(output);