val 0.3.6

An arbitrary precision calculator language
Documentation
import argparse
import os
import shutil
from textwrap import dedent

from arrg import app, argument


def snake_to_camel(snake_str):
  components = snake_str.split('_')
  return components[0] + ''.join(x.title() for x in components[1:])


@app(
  description='Generate a JavaScript dictionary from val source files using imports.',
  formatter_class=argparse.RawDescriptionHelpFormatter,
  epilog=dedent(
    """\
    Examples:
      %(prog)s ./src/tests ./frontend/src/lib/examples.js
      %(prog)s /path/to/val/files /path/to/output.js
    """
  ),
)
class App:
  source_dir: str = argument(help='Directory containing the val source files')
  output_file: str = argument(help='Path to the output JavaScript file')
  examples_dir: str = argument(
    help='Path to the assets/examples directory', default=None, nargs='?'
  )

  def run(self) -> int:
    try:
      self.generate()
      return 0
    except Exception as e:
      print(f'error: {e}')
      return 1

  def generate(self):
    if not os.path.exists(self.source_dir):
      raise FileNotFoundError(f"Source directory '{self.source_dir}' does not exist")

    if self.examples_dir is None:
      output_dir = os.path.dirname(self.output_file)
      base_dir = os.path.dirname(output_dir)
      self.examples_dir = os.path.join(base_dir, 'assets', 'examples')

    os.makedirs(self.examples_dir, exist_ok=True)
    os.makedirs(os.path.dirname(self.output_file), exist_ok=True)

    val_files = sorted([f for f in os.listdir(self.source_dir) if f.endswith('.val')])

    if not val_files:
      print(f'Warning: No .val files found in {self.source_dir}')
      return

    for file in val_files:
      source_file = os.path.join(self.source_dir, file)
      target_file = os.path.join(self.examples_dir, file)
      shutil.copy2(source_file, target_file)
      print(f'Copied {file} to {self.examples_dir}')

    imports = []
    dictionary_entries = []

    for file in val_files:
      base_name = file.replace('.val', '')
      var_name = snake_to_camel(base_name)
      rel_path = os.path.relpath(self.examples_dir, os.path.dirname(self.output_file))
      imports.append(f"import {var_name} from '{rel_path}/{file}?raw';")
      dictionary_entries.append(f'  {var_name}: {var_name}')

    content = self._generate_js_dictionary(imports, dictionary_entries)

    with open(self.output_file, 'w') as f:
      f.write(content)

    print(f'Successfully generated JS dictionary at {self.output_file}')
    print(f'Processed {len(val_files)} val files')

  def _generate_js_dictionary(self, imports, dictionary_entries):
    lines = [
      '// This file is generated by `example-generator`. Do not edit manually.',
      '',
    ]

    for import_line in imports:
      lines.append(import_line)

    lines.append('')
    lines.append('const EXAMPLES = {')

    dictionary_text = ',\n'.join(dictionary_entries)
    lines.append(dictionary_text)

    lines.append('};')
    lines.append('')
    lines.append('export default EXAMPLES;')

    return '\n'.join(lines)


if __name__ == '__main__':
  exit(App.from_args().run())