Exemple #1
0
def init(project, theme):
    """Initialize a site project."""

    if not opx(project):
        os.makedirs(project)

    if os.listdir(project):
        print("abort  : project dir %s is not empty" % project)
        sys.exit(1)

    dir_in = opj(project, "input")
    dir_out = opj(project, "output")

    os.mkdir(dir_in)
    os.mkdir(dir_out)

    for fname, content in EXAMPLE_FILES.items():
        print('info: create example %r' % fname)
        with open(opj(project, fname), 'w') as fp:
            fp.write(content)

    if theme != 'minimal':
        shutil.copy(opj(THEME_DIR, theme, 'page.html'), project)
        for fname in glob.glob(opj(THEME_DIR, theme, '*')):
            print('info: copy theme data %r' % fname)
            if os.path.basename(fname) == 'page.html':
                continue
            if os.path.isdir(fname):
                shutil.copytree(fname, opj(dir_in, os.path.basename(fname)))
            else:
                shutil.copy(fname, dir_in)

    print("success: initialized project")
Exemple #2
0
def init(project, theme):
    """Initialize a site project."""

    if not opx(project):
        os.makedirs(project)

    if os.listdir(project):
        print("abort  : project dir %s is not empty" % project)
        sys.exit(1)

    dir_in = opj(project, "input")
    dir_out = opj(project, "output")

    os.mkdir(dir_in)
    os.mkdir(dir_out)

    for fname, content in EXAMPLE_FILES.items():
        print('info: create example %r' % fname)
        with open(opj(project, fname), 'w') as fp:
            fp.write(content)

    if theme != 'minimal':
        shutil.copy(opj(THEME_DIR, theme, 'page.html'), project)
        for fname in glob.glob(opj(THEME_DIR, theme, '*')):
            print('info: copy theme data %r' % fname)
            if os.path.basename(fname) == 'page.html':
                continue
            if os.path.isdir(fname):
                shutil.copytree(fname, opj(dir_in, os.path.basename(fname)))
            else:
                shutil.copy(fname, dir_in)

    print("success: initialized project")
Exemple #3
0
def prepare_output(macmod):
    if hasattr(macmod, "prepare_output"):
        return macmod.prepare_output()

    print "Using built-in prepare."

    project = macmod.project
    dir_in = macmod.input
    dir_out = macmod.output

    # prepare output directory
    project_path = os.path.realpath(project)
    for fod in glob.glob(opj(dir_out, "*")):
        # don't remove the sources
        """
        real_fod = os.path.realpath(fod)
        if real_fod == project_path or real_fod.startswith(project_path):
            print real_fod, project_path
            continue
        """
        if os.path.isdir(fod):
            print "-dir", fod
            shutil.rmtree(fod)
        else:
            os.remove(fod)
    if not opx(dir_out):
        os.mkdir(dir_out)
Exemple #4
0
def prepare_output(macmod):
    if hasattr(macmod, "prepare_output"):
        return macmod.prepare_output()

    print "Using built-in prepare."

    project = macmod.project
    dir_in = macmod.input
    dir_out = macmod.output

    # prepare output directory
    project_path = os.path.realpath(project)
    for fod in glob.glob(opj(dir_out, "*")):
        # don't remove the sources
        """
        real_fod = os.path.realpath(fod)
        if real_fod == project_path or real_fod.startswith(project_path):
            print real_fod, project_path
            continue
        """
        if os.path.isdir(fod):
            print "-dir", fod
            shutil.rmtree(fod)
        else:
            os.remove(fod)
    if not opx(dir_out):
        os.mkdir(dir_out)
Exemple #5
0
def init(project='carlae_page',
         theme=None,
         output='docs',
         default_readme=False):
    """Initialize a site project."""
    HERE = os.path.dirname(os.path.realpath(__file__))
    THEME_DIR = opj(HERE, 'themes')

    cwd = os.getcwd()

    check_mkdir(opj(cwd, project))

    dir_out = opj(cwd, output)
    check_mkdir(dir_out)

    theme_dir = opj(project, "theme")
    if not (opx(theme_dir)):
        if theme is None:
            theme = 'skeleton'
        elif not (theme in os.listdir(THEME_DIR)):
            print("the selected theme not in theme dir, will use skeleton")
            theme = 'skeleton'
        shutil.copytree(opj(THEME_DIR, theme), theme_dir)
    THEME_DIR = theme_dir

    static_dir = os.listdir(theme_dir)
    static_dir = [d for d in static_dir if not (d[0] == '_')]
    for d in static_dir:
        if not (opx(opj(dir_out, d))):
            shutil.copytree(opj(theme_dir, d), opj(dir_out, d))

    if default_readme:
        for fname, content in EXAMPLE_FILES.items():
            if not (opx(opj(cwd, fname))):
                print('info: create example %r' % fname)
                with open(opj(cwd, fname), 'w') as fp:
                    fp.write(content)
            else:
                print(
                    'README.md file exists, skip making default readme.md file'
                )

    print("success: initialized project")
Exemple #6
0
def init(project, opts):
    """Initialize a site project."""

    if not opx(project):
        os.makedirs(project)

    if os.listdir(project):
        print("error  : project dir %s is not empty, abort" % project)
        sys.exit(1)

    os.mkdir(opj(project, "input"))
    os.mkdir(opj(project, opts.output_dir))

    for fname, content in EXAMPLE_FILES.items():
        with open(opj(project, fname), 'w') as fp:
            fp.write(content)

    print("success: initialized project")
Exemple #7
0
def init(project):
    """Initialize a site project."""

    if not opx(project):
        os.makedirs(project)

    if os.listdir(project):
        print("error  : project dir %s is not empty, abort" % project)
        sys.exit(1)

    os.mkdir(opj(project, "input"))
    os.mkdir(opj(project, "output"))

    for fname, content in EXAMPLE_FILES.items():
        with open(opj(project, fname), 'w') as fp:
            fp.write(content)

    print("success: initialized project")
Exemple #8
0
def build(project, opts):
    """Build a site project."""

    # -------------------------------------------------------------------------
    # utilities
    # -------------------------------------------------------------------------

    def abort_iex(page, itype, inline, exc):
        """Abort because of an exception in inlined Python code."""
        print("abort  : Python %s in %s failed" % (itype, page))
        print((" %s raising the exception " % itype).center(79, "-"))
        print(inline)
        print(" exception ".center(79, "-"))
        print(exc)
        sys.exit(1)

    # -------------------------------------------------------------------------
    # regex patterns and replacements
    # -------------------------------------------------------------------------

    regx_escp = re.compile(r'\\((?:(?:&lt;|<)!--|{)(?:{|%))')  # escaped code
    repl_escp = r'\1'
    regx_rurl = re.compile(r'(?<=(?:(?:\n| )src|href)=")([^#/&%].*?)(?=")')
    repl_rurl = lambda m: urlparse.urljoin(opts.base_url, m.group(1))

    regx_eval = re.compile(r'(?<!\\)(?:(?:<!--|{){)(.*?)(?:}(?:-->|}))', re.S)

    def repl_eval(m):
        """Replace a Python expression block by its evaluation."""

        expr = m.group(1)
        try:
            repl = eval(expr, macros.copy())
        except:
            abort_iex(page, "expression", expr, traceback.format_exc())
        else:
            if not isinstance(repl, basestring):  # e.g. numbers
                repl = unicode(repl)
            elif not isinstance(repl, unicode):
                repl = repl.decode("utf-8")
            return repl

    regx_exec = re.compile(r'(?<!\\)(?:(?:<!--|{)%)(.*?)(?:%(?:-->|}))', re.S)

    def repl_exec(m):
        """Replace a block of Python statements by their standard output."""

        stmt = m.group(1).replace("\r\n", "\n")

        # base indentation
        ind_lvl = len(re.findall(r'^(?: *\n)*( *)', stmt, re.MULTILINE)[0])
        ind_rex = re.compile(r'^ {0,%d}' % ind_lvl, re.MULTILINE)
        stmt = ind_rex.sub('', stmt)

        # execute
        sys.stdout = StringIO.StringIO()
        try:
            exec stmt in macros.copy()
        except:
            sys.stdout = sys.__stdout__
            abort_iex(page, "statements", stmt, traceback.format_exc())
        else:
            repl = sys.stdout.getvalue()[:-1]  # remove last line break
            sys.stdout = sys.__stdout__
            if not isinstance(repl, unicode):
                repl = repl.decode(opts.input_enc)
            return repl

    # -------------------------------------------------------------------------
    # preparations
    # -------------------------------------------------------------------------

    dir_in = opj(project, "input")
    dir_out = opj(project, "output")
    page_html = opj(project, "page.html")

    # check required files and folders
    for pelem in (page_html, dir_in, dir_out):
        if not opx(pelem):
            print(
                "abort  : %s does not exist, looks like project has not been "
                "initialized" % pelem)
            sys.exit(1)

    # prepare output directory
    if not opts.dry_run:
        for fod in glob.glob(opj(dir_out, "*")):
            if os.path.isdir(fod):
                shutil.rmtree(fod)
            else:
                os.remove(fod)
        if not opx(dir_out):
            os.mkdir(dir_out)

    # macro module
    fname = opj(opts.project, "macros.py")
    macros = imp.load_source("macros", fname).__dict__ if opx(fname) else {}

    macros["__encoding__"] = opts.output_enc
    macros["options"] = opts
    macros["project"] = project
    macros["input"] = dir_in
    macros["output"] = dir_out

    # "builtin" items for use in macros and templates
    macros["hx"] = hx
    macros["htmlspecialchars"] = hx  # legacy name of `htmlx` function
    macros["Page"] = Page

    # -------------------------------------------------------------------------
    # process input files
    # -------------------------------------------------------------------------

    Page._template = macros.get("page", {})
    Page._opts = opts
    Page._pstrip = dir_in
    pages = []
    custom_converter = macros.get('converter', {})

    for cwd, dirs, files in os.walk(dir_in.decode(opts.filename_enc)):
        cwd_site = cwd[len(dir_in):].lstrip(os.path.sep)
        if not opts.dry_run:
            for sdir in dirs[:]:
                if re.search(opts.ignore, opj(cwd_site, sdir)):
                    dirs.remove(sdir)
                else:
                    os.mkdir(opj(dir_out, cwd_site, sdir))
        for f in files:
            if re.search(opts.ignore, opj(cwd_site, f)):
                pass
            elif re.search(MKD_PATT, f):
                page = Page(opj(cwd, f))
                pages.append(page)
            else:
                # either use a custom converter or do a plain copy
                for patt, (func, ext) in custom_converter.items():
                    if re.search(patt, f):
                        f_src = opj(cwd, f)
                        f_dst = opj(dir_out, cwd_site, f)
                        f_dst = '%s.%s' % (os.path.splitext(f_dst)[0], ext)
                        print('info   : convert %s (%s)' %
                              (f_src, func.__name__))
                        if not opts.dry_run:
                            func(f_src, f_dst)
                        break
                else:
                    if not opts.dry_run:
                        src = opj(cwd, f)
                        try:
                            shutil.copy(src, opj(dir_out, cwd_site))
                        except OSError:
                            # some filesystems like FAT won't allow shutil.copy
                            shutil.copyfile(src, opj(dir_out, cwd_site, f))

    pages.sort(key=lambda p: int(p.get("sval", "0")))

    macros["pages"] = pages

    # -------------------------------------------------------------------------
    # run pre-convert hooks in macro module (named 'once' before)
    # -------------------------------------------------------------------------

    hooks = [a for a in macros if re.match(r'hook_preconvert_|once_', a)]
    for fn in sorted(hooks):
        macros[fn]()

    # -------------------------------------------------------------------------
    # convert pages (markdown to HTML)
    # -------------------------------------------------------------------------

    for page in pages:

        print("info   : convert %s" % page)

        # replace expressions and statements in page source
        macros["page"] = page
        out = regx_eval.sub(repl_eval, page.source)
        out = regx_exec.sub(repl_exec, out)

        # convert to HTML
        page.html = markdown.Markdown(extensions=opts.md_ext).convert(out)

    # -------------------------------------------------------------------------
    # run post-convert hooks in macro module
    # -------------------------------------------------------------------------

    hooks = [a for a in macros if a.startswith("hook_postconvert_")]
    for fn in sorted(hooks):
        macros[fn]()

    # -------------------------------------------------------------------------
    # render complete HTML pages
    # -------------------------------------------------------------------------

    with codecs.open(opj(project, "page.html"), 'r', opts.input_enc) as fp:
        default_template = fp.read()

    for page in pages:

        if 'template' in page:
            fname = opj(project, page['template'])
            with codecs.open(fname, 'r', opts.input_enc) as fp:
                template = fp.read()
        else:
            template = default_template

        print("info   : render %s" % page.url)

        # replace expressions and statements in page.html
        macros["page"] = page
        macros["__content__"] = page.html
        out = regx_eval.sub(repl_eval, template)
        out = regx_exec.sub(repl_exec, out)

        # un-escape escaped python code blocks
        out = regx_escp.sub(repl_escp, out)

        # make relative links absolute
        out = regx_rurl.sub(repl_rurl, out)

        # write HTML page
        fname = page.fname.replace(dir_in, dir_out)
        fname = re.sub(MKD_PATT, ".html", fname)
        if not opts.dry_run:
            with codecs.open(fname, 'w', opts.output_enc) as fp:
                fp.write(out)

    if opts.dry_run:
        print("success: built project (dry-run)")
    else:
        print("success: built project")
Exemple #9
0
def build(project, opts):
    """Build a site project."""

    # -------------------------------------------------------------------------
    # utilities
    # -------------------------------------------------------------------------

    def abort_iex(page, itype, inline, exc):
        """Abort because of an exception in inlined Python code."""
        print("error  : Python %s in %s failed" % (itype, page.fname))
        print((" %s raising the exception " % itype).center(79, "-"))
        print(inline)
        print(" exception ".center(79, "-"))
        print(exc)
        sys.exit(1)

    # -------------------------------------------------------------------------
    # regex patterns and replacements
    # -------------------------------------------------------------------------

    regx_escp = re.compile(r'\\((?:(?:&lt;|<)!--|{)(?:{|%))') # escaped code
    repl_escp = r'\1'
    regx_rurl = re.compile(r'(?<=(?:(?:\n| )src|href)=")([^#/&%].*?)(?=")')
    repl_rurl = lambda m: urlparse.urljoin(opts.base_url, m.group(1))

    regx_eval = re.compile(r'(?<!\\)(?:(?:<!--|{){)(.*?)(?:}(?:-->|}))', re.S)

    def repl_eval(m):
        """Replace a Python expression block by its evaluation."""

        expr = m.group(1)
        try:
            repl = eval(expr, macros.copy())
        except:
            abort_iex(page, "expression", expr, traceback.format_exc())
        else:
            if not isinstance(repl, basestring): # e.g. numbers
                repl = unicode(repl)
            elif not isinstance(repl, unicode):
                repl = repl.decode("utf-8")
            return repl

    regx_exec = re.compile(r'(?<!\\)(?:(?:<!--|{)%)(.*?)(?:%(?:-->|}))', re.S)

    def repl_exec(m):
        """Replace a block of Python statements by their standard output."""

        stmt = m.group(1).replace("\r\n", "\n")

        # base indentation
        ind_lvl = len(re.findall(r'^(?: *\n)*( *)', stmt, re.MULTILINE)[0])
        ind_rex = re.compile(r'^ {0,%d}' % ind_lvl, re.MULTILINE)
        stmt = ind_rex.sub('', stmt)

        # execute
        sys.stdout = StringIO.StringIO()
        try:
            exec stmt in macros.copy()
        except:
            sys.stdout = sys.__stdout__
            abort_iex(page, "statements", stmt, traceback.format_exc())
        else:
            repl = sys.stdout.getvalue()[:-1] # remove last line break
            sys.stdout = sys.__stdout__
            if not isinstance(repl, unicode):
                repl = repl.decode(opts.input_enc)
            return repl

    # -------------------------------------------------------------------------
    # preparations
    # -------------------------------------------------------------------------

    dir_in = opj(project, "input")
    dir_out = opj(project, opts.output_dir)
    page_html = opj(project, "page.html")

    # check required files and folders
    for pelem in (page_html, dir_in, dir_out):
        if not opx(pelem):
            print("error  : %s does not exist, looks like project has not been "
                  "initialized, abort" % pelem)
            sys.exit(1)

    # macro module
    class nomod: pass # dummy module, something we can set attributes on
    fname = opj(opts.project, "macros.py")
    macros = {"__encoding__": opts.output_enc}
    macmod = opx(fname) and imp.load_source("macros", fname) or nomod
    setattr(macmod, "options", opts)
    setattr(macmod, "project", project)
    setattr(macmod, "input", dir_in)
    setattr(macmod, "output", dir_out)
    for attr in dir(macmod):
        if not attr.startswith("_"):
            macros[attr] = getattr(macmod, attr)

    prepare_output(macmod)

    pages = []

    for _fn in dir(macmod):
        if _fn.startswith("init_"):
            getattr(macmod, _fn)(pages)

    # -------------------------------------------------------------------------
    # process input files
    # -------------------------------------------------------------------------

    page_global = macros.get("page", {})
    for cwd, dirs, files in os.walk(dir_in.decode(opts.filename_enc)):
        cwd_site = cwd[len(dir_in):].lstrip(os.path.sep)
        for sdir in dirs[:]:
            if re.search(opts.ignore, opj(cwd_site, sdir)):
                dirs.remove(sdir)
            else:
                tmp = opj(dir_out, cwd_site, sdir)
                if not os.path.exists(tmp):
                    os.mkdir(tmp)
        for f in files:
            if re.search(opts.ignore, opj(cwd_site, f)):
                pass
            elif re.search(MKD_PATT, f):
                page = Page(page_global, opj(cwd, f), dir_in, opts)
                pages.append(page)
            else:
                shutil.copy(opj(cwd, f), opj(dir_out, cwd_site))

    pages.sort(key=lambda p: int(p.get("sval", "0")))

    macros["pages"] = pages
    macmod.pages = pages

    # -------------------------------------------------------------------------
    # run pre-convert hooks in macro module (named 'once' before)
    # -------------------------------------------------------------------------

    hooks = [a for a in dir(macmod) if re.match(r'hook_preconvert_|once_', a)]
    for fn in sorted(hooks):
        getattr(macmod, fn)()

    # -------------------------------------------------------------------------
    # convert pages (markdown to HTML)
    # -------------------------------------------------------------------------

    for page in sorted(pages, key=lambda p: p["url"]):

        print("info   : convert %s" % page.fname)

        # replace expressions and statements in page source
        macmod.page = page
        macros["page"] = page
        out = regx_eval.sub(repl_eval, page.source)
        out = regx_exec.sub(repl_exec, out)

        # convert to HTML
        page.html = markdown.Markdown(extensions=opts.md_ext).convert(out)

    # -------------------------------------------------------------------------
    # run post-convert hooks in macro module
    # -------------------------------------------------------------------------

    hooks = [a for a in dir(macmod) if a.startswith("hook_postconvert_")]
    for fn in sorted(hooks):
        getattr(macmod, fn)()

    # -------------------------------------------------------------------------
    # render complete HTML pages
    # -------------------------------------------------------------------------

    with codecs.open(opj(project, "page.html"), 'r', opts.input_enc) as fp:
        skeleton = fp.read()

    hooks = [a for a in dir(macmod) if a.startswith("hook_html_")]

    for page in sorted(pages, key=lambda p: p["url"]):

        print("info   : render %s" % page.url)

        # replace expressions and statements in page.html
        macmod.page = page
        macros["page"] = page
        macros["__content__"] = page.html
        out = regx_eval.sub(repl_eval, skeleton)
        out = regx_exec.sub(repl_exec, out)

        # un-escape escaped python code blocks
        out = regx_escp.sub(repl_escp, out)

        # make relative links absolute
        out = regx_rurl.sub(repl_rurl, out)

        # apply final hooks
        for fn in hooks:
            out = getattr(macmod, fn)(out, page)

        # write HTML page
        fname = page.fname.replace(dir_in, dir_out)
        fname = re.sub(MKD_PATT, ".html", fname)
        with codecs.open(fname, 'w', opts.output_enc) as fp:
            fp.write(out)

    print("success: built project")
Exemple #10
0
def check_mkdir(outpath):
    #directory = os.path.dirname(outpath)
    #print(directory)
    if not opx(outpath):
        print('creating dir:', outpath)
        os.makedirs(outpath)
Exemple #11
0
def build(project, opts):
    """Build a site project."""

    # -------------------------------------------------------------------------
    # utilities
    # -------------------------------------------------------------------------

    def abort_iex(page, itype, inline, exc):
        """Abort because of an exception in inlined Python code."""
        print("abort  : Python %s in %s failed" % (itype, page))
        print((" %s raising the exception " % itype).center(79, "-"))
        print(inline)
        print(" exception ".center(79, "-"))
        print(exc)
        sys.exit(1)

    # -------------------------------------------------------------------------
    # regex patterns and replacements
    # -------------------------------------------------------------------------

    regx_escp = re.compile(r'\\((?:(?:&lt;|<)!--|{)(?:{|%))') # escaped code
    repl_escp = r'\1'
    regx_rurl = re.compile(r'(?<=(?:(?:\n| )src|href)=")([^#/&%].*?)(?=")')
    repl_rurl = lambda m: urlparse.urljoin(opts.base_url, m.group(1))

    regx_eval = re.compile(r'(?<!\\)(?:(?:<!--|{){)(.*?)(?:}(?:-->|}))', re.S)

    def repl_eval(m):
        """Replace a Python expression block by its evaluation."""

        expr = m.group(1)
        try:
            repl = eval(expr, macros.copy())
        except:
            abort_iex(page, "expression", expr, traceback.format_exc())
        else:
            if not isinstance(repl, basestring): # e.g. numbers
                repl = unicode(repl)
            elif not isinstance(repl, unicode):
                repl = repl.decode("utf-8")
            return repl

    regx_exec = re.compile(r'(?<!\\)(?:(?:<!--|{)%)(.*?)(?:%(?:-->|}))', re.S)

    def repl_exec(m):
        """Replace a block of Python statements by their standard output."""

        stmt = m.group(1).replace("\r\n", "\n")

        # base indentation
        ind_lvl = len(re.findall(r'^(?: *\n)*( *)', stmt, re.MULTILINE)[0])
        ind_rex = re.compile(r'^ {0,%d}' % ind_lvl, re.MULTILINE)
        stmt = ind_rex.sub('', stmt)

        # execute
        sys.stdout = StringIO.StringIO()
        try:
            exec stmt in macros.copy()
        except:
            sys.stdout = sys.__stdout__
            abort_iex(page, "statements", stmt, traceback.format_exc())
        else:
            repl = sys.stdout.getvalue()[:-1] # remove last line break
            sys.stdout = sys.__stdout__
            if not isinstance(repl, unicode):
                repl = repl.decode(opts.input_enc)
            return repl

    # -------------------------------------------------------------------------
    # preparations
    # -------------------------------------------------------------------------

    dir_in = opj(project, "input")
    dir_out = opj(project, "output")
    page_html = opj(project, "page.html")

    # check required files and folders
    for pelem in (page_html, dir_in, dir_out):
        if not opx(pelem):
            print("abort  : %s does not exist, looks like project has not been "
                  "initialized" % pelem)
            sys.exit(1)

    # prepare output directory
    for fod in glob.glob(opj(dir_out, "*")):
        if os.path.isdir(fod):
            shutil.rmtree(fod)
        else:
            os.remove(fod)
    if not opx(dir_out):
        os.mkdir(dir_out)

    # macro module
    fname = opj(opts.project, "macros.py")
    macros = imp.load_source("macros", fname).__dict__ if opx(fname) else {}

    macros["__encoding__"] = opts.output_enc
    macros["options"] = opts
    macros["project"] = project
    macros["input"] = dir_in
    macros["output"] = dir_out

    # "builtin" items for use in macros and templates
    macros["hx"] = hx
    macros["htmlspecialchars"] = hx # legacy name of `htmlx` function
    macros["Page"] = Page

    # -------------------------------------------------------------------------
    # process input files
    # -------------------------------------------------------------------------

    Page._template = macros.get("page", {})
    Page._opts = opts
    Page._pstrip = dir_in
    pages = []
    custom_converter = macros.get('converter', {})

    for cwd, dirs, files in os.walk(dir_in.decode(opts.filename_enc)):
        cwd_site = cwd[len(dir_in):].lstrip(os.path.sep)
        for sdir in dirs[:]:
            if re.search(opts.ignore, opj(cwd_site, sdir)):
                dirs.remove(sdir)
            else:
                os.mkdir(opj(dir_out, cwd_site, sdir))
        for f in files:
            if re.search(opts.ignore, opj(cwd_site, f)):
                pass
            elif re.search(MKD_PATT, f):
                page = Page(opj(cwd, f))
                pages.append(page)
            else:
                # either use a custom converter or do a plain copy
                for patt, (func, ext) in custom_converter.items():
                    if re.search(patt, f):
                        f_src = opj(cwd, f)
                        f_dst = opj(dir_out, cwd_site, f)
                        f_dst = '%s.%s' % (os.path.splitext(f_dst)[0], ext)
                        print('info   : convert %s (%s)' % (f_src, func.__name__))
                        func(f_src, f_dst)
                        break
                else:
                    src = opj(cwd, f)
                    try:
                        shutil.copy(src, opj(dir_out, cwd_site))
                    except OSError:
                        # some filesystems like FAT won't allow shutil.copy
                        shutil.copyfile(src, opj(dir_out, cwd_site, f))

    pages.sort(key=lambda p: int(p.get("sval", "0")))

    macros["pages"] = pages

    # -------------------------------------------------------------------------
    # run pre-convert hooks in macro module (named 'once' before)
    # -------------------------------------------------------------------------

    hooks = [a for a in macros if re.match(r'hook_preconvert_|once_', a)]
    for fn in sorted(hooks):
        macros[fn]()

    # -------------------------------------------------------------------------
    # convert pages (markdown to HTML)
    # -------------------------------------------------------------------------

    for page in pages:

        print("info   : convert %s" % page)

        # replace expressions and statements in page source
        macros["page"] = page
        out = regx_eval.sub(repl_eval, page.source)
        out = regx_exec.sub(repl_exec, out)

        # convert to HTML
        extensions = opts.md_ext
        extensions.append('markdown.extensions.fenced_code')
        extensions.append('markdown.extensions.tables')
        extensions.append('markdown.extensions.admonition')
        extensions = list(set(extensions))

        page.html = markdown.Markdown(extensions=extensions).convert(out)

    # -------------------------------------------------------------------------
    # run post-convert hooks in macro module
    # -------------------------------------------------------------------------

    hooks = [a for a in macros if a.startswith("hook_postconvert_")]
    for fn in sorted(hooks):
        macros[fn]()

    # -------------------------------------------------------------------------
    # render complete HTML pages
    # -------------------------------------------------------------------------

    with codecs.open(opj(project, "page.html"), 'r', opts.input_enc) as fp:
        skeleton = fp.read()

    for page in pages:

        print("info   : render %s" % page.url)

        # replace expressions and statements in page.html
        macros["page"] = page
        macros["__content__"] = page.html
        out = regx_eval.sub(repl_eval, skeleton)
        out = regx_exec.sub(repl_exec, out)

        # un-escape escaped python code blocks
        out = regx_escp.sub(repl_escp, out)

        # make relative links absolute
        #out = regx_rurl.sub(repl_rurl, out)

        # write HTML page
        fname = page.fname.replace(dir_in, dir_out)
        fname = re.sub(MKD_PATT, ".html", fname)
        with codecs.open(fname, 'w', opts.output_enc) as fp:
            fp.write(out)

    print("success: built project")
Exemple #12
0
def build(project, opts):
    """Build a site project."""

    # -------------------------------------------------------------------------
    # utilities
    # -------------------------------------------------------------------------

    def abort_iex(page, itype, inline, exc):
        """Abort because of an exception in inlined Python code."""
        print("error  : Python %s in %s failed" % (itype, page.fname))
        print((" %s raising the exception " % itype).center(79, "-"))
        print(inline)
        print(" exception ".center(79, "-"))
        print(exc)
        sys.exit(1)

    # -------------------------------------------------------------------------
    # regex patterns and replacements
    # -------------------------------------------------------------------------

    regx_escp = re.compile(r'\\((?:(?:&lt;|<)!--|{)(?:{|%))')  # escaped code
    repl_escp = r'\1'
    regx_rurl = re.compile(r'(?<=(?:(?:\n| )src|href)=")([^#/&%].*?)(?=")')
    repl_rurl = lambda m: urlparse.urljoin(opts.base_url, m.group(1))

    regx_eval = re.compile(r'(?<!\\)(?:(?:<!--|{){)(.*?)(?:}(?:-->|}))', re.S)

    def repl_eval(m):
        """Replace a Python expression block by its evaluation."""

        expr = m.group(1)
        try:
            repl = eval(expr, macros.copy())
        except:
            abort_iex(page, "expression", expr, traceback.format_exc())
        else:
            if not isinstance(repl, basestring):  # e.g. numbers
                repl = unicode(repl)
            elif not isinstance(repl, unicode):
                repl = repl.decode("utf-8")
            return repl

    regx_exec = re.compile(r'(?<!\\)(?:(?:<!--|{)%)(.*?)(?:%(?:-->|}))', re.S)

    def repl_exec(m):
        """Replace a block of Python statements by their standard output."""

        stmt = m.group(1).replace("\r\n", "\n")

        # base indentation
        ind_lvl = len(re.findall(r'^(?: *\n)*( *)', stmt, re.MULTILINE)[0])
        ind_rex = re.compile(r'^ {0,%d}' % ind_lvl, re.MULTILINE)
        stmt = ind_rex.sub('', stmt)

        # execute
        sys.stdout = StringIO.StringIO()
        try:
            exec stmt in macros.copy()
        except:
            sys.stdout = sys.__stdout__
            abort_iex(page, "statements", stmt, traceback.format_exc())
        else:
            repl = sys.stdout.getvalue()[:-1]  # remove last line break
            sys.stdout = sys.__stdout__
            if not isinstance(repl, unicode):
                repl = repl.decode(opts.input_enc)
            return repl

    # -------------------------------------------------------------------------
    # preparations
    # -------------------------------------------------------------------------

    dir_in = opj(project, "input")
    dir_out = opj(project, OUTPUT_DIR)
    page_html = opj(project, "page.html")

    # check required files and folders
    for pelem in (page_html, dir_in, dir_out):
        if not opx(pelem):
            print(
                "error  : %s does not exist, looks like project has not been "
                "initialized, abort" % pelem)
            sys.exit(1)

    # prepare output directory
    project_path = os.path.realpath(project)
    for fod in glob.glob(opj(dir_out, "*")):
        # don't remove the sources
        real_fod = os.path.realpath(fod)
        if real_fod == project_path or real_fod.startswith(project_path +
                                                           os.path.sep):
            continue
        # don't remove version control
        if os.path.basename(fod) in ('.git', '.hg', '.svn'):
            continue
        if os.path.isdir(fod):
            shutil.rmtree(fod)
        else:
            os.remove(fod)
    if not opx(dir_out):
        os.mkdir(dir_out)

    # macro module
    class nomod:
        pass  # dummy module, something we can set attributes on

    fname = opj(opts.project, "macros.py")
    macros = {"__encoding__": opts.output_enc}
    macmod = opx(fname) and imp.load_source("macros", fname) or nomod
    setattr(macmod, "options", opts)
    setattr(macmod, "project", project)
    setattr(macmod, "input", dir_in)
    setattr(macmod, "output", dir_out)
    for attr in dir(macmod):
        if not attr.startswith("_"):
            macros[attr] = getattr(macmod, attr)

    # -------------------------------------------------------------------------
    # process input files
    # -------------------------------------------------------------------------

    pages = []
    page_global = macros.get("page", {})
    for cwd, dirs, files in os.walk(dir_in.decode(opts.filename_enc)):
        cwd_site = cwd[len(dir_in):].lstrip(os.path.sep)
        for sdir in dirs[:]:
            if re.search(opts.ignore, opj(cwd_site, sdir)):
                dirs.remove(sdir)
            else:
                os.mkdir(opj(dir_out, cwd_site, sdir))
        for f in files:
            if re.search(opts.ignore, opj(cwd_site, f)):
                pass
            elif re.search(MKD_PATT, f):
                page = Page(page_global, opj(cwd, f), dir_in, opts)
                pages.append(page)
            else:
                shutil.copy(opj(cwd, f), opj(dir_out, cwd_site))

    macros["pages"] = pages
    pages.sort(key=lambda p: p.get('fname')
               )  # FIXME: some pages are lost without this, WTF?!
    macmod.pages = pages

    # -------------------------------------------------------------------------
    # run pre-convert hooks in macro module (named 'once' before)
    # -------------------------------------------------------------------------

    hooks = [a for a in dir(macmod) if re.match(r'hook_preconvert_|once_', a)]
    for fn in sorted(hooks):
        getattr(macmod, fn)()

    # -------------------------------------------------------------------------
    # convert pages (markdown to HTML)
    # -------------------------------------------------------------------------

    got = []
    for page in pages:
        print("info   : convert %s" % page.fname)
        if page.fname in got:
            print >> sys.stderr, "ERROR  : %s appears twice" % page.fname
            continue
        got.append(page.fname)

        # replace expressions and statements in page source
        macmod.page = page
        macros["page"] = page
        out = regx_eval.sub(repl_eval, page.source)
        out = regx_exec.sub(repl_exec, out)

        # convert to HTML
        page.html = markdown.Markdown(extensions=opts.md_ext).convert(out)

    # -------------------------------------------------------------------------
    # run post-convert hooks in macro module
    # -------------------------------------------------------------------------

    hooks = [a for a in dir(macmod) if a.startswith("hook_postconvert_")]
    for fn in sorted(hooks):
        getattr(macmod, fn)()

    # -------------------------------------------------------------------------
    # render complete HTML pages
    # -------------------------------------------------------------------------

    with codecs.open(opj(project, "page.html"), 'r', opts.input_enc) as fp:
        skeleton = fp.read()

    pages.sort(key=lambda p: int(p.get("sval", "0")))

    for page in pages:

        print("info   : render %s" % page.url)

        # replace expressions and statements in page.html
        macmod.page = page
        macros["page"] = page
        macros["__content__"] = page.html
        out = regx_eval.sub(repl_eval, skeleton)
        out = regx_exec.sub(repl_exec, out)

        # un-escape escaped python code blocks
        out = regx_escp.sub(repl_escp, out)

        # make relative links absolute
        out = regx_rurl.sub(repl_rurl, out)

        # write HTML page
        fname = page.fname.replace(dir_in, dir_out)
        fname = re.sub(MKD_PATT, ".html", fname)
        with codecs.open(fname, 'w', opts.output_enc) as fp:
            fp.write(out)

    print("success: built project")