首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >静态博客生成器

静态博客生成器
EN

Code Review用户
提问于 2014-09-17 18:32:50
回答 1查看 252关注 0票数 4

我还想对我的静态博客生成器脚本进行另一次审查,以求改进。

代码语言:javascript
复制
#
#Static blogs generator.
#See https://github.com/st-kurilin/blogator for details.
#
#Main script. Used to build final script using build.py script.
#

###Files operations
#separated to make testing easier

"""Default values for some files.
   Used to distribute script as a single file.
   Actual values filled by build.py script."""
PREDEFINED = {}

def read(path):
    """Reads file content from FS"""
    if path in PREDEFINED:
        return PREDEFINED[path]
    with open(path.as_posix()) as file:
        return file.read()

def write(path, content):
    """Writes file content to FS"""
    with open(path.as_posix(), 'w') as file:
        file.write(content)

def copy(from_p, to_p):
    """Copies file content"""
    import shutil
    shutil.copyfile(from_p.as_posix(), to_p.as_posix())

def file_exist(path):
    """Check if file exist for specified path"""
    return path.is_file()


###Markdown template engine operations
def md_read(inp):
    """Reads markdown formatted message."""
    import markdown
    md_converter = markdown.Markdown(extensions=['meta'])
    content = md_converter.convert(inp)
    meta = getattr(md_converter, 'Meta', [])
    return {
        'meta' : meta,
        'content' : content
    }

def md_meta_get(meta, key, alt=None, single_value=True):
    """Reads value from markdown read message meta."""
    if key in meta:
        if single_value and meta[key]:
            return meta[key][0]
        else:
            return meta[key]
    return alt


###Pystache template engine operations
def pystached(template, data):
    """Applies data to pystache template"""
    import pystache
    pys_template = pystache.parse(template)
    pys_renderer = pystache.Renderer()
    return pys_renderer.render(pys_template, data)


###Meta files readers operations
def parse_blog_meta(blog_meta_content):
    """Reads general blog info from file."""
    from functools import partial
    meta = md_read(blog_meta_content)['meta']
    get = partial(md_meta_get, meta)
    favicon_file = get('favicon-file')
    favicon_url = get('favicon-url', 'favicon.cc/favicon/169/1/favicon.png')
    return {
        'meta'         : meta,
        'title'        : get('title', 'Blog'),
        'annotation'   : get('annotation', 'Blogging for living'),
        'favicon-file' : favicon_file,
        'favicon'      : 'favicon.ico' if favicon_file else favicon_url,
        'posts'        : get('posts', [], False),
        'disqus'       : get('disqus'),
        'ganalitics'   : get('ganalitics'),
    }

def parse_post(post_blob, post_blob_orig_name):
    """Reads post info from file."""
    import datetime
    from functools import partial

    def reformat_date(inpf, outf, date):
        """Reformats dates from one specified format to other one."""
        if date is None:
            return None
        return datetime.datetime.strptime(date, inpf).strftime(outf)

    row_post = md_read(post_blob)
    post = {}
    post['meta'] = meta = row_post['meta']
    get = partial(md_meta_get, meta)
    post['content'] = row_post['content']
    post['title'] = get('title', post_blob_orig_name)
    post['brief'] = get('brief')
    post['short_title'] = get('short_title', post['title'])
    post['link_base'] = get('link', post_blob_orig_name + ".html")
    post['link'] = './' + post['link_base']
    post['published'] = reformat_date('%Y-%m-%d', '%d %b %Y',
                                      get('published'))
    return post


###Flow operations
def clean_target(target):
    """Cleans target directory. Hidden files will not be deleted."""
    import os
    import glob
    tpath = target.as_posix()
    if not os.path.exists(tpath):
        os.makedirs(tpath)
    for file in glob.glob(tpath + '/*'):
        os.remove(file)

def generate(blog_path, templates, target):
    """Generates blog content. Target directory expected to be empty."""

    def prepare_favicon(blog, blog_home_dir, target):
        """Puts favicon file in right place with right name."""
        if blog['favicon-file']:
            orig_path = blog_home_dir / blog['favicon-file']
            destination_path = target / 'favicon.ico'
            copy(orig_path, destination_path)

    def marked_active_post(orig_posts, active_index):
        """Returns copy of original posts
        with specified post marked as active"""
        active_post = orig_posts[active_index]
        posts_view = orig_posts.copy()
        active_post = orig_posts[active_index].copy()
        active_post['active?'] = True
        posts_view[active_index] = active_post
        return posts_view

    def write_templated(template_path, out_path, data):
        """Generate templated content to file."""
        write(out_path, pystached(read(template_path), data))

    def fname(file_name):
        """file name without extension"""
        from pathlib import Path
        as_path = Path(file_name)
        name = as_path.name
        suffix = as_path.suffix
        return name.rsplit(suffix, 1)[0]

    def read_post(declared_path, work_dir):
        """Find location of post and read it"""
        from pathlib import Path
        candidates = [work_dir / declared_path, Path(declared_path)]
        for candidate in candidates:
            if file_exist(candidate):
                return read(candidate)
        raise NameError("Tried find post file by [{}] but didn't find anything"
                        .format(', '.join([_.as_posix() for _ in candidates])))

    blog = parse_blog_meta(read(blog_path))
    work_dir = blog_path.parent
    prepare_favicon(blog, work_dir, target)
    posts = [parse_post(read_post(_, work_dir), fname(_))
             for _ in blog['posts']]
    for active_index, post in enumerate(posts):
        posts_view = marked_active_post(posts, active_index)
        write_templated(templates / "post.template.html",
                        target / post['link_base'],
                        {'blog': blog, 'posts': posts_view, 'post': post})

    write_templated(templates / "index.template.html",
                    target / "index.html",
                    {'blog' : blog, 'posts': posts})


###Utils
def create_parser():
    """Parser factory method."""
    import argparse
    from pathlib import Path

    parser = argparse.ArgumentParser(description='''Generates static blog
                                                  content from markdown posts.
                                                 ''')
    parser.add_argument('blog',
                        type=Path,
                        help='File with general information about blog',
                        default='blog')
    parser.add_argument('-target',
                        type=Path,
                        help='generated content destination',
                        default='target')
    parser.add_argument('-templates',
                        type=Path,
                        help='directory with templates',
                        default='blogtor-virtual/templates')
    return parser


def main():
    """Start endpoint"""
    args = create_parser().parse_args()
    clean_target(args.target)
    generate(args.blog, args.templates, args.target)

我使用一个单一源文件来简化分发和导入功能,使其更加清晰。

EN

回答 1

Code Review用户

回答已采纳

发布于 2014-09-22 12:06:24

(代码看起来很棒,我仍然试图提供有用的建议。)

我使用单个源文件使分发变得更容易。

为什么一个.py文件更容易分发?我们有很多种方法来分发Python文件,比如git clone ... && python setup.py install,pip,轮子.

并在功能上进行导入,让自己更加清楚。

除了违反PEP 8,这意味着其他开发人员将难以理解(并坚持)您的约定。谈到PEP8,pip install flake8flake8 myfile.py会有所帮助。我不会再评论词汇语法了。

代码语言:javascript
复制
###Files operations
#separated to make testing easier

这些函数只会使代码复杂化,因为提供的抽象不足以补偿所增加的复杂性。

代码语言:javascript
复制
"""Default values for some files.
   Used to distribute script as a single file.
   Actual values filled by build.py script."""
PREDEFINED = {}

def read(path):
    """Reads file content from FS"""
    if path in PREDEFINED:
        return PREDEFINED[path]
    with open(path.as_posix()) as file:
        return file.read()

这将不需要一个标准的方式来分发您的文件。此外,路径库文档建议str(path)而不是path.as_posix()

代码语言:javascript
复制
def write(path, content):
    """Writes file content to FS"""
    with open(path.as_posix(), 'w') as file:
        file.write(content)

既然您是在应用程序的上下文中,是否可以给write函数取一个更有意义的名称?

代码语言:javascript
复制
def copy(from_p, to_p):
    """Copies file content"""
    import shutil
    shutil.copyfile(from_p.as_posix(), to_p.as_posix())

def file_exist(path):
    """Check if file exist for specified path"""
    return path.is_file()

考虑一下path.is_file()的清晰性,但是这两个函数使代码变得复杂(我需要查找它们所做的事情),而shutil.copyfileis_file()是标准的,更有可能为其他开发人员所了解。

代码语言:javascript
复制
###Markdown template engine operations
def md_read(inp):
    """Reads markdown formatted message."""
    import markdown
    md_converter = markdown.Markdown(extensions=['meta'])
    content = md_converter.convert(inp)
    meta = getattr(md_converter, 'Meta', [])

md_converter.meta还不够吗?

代码语言:javascript
复制
    return {
        'meta' : meta,
        'content' : content
    }

这很有趣。您可以将其视为要传递的规范数据结构,使用的是标准名称,而不是发送特定的metacontent

代码语言:javascript
复制
def md_meta_get(meta, key, alt=None, single_value=True):
    """Reads value from markdown read message meta."""
    if key in meta:
        if single_value and meta[key]:
            return meta[key][0]
        else:
            return meta[key]
    return alt

考虑使用meta_key = meta.get(key, alt)

代码语言:javascript
复制
###Pystache template engine operations
def pystached(template, data):
    """Applies data to pystache template"""
    import pystache
    pys_template = pystache.parse(template)
    pys_renderer = pystache.Renderer()
    return pys_renderer.render(pys_template, data)

很好的抽象!

代码语言:javascript
复制
###Meta files readers operations
def parse_blog_meta(blog_meta_content):
    """Reads general blog info from file."""
    from functools import partial
    meta = md_read(blog_meta_content)['meta']
    get = partial(md_meta_get, meta)

get有点泛泛而谈,令人困惑。保存几个按键有那么重要吗?

代码语言:javascript
复制
    favicon_file = get('favicon-file')
    favicon_url = get('favicon-url', 'favicon.cc/favicon/169/1/favicon.png')
    return {
        'meta'         : meta,
        'title'        : get('title', 'Blog'),
        'annotation'   : get('annotation', 'Blogging for living'),
        'favicon-file' : favicon_file,
        'favicon'      : 'favicon.ico' if favicon_file else favicon_url,
        'posts'        : get('posts', [], False),
        'disqus'       : get('disqus'),
        'ganalitics'   : get('ganalitics'),
    }

def parse_post(post_blob, post_blob_orig_name):
    """Reads post info from file."""
    import datetime
    from functools import partial

    def reformat_date(inpf, outf, date):
        """Reformats dates from one specified format to other one."""
        if date is None:
            return None
        return datetime.datetime.strptime(date, inpf).strftime(outf)

    row_post = md_read(post_blob)
    post = {}
    post['meta'] = meta = row_post['meta']
    get = partial(md_meta_get, meta)
    post['content'] = row_post['content']
    post['title'] = get('title', post_blob_orig_name)
    post['brief'] = get('brief')
    post['short_title'] = get('short_title', post['title'])
    post['link_base'] = get('link', post_blob_orig_name + ".html")
    post['link'] = './' + post['link_base']

这有什么意义呢?如果没有post['link_base']的话,这不是等价的吗?

代码语言:javascript
复制
    post['published'] = reformat_date('%Y-%m-%d', '%d %b %Y',
                                      get('published'))
    return post


###Flow operations
def clean_target(target):
    """Cleans target directory. Hidden files will not be deleted."""
    import os
    import glob
    tpath = target.as_posix()
    if not os.path.exists(tpath):
        os.makedirs(tpath)
    for file in glob.glob(tpath + '/*'):
        os.remove(file)

很好的抽象。确保您的用户可以理解任何故障。

代码语言:javascript
复制
def generate(blog_path, templates, target):
    """Generates blog content. Target directory expected to be empty."""

我喜欢嵌套函数,但请承认“请把我放在我自己的模块中!”:)

代码语言:javascript
复制
    def prepare_favicon(blog, blog_home_dir, target):
        """Puts favicon file in right place with right name."""
        if blog['favicon-file']:
            orig_path = blog_home_dir / blog['favicon-file']
            destination_path = target / 'favicon.ico'
            copy(orig_path, destination_path)

    def marked_active_post(orig_posts, active_index):
        """Returns copy of original posts
        with specified post marked as active"""
        active_post = orig_posts[active_index]
        posts_view = orig_posts.copy()
        active_post = orig_posts[active_index].copy()
        active_post['active?'] = True
        posts_view[active_index] = active_post
        return posts_view

所以..。orig_posts[active_index]['active?'] = True不等同于marked_active_post[orig_posts, active_index]吗?

代码语言:javascript
复制
    def write_templated(template_path, out_path, data):
        """Generate templated content to file."""
        write(out_path, pystached(read(template_path), data))

    def fname(file_name):
        """file name without extension"""
        from pathlib import Path
        as_path = Path(file_name)
        name = as_path.name
        suffix = as_path.suffix
        return name.rsplit(suffix, 1)[0]

使用return Path(file_name).stem

代码语言:javascript
复制
    def read_post(declared_path, work_dir):
        """Find location of post and read it"""
        from pathlib import Path
        candidates = [work_dir / declared_path, Path(declared_path)]
        for candidate in candidates:
            if file_exist(candidate):
                return read(candidate)
        raise NameError("Tried find post file by [{}] but didn't find anything"
                        .format(', '.join([_.as_posix() for _ in candidates])))

    blog = parse_blog_meta(read(blog_path))
    work_dir = blog_path.parent
    prepare_favicon(blog, work_dir, target)
    posts = [parse_post(read_post(_, work_dir), fname(_))
             for _ in blog['posts']]
    for active_index, post in enumerate(posts):
        posts_view = marked_active_post(posts, active_index)
        write_templated(templates / "post.template.html",
                        target / post['link_base'],
                        {'blog': blog, 'posts': posts_view, 'post': post})

    write_templated(templates / "index.template.html",
                    target / "index.html",
                    {'blog' : blog, 'posts': posts})


###Utils
def create_parser():
    """Parser factory method."""
    import argparse
    ...

谢谢您使用argparse (顺便说一下,还有pathlib.Path )。还有比create_parserParser factory method更好的吗?这些注释适用于任何argparse使用。

代码语言:javascript
复制
def main():
    """Start endpoint"""
    args = create_parser().parse_args()
    clean_target(args.target)
    generate(args.blog, args.templates, args.target)
票数 5
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/63174

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档