Rearrange the file tree.
[u/mdw/catacomb] / multigen
diff --git a/multigen b/multigen
deleted file mode 100755 (executable)
index 2d626c2..0000000
--- a/multigen
+++ /dev/null
@@ -1,406 +0,0 @@
-#! @PYTHON@
-###
-### Generate files by filling in simple templates
-###
-### (c) 2013 Straylight/Edgeware
-###
-
-###----- Licensing notice ---------------------------------------------------
-###
-### This file is part of Catacomb.
-###
-### Catacomb is free software; you can redistribute it and/or modify
-### it under the terms of the GNU Library General Public License as
-### published by the Free Software Foundation; either version 2 of the
-### License, or (at your option) any later version.
-###
-### Catacomb is distributed in the hope that it will be useful,
-### but WITHOUT ANY WARRANTY; without even the implied warranty of
-### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-### GNU Library General Public License for more details.
-###
-### You should have received a copy of the GNU Library General Public
-### License along with Catacomb; if not, write to the Free
-### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-### MA 02111-1307, USA.
-
-from __future__ import with_statement
-
-import itertools as IT
-import optparse as OP
-import os as OS
-import re as RX
-from cStringIO import StringIO
-from sys import argv, exit, stderr
-
-###--------------------------------------------------------------------------
-### Utilities.
-
-QUIS = OS.path.basename(argv[0])
-
-def die(msg):
-  stderr.write('%s: %s\n' % (QUIS, msg))
-  exit(1)
-
-def indexed(seq):
-  return IT.izip(IT.count(), seq)
-
-###--------------------------------------------------------------------------
-### Reading the input values.
-
-COLMAP = {}
-
-class Cursor (object):
-  def __init__(me, rel):
-    me._rel = rel
-    me._i = 0
-    me._row = rel[0]
-  def step(me):
-    me._i += 1
-    if me._i >= len(me._rel):
-      me._i = me._row = None
-      return False
-    me._row = me._rel[me._i]
-    return True
-  def reset(me):
-    me._i = 0
-    me._row = me._rel[0]
-  def __getitem__(me, i):
-    return me._row[i]
-  def __repr__(me):
-    return '#<Cursor %r[%d] = %r>' % (me._rel, me._i, me._row)
-
-class CursorSet (object):
-  def __init__(me):
-    me._map = {}
-    me._stack = []
-    me._act = None
-  def push(me, rels):
-    cc = []
-    rr = []
-    for r in rels:
-      if r in me._map: continue
-      c = me._map[r] = Cursor(r)
-      rr.append(r)
-      cc.append(c)
-    me._stack.append((me._act, rr))
-    me._act = cc
-  def step(me):
-    i = 0
-    while i < len(me._act):
-      if me._act[i].step(): return True
-      if i >= len(me._act): return False
-      me._act[i].reset()
-      i += 1
-    return False
-  def pop(me):
-    me._act, rels = me._stack.pop()
-    for r in rels: del me._map[r]
-  def get(me, rel, i):
-    return me._map[rel][i]
-
-class Relation (object):
-  def __init__(me, head):
-    me._head = head
-    me._rows = []
-    for i, c in indexed(head): COLMAP[c] = me, i
-  def addrow(me, row):
-    if len(row) != len(me._head):
-      die("mismatch: row `%s' doesn't match heading `%s'" %
-          (', '.join(row), ', '.join(head)))
-    me._rows.append(row)
-  def __len__(me):
-    return len(me._rows)
-  def __getitem__(me, i):
-    return me._rows[i]
-  def __repr__(me):
-    return '#<Relation %r>' % me._head
-
-def read_immediate(word):
-  head, rels = word.split('=', 1)
-  rel = Relation([c.strip() for c in head.split(',')])
-  for row in rels.split(): rel.addrow([c.strip() for c in row.split(',')])
-
-def read_file(spec):
-  file, head = spec.split(':', 1)
-  rel = Relation([c.strip() for c in head.split(',')])
-  cols = [c.strip() for c in head.split(',')]
-  with open(file) as f:
-    for line in f:
-      line = line.strip()
-      if line.startswith('#') or line == '': continue
-      rel.addrow(line.split())
-
-def read_thing(spec):
-  if spec.startswith('@'): read_file(spec[1:])
-  else: read_immediate(spec)
-
-###--------------------------------------------------------------------------
-### Template structure.
-
-class BasicTemplate (object):
-  pass
-
-class LiteralTemplate (BasicTemplate):
-  def __init__(me, text, **kw):
-    super(LiteralTemplate, me).__init__(**kw)
-    me._text = text
-  def relations(me):
-    return set()
-  def subst(me, out, cs):
-    out.write(me._text)
-  def __repr__(me):
-    return '#<LiteralTemplate %r>' % me._text
-
-class TagTemplate (BasicTemplate):
-  def __init__(me, rel, i, op, **kw):
-    super(TagTemplate, me).__init__(**kw)
-    me._rel = rel
-    me._i = i
-    me._op = op
-  def relations(me):
-    return set([me._rel])
-  def subst(me, out, cs):
-    val = cs.get(me._rel, me._i)
-    if me._op is not None: val = me._op(val)
-    out.write(val)
-  def __repr__(me):
-    return '#<TagTemplate %s>' % me._rel._head[me._i]
-
-class SequenceTemplate (BasicTemplate):
-  def __new__(cls, seq, **kw):
-    if len(seq) == 1:
-      return seq[0]
-    else:
-      me = super(SequenceTemplate, cls).__new__(cls, seq = seq, **kw)
-      tt = []
-      cls = type(me)
-      for t in seq:
-        if isinstance(t, cls): tt += t._seq
-        else: tt.append(t)
-      me._seq = tt
-      return me
-  def __init__(me, seq, **kw):
-    super(SequenceTemplate, me).__init__(**kw)
-  def relations(me):
-    rr = set()
-    for t in me._seq: rr.update(t.relations())
-    return rr
-  def subst(me, out, cs):
-    for t in me._seq: t.subst(out, cs)
-  def __repr__(me):
-    return '#<SequenceTemplate %r>' % me._seq
-
-class RepeatTemplate (BasicTemplate):
-  def __init__(me, sub):
-    me._sub = sub
-  def relations(me):
-    return set()
-  def subst(me, out, cs):
-    rr = me._sub.relations()
-    for r in rr:
-      if len(r) == 0: return
-    cs.push(rr)
-    while True:
-      me._sub.subst(out, cs)
-      if not cs.step(): break
-    cs.pop()
-  def __repr__(me):
-    return '#<RepeatTemplate %r>' % me._sub
-
-###--------------------------------------------------------------------------
-### Some slightly cheesy parsing machinery.
-
-class ParseState (object):
-  def __init__(me, file, text):
-    me._file = file
-    me._i = 0
-    me._it = iter(text.splitlines(True))
-    me.step()
-  def step(me):
-    me.curr = next(me._it, None)
-    if me.curr is not None: me._i += 1
-  def error(me, msg):
-    die('%s:%d: %s' % (me._file, me._i, msg))
-
-class token (object):
-  def __init__(me, name):
-    me._name = name
-  def __repr__(me):
-    return '#<%s>' % me._name
-
-EOF = token('eof')
-END = token('end')
-
-R_SIMPLETAG = RX.compile(r'@ (\w+)', RX.VERBOSE)
-R_COMPLEXTAG = RX.compile(r'@ { (\w+) ((?: : \w+)*) }', RX.VERBOSE)
-
-OPMAP = {}
-
-def defop(func):
-  name = func.func_name
-  if name.startswith('op_'): name = name[3:]
-  OPMAP[name] = func
-  return func
-
-@defop
-def op_u(val): return val.upper()
-
-@defop
-def op_l(val): return val.lower()
-
-R_NOTIDENT = RX.compile(r'[^a-zA-Z0-9_]+')
-@defop
-def op_c(val): return R_NOTIDENT.sub('_', val)
-
-def _pairify(val):
-  c = val.find('=')
-  if c >= 0: return val[:c], val[c + 1:]
-  else: return val, val
-
-@defop
-def op_left(val): return _pairify(val)[0]
-@defop
-def op_right(val): return _pairify(val)[1]
-
-def parse_text(ps):
-  tt = []
-  lit = StringIO()
-  def spill():
-    l = lit.getvalue()
-    if l: tt.append(LiteralTemplate(l))
-    lit.reset()
-    lit.truncate()
-  while True:
-    line = ps.curr
-    if line is None: break
-    elif line.startswith('%'):
-      if line.startswith('%#'): ps.step(); continue
-      elif line.startswith('%%'): line = line[1:]
-      else: break
-    i = 0
-    while True:
-      j = line.find('@', i)
-      if j < 0: break
-      lit.write(line[i:j])
-      m = R_SIMPLETAG.match(line, j)
-      if not m: m = R_COMPLEXTAG.match(line, j)
-      if not m: ps.error('invalid tag')
-      col = m.group(1)
-      try: rel, i = COLMAP[col]
-      except KeyError: ps.error("unknown column `%s'" % col)
-      wholeop = None
-      ops = m.lastindex >= 2 and m.group(2)
-      if ops:
-        for opname in ops[1:].split(':'):
-          try: op = OPMAP[opname]
-          except KeyError: ps.error("unknown operation `%s'" % opname)
-          if wholeop is None: wholeop = op
-          else: wholeop = (lambda f, g: lambda x: f(g(x)))(op, wholeop)
-      spill()
-      tt.append(TagTemplate(rel, i, wholeop))
-      i = m.end()
-    lit.write(line[i:])
-    ps.step()
-  spill()
-  return SequenceTemplate(tt)
-
-DIRECT = []
-
-def direct(rx):
-  def _(func):
-    DIRECT.append((RX.compile(rx, RX.VERBOSE), func))
-    return func
-  return _
-
-def parse_template(ps):
-  while ps.curr is not None and ps.curr.startswith('%#'): ps.step()
-  if ps.curr is None: return EOF
-  elif ps.curr.startswith('%'):
-    if ps.curr.startswith('%%'): return parse_text(ps)
-    for rx, func in DIRECT:
-      line = ps.curr[1:].strip()
-      m = rx.match(line)
-      if m:
-        ps.step()
-        return func(ps, m)
-    ps.error("unrecognized directive")
-  else:
-    return parse_text(ps)
-
-def parse_templseq(ps, nestp):
-  tt = []
-  while True:
-    t = parse_template(ps)
-    if t is END:
-      if nestp: break
-      else: ps.error("unexpected `end' directive")
-    elif t is EOF:
-      if nestp: ps.error("unexpected end of file")
-      else: break
-    tt.append(t)
-  return SequenceTemplate(tt)
-
-@direct(r'repeat')
-def dir_repeat(ps, m):
-  return RepeatTemplate(parse_templseq(ps, True))
-
-@direct(r'end')
-def dir_end(ps, m):
-  return END
-
-def compile_template(file, text):
-  ps = ParseState(file, text)
-  t = parse_templseq(ps, False)
-  return t
-
-###--------------------------------------------------------------------------
-### Main code.
-
-op = OP.OptionParser(
-  description = 'Generates files by filling in simple templates',
-  usage = 'usage: %prog [-gl] FILE [COL,...=VAL,... ... | @FILE:COL,...] ...',
-  version = 'Catacomb version @VERSION@')
-for short, long, kw in [
-  ('-l', '--list', dict(
-      action = 'store_const', const = 'list', dest = 'mode',
-      help = 'list filenames generated')),
-  ('-g', '--generate', dict(
-      action = 'store', metavar = 'PATH', dest = 'input',
-      help = 'generate output (default)'))]:
-  op.add_option(short, long, **kw)
-op.set_defaults(mode = 'gen')
-opts, args = op.parse_args()
-
-if len(args) < 1: op.error('missing FILE')
-filepat = args[0]
-for rel in args[1:]: read_thing(rel)
-filetempl = compile_template('<output>', filepat)
-
-def filenames(filetempl):
-  cs = CursorSet()
-  rr = filetempl.relations()
-  for r in rr:
-    if not len(r): return
-  cs.push(rr)
-  while True:
-    out = StringIO()
-    filetempl.subst(out, cs)
-    yield out.getvalue(), cs
-    if not cs.step(): break
-  cs.pop()
-
-if opts.mode == 'list':
-  for file, cs in filenames(filetempl): print file
-elif opts.mode == 'gen':
-  with open(opts.input) as f:
-    templ = RepeatTemplate(compile_template(opts.input, f.read()))
-  for file, cs in filenames(filetempl):
-    new = file + '.new'
-    with open(new, 'w') as out:
-      templ.subst(out, cs)
-    OS.rename(new, file)
-else:
-  raise Exception, 'What am I doing here?'
-
-###----- That's all, folks --------------------------------------------------