#!/usr/bin/env python3

"""
Convert YAML to tmTheme format.

Reads a theme in YAML format and outputs tmTheme plist format used in
TextMate and Sublime Text.

Input files are concatenated.

An optional dictionary file (in YAML format), can be used to translate
strings.

Note: Uses plistlib interface introduced in Python 3.4.
"""

import argparse
import fileinput
import plistlib
import re
import yaml

userdict = {}

def dictrepl(matchobj):
    if matchobj.group(1) == '$':
        return '$'
    else:
        return userdict[matchobj.group(1)]

def str_constructor(loader, data):
    s = loader.construct_scalar(data)
    s = re.sub('\$(\$|\w+)', dictrepl, s)
    return s

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Convert YAML to tmTheme.')
    parser.add_argument('-d', '--dict', type=argparse.FileType('r'),
                        help='YAML dictionary file')
    parser.add_argument('infile', nargs='+', help='YAML theme file')
    parser.add_argument('outfile', type=argparse.FileType('wb'),
                        help='tmTheme theme file')
    args = parser.parse_args()

    # if user supplied a dictionary, read it and add our own constructor
    # for str objects that performs lookup
    if args.dict:
        userdict = yaml.safe_load(args.dict)
        yaml.add_constructor(u'tag:yaml.org,2002:str', str_constructor,
                             Loader=yaml.SafeLoader)

    # read YAML and write tmTheme plist
    with fileinput.input(args.infile) as f:
        theme = yaml.safe_load(''.join(f))
        plistlib.dump(theme, args.outfile)

@@ -348,25 +348,27 @@
 %0Aimport 
-fileinput
+collections
 %0Aimport 
@@ -386,16 +386,17 @@
 port re%0A
+%0A
 import y
@@ -400,16 +400,1257 @@
 t yaml%0A%0A
+%0AFileRange = collections.namedtuple('FileRange', 'lo hi name')%0A%0A%0Aclass MultiFile:%0A    %22%22%22%0A    Array of lines from multiple files.%0A    %22%22%22%0A%0A    def __init__(self, filenames=None, mode='r'):%0A        self.lines = %5B%5D%0A        self._files = %5B%5D%0A        if filenames is not None:%0A            self.add_files(filenames, mode)%0A%0A    def __iter__(self):%0A        return iter(self.lines)%0A%0A    def add_files(self, filenames, mode='r'):%0A        %22%22%22%0A        Add lines from files to end of MultiFile.%0A%0A        :param filenames: List of filenames%0A        %22%22%22%0A        base = len(self.lines)%0A        for name in filenames:%0A            with open(name, mode) as f:%0A                self.lines.extend(f.readlines())%0A                self._files.append(FileRange(base, len(self.lines), name))%0A                base = len(self.lines)%0A%0A    def lookup_line(self, n):%0A        %22%22%22%0A        Look up filename and line number from line index.%0A%0A        :param n: Absolute index of line (starting at 0)%0A        :rtype: (str, int) or None%0A        :return: Filename and line number (starting at 1)%0A        %22%22%22%0A        for f in self._files:%0A            if n %3E= f.lo and n %3C f.hi:%0A                return f.name, n - f.lo + 1%0A        else:%0A            return None%0A%0A%0A_mf = MultiFile()%0A%0A_
 userdict
@@ -1656,16 +1656,17 @@
 t = %7B%7D%0A%0A
+%0A
 def dict
@@ -1758,16 +1758,17 @@
  return 
+_
 userdict
@@ -1788,16 +1788,17 @@
 up(1)%5D%0A%0A
+%0A
 def str_
@@ -1862,16 +1862,29 @@
 r(data)%0A
+    try:%0A    
     s = 
@@ -1917,16 +1917,167 @@
 epl, s)%0A
+    except KeyError as e:%0A        name, line = _mf.lookup_line(data.start_mark.line)%0A        print('KeyError in %22%7B%7D%22, line %7B%7D:'.format(name, line), e)%0A
     retu
@@ -2082,16 +2082,17 @@
 turn s%0A%0A
+%0A
 if __nam
@@ -2668,16 +2668,17 @@
         
+_
 userdict
@@ -2852,118 +2852,431 @@
 AML 
-and write tmTheme plist%0A    with fileinput.input(args.infile) as f:%0A        theme = yaml.safe_load(''.join(f))
+input files into MultiFile%0A    _mf.add_files(args.infile)%0A%0A    # load YAML and write tmTheme plist%0A    try:%0A        theme = yaml.safe_load(''.join(_mf.lines))%0A    except yaml.YAMLError as e:%0A        if hasattr(e, 'problem_mark'):%0A            name, line = _mf.lookup_line(e.problem_mark.line)%0A            print('YAML error in %22%7B%7D%22, line %7B%7D:'.format(name, line), e)%0A        else:%0A            print('YAML error:', e)%0A    else:
 %0A   

Update yamltotm.py