#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""A program (and Python module) to generate a tree of symlinks to LV2
extension bundles, where the path of the symlink corresponds to the URI of
the extension.  This allows including extension headers in code without using
the bundle name.  Including extension headers in this way is much better,
since there is no dependency on the (meaningless and non-persistent) bundle
name in the code using the header.

For example, after running lv2config (and setting the compiler include
path appropriately), LV2 headers could be included like so:

#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
#include "lv2/lv2plug.in/ns/ext/event/event.h"
#include "lv2/example.org/foo/foo.h"

Where the initial "lv2" is arbitrary; in this case lv2config's output
directory was "lv2", and that directory's parent was added to the compiler
include search path.  It is a good idea to use such a prefix directory so
domain names do not conflict with anything else in the include path.
"""

__authors__ = 'David Robillard'
__license   = 'GNU GPL v3 or later <http://www.gnu.org/licenses/gpl.html>'
__contact__ = 'devel@lists.lv2plug.in'
__date__    = '2010-10-05'

import errno
import glob
import os
import stat
import sys

redland = True

try:
    import RDF # Attempt to import Redland
except:
    try:
        import rdflib # Attempt to import RDFLib
        redland = False
    except:
        print >> sys.stderr, """Failed to import `RDF' (Redland) or `rdflib'.
(Please install either package, likely `python-librdf' or `python-rdflib')"""
        sys.exit(1)

def rdf_namespace(uri):
    "Create a new RDF namespace"
    if redland:
        return RDF.NS(uri)
    else:
        return rdflib.Namespace(uri)

def rdf_load(uri):
    "Load an RDF model"
    if redland:
        model = RDF.Model()
        parser = RDF.Parser(name="turtle")
        parser.parse_into_model(model, uri)
    else:
        model = rdflib.ConjunctiveGraph()
        model.parse(uri, format="n3")
    return model

def rdf_find_type(model, rdf_type):
    "Return a list of the URIs of all resources in model with a given type"
    if redland:
        results = model.find_statements(RDF.Statement(None, rdf.type, rdf_type))
        ret = []
        for r in results:
            ret.append(str(r.subject.uri))
        return ret
    else:
        results = model.triples([None, rdf.type, rdf_type])
        ret = []
        for r in results:
            ret.append(r[0])
        return ret

rdf = rdf_namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
lv2 = rdf_namespace('http://lv2plug.in/ns/lv2core#')

def lv2_path():
    "Return the LV2 search path (LV2_PATH in the environment, or a default)."
    if 'LV2_PATH' in os.environ:
        return os.environ['LV2_PATH']
    else:
        ret = '/usr/lib/lv2' + os.pathsep + '/usr/local/lib/lv2'
        print 'LV2_PATH unset, using default ' + ret
        return ret

def __mkdir_p(path):
    "Equivalent of UNIX mkdir -p"
    try:
        os.makedirs(path)
    except OSError, e:
        if e.errno == errno.EEXIST:
            pass
        else:
            raise

def build_tree(search_path, outdir):
    """Build a directory tree under outdir containing symlinks to all LV2
    extensions found in search_path, such that the symlink paths correspond to
    the extension URIs."""
    if os.path.basename(outdir) != 'lv2':
        print >> sys.stderr, "lv2config: output dir must be named `lv2'"
        sys.exit(1)
        
    if os.access(outdir, os.F_OK) and not os.access(outdir, os.W_OK):
        print >> sys.stderr, "lv2config: cannot build `%s': Permission denied" % outdir
        sys.exit(1)

    for dir in search_path.split(os.pathsep):
        if not os.access(dir, os.F_OK):
            continue

        print 'Building includes in %s for %s/*.lv2' % (outdir, dir)
        for bundle in glob.glob(os.path.join(dir, '*.lv2')):
            # Load manifest into model
            manifest = rdf_load('file://' + os.path.join(bundle, 'manifest.ttl'))

            # Query extension URI
            specs = rdf_find_type(manifest, lv2.Specification)
            for ext_uri in specs:
                ext_path   = os.path.normpath(ext_uri[ext_uri.find(':') + 1:].lstrip('/'))
                ext_dir    = os.path.join(outdir, ext_path)
    
                # Make parent directories
                __mkdir_p(os.path.dirname(ext_dir))
    
                # Remove existing symlink if necessary
                if os.access(ext_dir, os.F_OK):
                    mode = os.lstat(ext_dir)[stat.ST_MODE]
                    if stat.S_ISLNK(mode):
                        os.remove(ext_dir)
                    else:
                        raise Exception(ext_dir + " exists and is not a link")
    
                # Make symlink to bundle directory
                os.symlink(bundle, ext_dir)
            
def __usage():
    script = os.path.basename(sys.argv[0])
    print """Usage: %s
    Build the default system lv2 include directories,
    /usr/include/lv2 and /usr/local/include/lv2

Usage: %s INCLUDEDIR
    Build an lv2 include directory tree at INCLUDEDIR
    for all extensions found in $LV2_PATH.

Usage: %s BUNDLESDIR INCLUDEDIR
    Build an lv2 include directory tree at INCLUDEDIR
    for all extensions found in bundles under BUNDLESDIR.
""" % (script, script, script)

if __name__ == "__main__":
    args = sys.argv[1:]

    if len(args) == 0:
        build_tree('/usr/local/lib/lv2',          '/usr/local/include/lv2')
        build_tree('/usr/lib/lv2',                '/usr/include/lv2')
        build_tree('/Library/Audio/Plug-Ins/LV2', '/Developer/Headers/lv2')

    elif '--help' in args or '-h' in args:
        __usage()

    elif len(args) == 1:
        build_tree(lv2_path(), args[0])

    elif len(args) == 2:
        build_tree(args[0], args[1])
        
    else:
        __usage()
        sys.exit(1)