#!/usr/bin/env python
#
# Cut source files into predictably-named snippet files that can be xi:included
# in XML and so forth.
#
# By Eron Hennessey
#
# The rules:
#
# * All snips begin with a prefix. This can be anywhere on the line, but
# anything after the prefix is interpreted as a snippet.
#
# * After the snippet there is some whitespace.
#
# * The whitespace is followed by a unique name that identifies the snippet.
#
#   This name cannot contain any whitespace, and must be a valid filename, as
#   well, since the snippet files will be named thusly:
#
#      orig_file_basename-snippet_id.orig_extension
#
# * All snips end with a postfix. The postfix can be the same as the prefix,
#   but many people find it easy to pick out visually distinct endings and
#   beginnings.
#
# * Different snips may not overlap.
#

import os
import sys

source_dir = 'src'
snip_dir = 'snips'
unsnip_dir = 'unsnips'
snip_prefix = "~~|"
snip_postfix = "|~~"
files = []
USAGE = """snippetize - cut a file into snippets (or remove snippet markers)

Usage:
------

    snippetize [-u] [file1] [file2] ...

* If *no arguments* are given, then all of the files in the ``{source_dir}``
  dir will be processed.

* If a *directory* is provided as input, then all files in the given directory
  will be processed.

* All snipped files will be written to the ``{snip_dir}`` directory.

* If -u is specified, then the files will be "unsnipped"--all of the snip
  markers will be removed from the file and the resulting file will be saved in
  the ``{unsnip_dir}`` dir.

Making snippets:
----------------

1. To make a snippet in a source file, simply delimit the beginning of your
   snippet like this::

      {snip_prefix} snippet_name

   The *snippet_name* is a unique name within the file that you use to refer
   to the snippet. The resulting snippet file will be named like this::

     file_name-snippet_name.file_ext

2. End your snip with the end-snip delimiter::

      {snip_postfix}

   Any lines between the delimiters will be copied into the resulting snippet
   file.

Begin-snip and end-snip delimiters are usually placed within comments in the
source files. For example, in a C++ or Java source file, you might have::

    // {snip_prefix} first_snip
    ... some code here ...
    // {snip_postfix}

In Python, Ruby, or in a bash script, you would have::

    # {snip_prefix} first_snip
    ... some code here ...
    # {snip_postfix}
""".format(source_dir=source_dir, snip_dir=snip_dir, unsnip_dir=unsnip_dir,
        snip_prefix=snip_prefix, snip_postfix=snip_postfix)

#
# Functions used by the script
#

def unsnip_file(filename):
    """Write the file to the destination without any snip tags"""

    f = open(filename)
    of = open('%s/%s' % (unsnip_dir, filename), 'w+')

    for line in f.readlines():
        if (snip_prefix in line) or (snip_postfix in line):
            continue
        else:
            of.write(line)


def snip_file(filename):
    """Extract any snips from the given file into their own snip files."""

    cur_tag = None
    f = open(filename)
    of = None
    subtract_leading_space = -1;
    for line in f.readlines():
        if cur_tag is None:
            # Not in a snip... yet.
            x = line.find(snip_prefix)
            if x > -1:
                x += len(snip_prefix)
                cur_tag = line[x:].strip()
                (f_base, f_ext) = os.path.splitext(os.path.basename(filename))
                of_path = "%s/%s-%s%s" % (snip_dir, f_base, cur_tag, f_ext)
                print "   -> writing: %s" % of_path
                of = open(of_path, 'w+')
                subtract_leading_space = -1;
        else:
            # In a snip... but are we finished?
            x = line.find(snip_postfix)
            if x > -1:
                # Yep, we're finished.
                of.close()
                cur_tag = None
            else:
                # Nope; this line must be part of the snippet.
                if subtract_leading_space == -1:
                    # detect how much leading space to remove from the snippet
                    # based on the first line.
                    leading = line.lstrip()
                    subtract_leading_space = len(line) - len(leading)
                of.write(line[subtract_leading_space:])
    # remember to close the file...
    f.close()


def process_file_list(process, files):
    """Process (either snip or unsnip) a list of files."""
    for filename in files:
        filename = os.path.abspath(filename)
        if os.path.isdir(filename) is True:
            # if a directory, operate on all files within it.
            file_list = [os.path.abspath("%s/%s" % (filename, x)) for x in os.listdir(filename)]
            process_file_list(process, file_list)
        else:
            print "** processing: %s" % filename
            process(filename)

#
# The script starts here
#

# default to snip.
process = snip_file

# check command-line args.
for arg_num in range(1, len(sys.argv)):
    arg = sys.argv[arg_num]
    if arg.startswith('-'):
        if (arg == '-h') or (arg == '--help') or (arg == '-?'):
            print USAGE
            sys.exit()
        elif arg == '-u' or (arg == '--unsnip'):
            process = unsnip_file
        else:
            print "Unrecognized command: '%s'." % arg
            print "Use 'snippetize -h' for help."
            sys.exit()
    else:
        files = sys.argv[arg_num:]
        break

# if no files were given, then just operate on all files in the source
# directory.
if len(files) == 0:
    files.append(source_dir)

# make sure output dirs are there!
if process == unsnip_file:
    if not os.path.exists(unsnip_dir):
        os.makedirs(unsnip_dir)
else:
    if not os.path.exists(snip_dir):
        os.makedirs(snip_dir)

process_file_list(process, files)

