<filename>jekkish/jekkish.py
import sys
from os.path import expanduser
from os.path import splitext
from os.path import split
from os.path import exists
from os.path import realpath
from os.path import dirname
from os.path import join
from os import remove, stat
import errno
from subprocess import check_call
import yaml
import argparse
from time import sleep

from jinja2 import Environment
from jekkish.texhelpers import escape_tex, TeXLoader

__version__ = "0.3.2"


class Jekkish():

    def __init__(self, target_file, job_name=False):
        self.target_file = target_file
        fullpath, ext = splitext(self.target_file.name)
        path, filename = split(fullpath)
        self.temp_file = filename + '._' + ext[1:]
        self.job_name = job_name if job_name else filename
        self.variables = self.load_variables()
        self.home = expanduser("~")
        self.template_dir = self.home + '/.jekkish'
        if 'xelatex' in self.variables:
            self.engine = 'xelatex'
            self.default_template = 'default-xe'
        else:
            self.engine = 'pdflatex'
            self.default_template = 'default'

        self.command = '{} --jobname={} {}'.format(
            self.engine,
            self.job_name,
            self.temp_file)

    def load_variables(self, division_string="---\n"):
        """ Converts the file to YAML and returns the parsed data.

        Ignores any content above the YAML header (start_yaml),
        Loads everything after the YAML as part of the 'content' variable """

        start_yaml = False
        end_yaml = False
        variables = ""
        content = "content: >"

        for line in self.target_file:
            if str(line) == division_string:
                if start_yaml:
                    end_yaml = True
                else:
                    start_yaml = True
            else:
                if start_yaml:
                    if not end_yaml:
                        variables += line
                    else:
                        if line == "\n":
                            content += "  {}".format(line)
                        else:
                            content += "  {}\n".format(line)

        variables += content
        return yaml.load(variables)

    def make_file(self):

        texenv = Environment(loader=TeXLoader(self.home))
        texenv.block_start_string = '((*'
        texenv.block_end_string = '*))'
        texenv.variable_start_string = '((('
        texenv.variable_end_string = ')))'
        texenv.comment_start_string = '((='
        texenv.comment_end_string = '=))'
        texenv.filters['escape_tex'] = escape_tex

        if "template" not in self.variables:
            self.variables["template"] = self.default_template

        template_file = self.template_dir + \
            '/' + self.variables["template"] + '.tex'

        if not exists(template_file):
            template_file = join(
                dirname(realpath(__file__)),
                self.variables["template"] + '.tex')

        template = texenv.get_template(template_file)

        f = open(self.temp_file, "w")
        f.write(template.render(self.variables))
        f.close()

        print("Temporary LaTeX file created ({})\n---".format(self.temp_file))

    def make_pdf(self, clean=True):
        """ Calls pdftex and (optionally) removes temporary files """

        print("Generating PDF\n---")

        check_call(self.command, shell=True)

        if clean:
            for ext in ['aux', 'log', 'out', 'ent']:
                try:
                    remove(self.job_name + '.' + ext)
                except (OSError, IOError) as e:
                    # Use FileNotFoundError when python 2 is dropped
                    if e.errno != errno.ENOENT:
                        raise

    def render(self):
        self.make_file()
        self.make_pdf()

    def watch(self):
        print("---\nJekkish running in watch mode\n")
        print("---\nPerforming initial rendering\n---")

        last_time = False
        while True:
            if last_time != stat(self.target_file.name).st_mtime:
                last_time = stat(self.target_file.name).st_mtime
                sleep(0.1)
                self.target_file = open(self.target_file.name, 'r')
                self.variables = self.load_variables()
                self.make_file()
                sleep(0.1)
                self.render()
                print("---\nWatching {}\n---".format(self.target_file.name))


def main():
    parser = argparse.ArgumentParser(
        prog="Jekkish",
        description="A template-based pdftex CLI frontend inspired by Jekyll"
    )
    parser.add_argument(
        'filename',
        type=argparse.FileType('r'),
        default=sys.stdin,
        help='The file to process'
    )
    parser.add_argument(
        'jobname',
        nargs="?",
        default=False,
        help='Job name for pdftex output'
    )
    parser.add_argument(
        '--watch',
        action='store_const',
        const='watch',
        help='Watch <filename> for changes'
    )
    parser.add_argument(
        '--xelatex',
        action='store_const',
        const=True,
        help='Use xeLaTeX for rendering'
    )
    parser.add_argument(
        '--version',
        action='version',
        version='%(prog)s {}'.format(__version__)
    )
    args = parser.parse_args()
    new_file = Jekkish(args.filename, args.jobname)

    if args.watch:
        new_file.watch()
    else:
        new_file.render()


if __name__ == "__main__":
    main()

<filename>jekkish/jekkish.py
import sys
from os.path import expanduser
from os.path import splitext
from os.path import split
from os.path import exists
from os.path import realpath
from os.path import dirname
from os.path import join
from os import remove, stat
import errno
from subprocess import check_call
import yaml
import argparse
from time import sleep

from jinja2 import Environment
from jekkish.texhelpers import escape_tex, TeXLoader

__version__ = "0.3.2"


class Jekkish():

    def __init__(self, target_file, job_name=False):
        self.target_file = target_file
        fullpath, ext = splitext(self.target_file.name)
        path, filename = split(fullpath)
        self.temp_file = filename + '._' + ext[1:]
        self.job_name = job_name if job_name else filename
        self.variables = self.load_variables()
        self.home = expanduser("~")
        self.template_dir = self.home + '/.jekkish'
        if 'xelatex' in self.variables:
            self.engine = 'xelatex'
            self.default_template = 'default-xe'
        else:
            self.engine = 'pdflatex'
            self.default_template = 'default'

        self.command = '{} --jobname={} {}'.format(
            self.engine,
            self.job_name,
            self.temp_file)

    def load_variables(self, division_string="---\n"):
        """ Converts the file to YAML and returns the parsed data.

        Ignores any content above the YAML header (start_yaml),
        Loads everything after the YAML as part of the 'content' variable """

        start_yaml = False
        end_yaml = False
        variables = ""
        content = "content: >"

        for line in self.target_file:
            if str(line) == division_string:
                if start_yaml:
                    end_yaml = True
                else:
                    start_yaml = True
            else:
                if start_yaml:
                    if not end_yaml:
                        variables += line
                    else:
                        if line == "\n":
                            content += "  {}".format(line)
                        else:
                            content += "  {}\n".format(line)

        variables += content
        return yaml.load(variables)

    def make_file(self):

        texenv = Environment(loader=TeXLoader(self.home))
        texenv.block_start_string = '((*'
        texenv.block_end_string = '*))'
        texenv.variable_start_string = '((('
        texenv.variable_end_string = ')))'
        texenv.comment_start_string = '((='
        texenv.comment_end_string = '=))'
        texenv.filters['escape_tex'] = escape_tex

        if "template" not in self.variables:
            self.variables["template"] = self.default_template

        template_file = self.template_dir + \
            '/' + self.variables["template"] + '.tex'

        if not exists(template_file):
            template_file = join(
                dirname(realpath(__file__)),
                self.variables["template"] + '.tex')

        template = texenv.get_template(template_file)

        f = open(self.temp_file, "w")
        f.write(template.render(self.variables))
        f.close()

        print("Temporary LaTeX file created ({})\n---".format(self.temp_file))

    def make_pdf(self, clean=True):
        """ Calls pdftex and (optionally) removes temporary files """

        print("Generating PDF\n---")

        check_call(self.command, shell=True)

        if clean:
            for ext in ['aux', 'log', 'out', 'ent']:
                try:
                    remove(self.job_name + '.' + ext)
                except (OSError, IOError) as e:
                    # Use FileNotFoundError when python 2 is dropped
                    if e.errno != errno.ENOENT:
                        raise

    def render(self):
        self.make_file()
        self.make_pdf()

    def watch(self):
        print("---\nJekkish running in watch mode\n")
        print("---\nPerforming initial rendering\n---")

        last_time = False
        while True:
            if last_time != stat(self.target_file.name).st_mtime:
                last_time = stat(self.target_file.name).st_mtime
                sleep(0.1)
                self.target_file = open(self.target_file.name, 'r')
                self.variables = self.load_variables()
                self.make_file()
                sleep(0.1)
                self.render()
                print("---\nWatching {}\n---".format(self.target_file.name))


def main():
    parser = argparse.ArgumentParser(
        prog="Jekkish",
        description="A template-based pdftex CLI frontend inspired by Jekyll"
    )
    parser.add_argument(
        'filename',
        type=argparse.FileType('r'),
        default=sys.stdin,
        help='The file to process'
    )
    parser.add_argument(
        'jobname',
        nargs="?",
        default=False,
        help='Job name for pdftex output'
    )
    parser.add_argument(
        '--watch',
        action='store_const',
        const='watch',
        help='Watch <filename> for changes'
    )
    parser.add_argument(
        '--xelatex',
        action='store_const',
        const=True,
        help='Use xeLaTeX for rendering'
    )
    parser.add_argument(
        '--version',
        action='version',
        version='%(prog)s {}'.format(__version__)
    )
    args = parser.parse_args()
    new_file = Jekkish(args.filename, args.jobname)

    if args.watch:
        new_file.watch()
    else:
        new_file.render()


if __name__ == "__main__":
    main()

Converts the file to YAML and returns the parsed data.          Ignores any content above the YAML header (start_yaml),         Loads everything after the YAML as part of the 'content' variable Calls pdftex and (optionally) removes temporary files # Use FileNotFoundError when python 2 is dropped