Source

scraps / python / includeguard.py

Full commit
#!/usr/bin/env python2.6
"""Generate include guards for C/C++ headers.

Note:
"Reserved" in the context of the --reserved option means "reserved to the implementation of the standard library" and is specified in the relevant language standard from ISO.  Thus --reserved should only be used when writing an implementation of the standard libraries.

To Do:
- look at different UUID variations, make sure using the most appropriate
- smarter --replace?
  - currently only checks first two lines and last line, if they are ifndef/define/endif directives, assumes they are include guards.  this might not be correct.
"""

USAGE = "%prog [OPTIONS] [DESTS...]"
DESCRIPTION = "Generate include guards for C/C++ headers.  Write to each DEST, if supplied, or stdout.  DEST is wrapped if existing (by default), otherwise it is created.  A DEST of '-' means write to stdout.  If stdin is not a TTY, it is used as header contents.  If not --reserved (the default), PREFIX must not start with an underscore nor contain adjacent underscores.  Otherwise if PREFIX would not generate a reserved identifier, two underscores are prepended."

import optparse
import os
import re
import sys
import uuid


def main(args):
  optparser = optparse.OptionParser(USAGE, description=DESCRIPTION)
  optparser.add_option("--prefix", default="INCLUDE_GUARD_",
    help="include guard prefix [default: %default]")
  optparser.add_option("--stdin", action="store_true", default=False,
    help="read header contents from stdin, even if a TTY")
  optparser.add_option("--reserved", action="store_true", default=False,
    help="require reserved identifiers")
  optparser.add_option("--no-existing", dest="existing", action="store_false", default=True,
    help="disable working on existing files")
  optparser.add_option("--replace", action="store_true", default=False,
    help="replace existing include guards, if any")

  opts, args = optparser.parse_args(args)

  if not opts.prefix:
    return "prefix must not be empty"

  if not opts.reserved:
    if opts.prefix.startswith("_"):
      return "prefixes with a leading underscore are reserved"
    if "__" in opts.prefix:
      return "prefixes with adjacent underscores are reserved"
  else: # implementation-reserved identifiers
    if not (opts.prefix.startswith("_") or "__" in opts.prefix):
      opts.prefix = "__" + opts.prefix

  if not re.match("^[A-Za-z_][A-Za-z_0-9]*$", opts.prefix):
    return "invalid identifier prefix: %r" % opts.prefix

  if opts.stdin or not os.isatty(sys.stdin.fileno()):
    contents = sys.stdin.read()
    if contents[-1:] != "\n":
      contents += "\n"
  else:
    contents = "\n"

  if not args:
    args = ["-"]

  seen_names = set()
  for filename in args:
    if filename in seen_names:
      continue

    write_contents = contents

    if filename == "-":
      out = sys.stdout
    else:
      seen_names.add(filename)
      try:
        outfd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o666)
        # fails if the file already exists
      except OSError as e:
        if e.errno != 17 or not opts.existing:  # file exists
          return str(e)
        if contents != "\n":  #TODO: handle better
          return "contents supplied and " + str(e)
        write_contents = open(filename).read()
        if write_contents[-1:] != "\n":
          write_contents += "\n"
        out = open(filename, "w")
      else:
        out = os.fdopen(outfd, "w")

    guard = "%s%s" % (opts.prefix, uuid.uuid4().hex.upper())
    print >>out, "#ifndef %s\n#define %s\n" % (guard, guard)
    if opts.replace:
      m = re.match(r"^#ifndef ([^ ]+)\n#define \1\n(?P<body>(?:.*\n)*)#endif\n?$", write_contents)
      if m:
        write_contents = m.group("body").strip("\n") + "\n"
    out.write(write_contents)
    print >>out, "\n#endif"


if __name__ == "__main__":
  sys.exit(main(sys.argv[1:]))