From 4acde4bc643cc373b8d91c5509b2c02e4964f871 Mon Sep 17 00:00:00 2001 From: Lakshmi Vyasarajan Date: Thu, 16 Dec 2010 20:43:14 +0530 Subject: [PATCH] Basic Command Line declarative sytnax complete --- dev-req.txt | 3 - hyde/cli.py | 11 +- hyde/command_line.py | 95 +++++++ hyde/engine.py | 69 ++++- .../templates/{jinja2Template.py => jinja.py} | 21 +- hyde/template.py | 13 +- hyde/tests/test_command_line.py | 36 +++ hyde/tests/test_jinja2template.py | 2 +- hyde/tests/util.py | 13 +- pylintrc | 267 ------------------ 10 files changed, 244 insertions(+), 286 deletions(-) create mode 100644 hyde/command_line.py rename hyde/ext/templates/{jinja2Template.py => jinja.py} (52%) create mode 100644 hyde/tests/test_command_line.py delete mode 100644 pylintrc diff --git a/dev-req.txt b/dev-req.txt index e515f09..4b64b51 100644 --- a/dev-req.txt +++ b/dev-req.txt @@ -1,7 +1,4 @@ -Django==1.2.3 -Genshi==0.6 Jinja2==2.5.5 -Mako==0.3.6 Markdown==2.0.3 MarkupSafe==0.11 PyYAML==3.09 diff --git a/hyde/cli.py b/hyde/cli.py index 6f6ffb7..b908024 100644 --- a/hyde/cli.py +++ b/hyde/cli.py @@ -3,15 +3,17 @@ The command line interface for hyde. """ import argparse - -from engine import init, gen, serve from version import __version__ +from engine import init, gen, serve + def main(): """ The main function called by hyde executable """ - parser = argparse.ArgumentParser(description='hyde - A Python Static Website Generator', + import sys + print sys.argv + parser = argparse.ArgumentParser(description='hyde - a python static website generator', epilog='Use %(prog)s {command} -h to get help on individual commands') parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__) parser.add_argument('-s', '--sitepath', action='store', default='.', help="Location of the hyde site") @@ -24,4 +26,5 @@ def main(): init_command.add_argument('-f', '--force', action='store_true', default=False, dest='force', help='Overwrite the current site if it exists') args = parser.parse_args() - args.run(args) \ No newline at end of file + args.run(args) + diff --git a/hyde/command_line.py b/hyde/command_line.py new file mode 100644 index 0000000..a9992bb --- /dev/null +++ b/hyde/command_line.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +""" +A nice declarative interface for Argument parser +""" +from argparse import ArgumentParser, Namespace +from collections import namedtuple + +__all__ = [ + 'command', + 'param', + 'Application' + ] + +class CommandLine(type): + """ + Meta class that enables declarative command definitions + """ + def __new__(cls, name, bases, attrs): + instance = super(CommandLine, cls).__new__(cls, name, bases, attrs) + subcommands = [] + main_command = None + for name, member in attrs.iteritems(): + if hasattr(member, "command"): + main_command = member + # else if member.params: + # subcommands.append(member) + parser = None + if main_command: + parser = ArgumentParser(*main_command.command.args, **main_command.command.kwargs) + for param in main_command.params: + parser.add_argument(*param.args, **param.kwargs) + + + # subparsers = None + # if subcommands.length: + # subparsers = parser.add_subparsers() + # + # for command in subcommands: + # + # for param in main_command.params: + # parser.add_argument(*param.args, **param.kwargs) + + instance.parser = parser + instance.main = main_command + return instance + +values = namedtuple('__meta_values', 'args, kwargs') +class metarator(object): + """ + A generic decorator that tags the decorated method with + the passed in arguments for meta classes to process them. + """ + def __init__(self, *args, **kwargs): + self.values = values._make((args, kwargs)) + + def metarate(self, f, name='values'): + setattr(f, name, self.values) + return f + + def __call__(self, f): + return self.metarate(f) + + +class command(metarator): + """ + Used to decorate the main entry point + """ + def __call__(self, f): + return self.metarate(f, name='command') + +class param(metarator): + """ + Use this decorator instead of `ArgumentParser.add_argument`. + """ + def __call__(self, f): + f.params = f.params if hasattr(f, 'params') else [] + f.params.append(self.values) + return f + + +class Application(object): + """ + Bare bones base class for command line applications. Hides the + meta programming complexities. + """ + __metaclass__ = CommandLine + + def parse(self, argv): + return self.parser.parse_args(argv) + + def run(self, args): + if hasattr(args, 'run'): + args.run(args) + else: + self.main(args) \ No newline at end of file diff --git a/hyde/engine.py b/hyde/engine.py index 00e3736..5aa7fbc 100644 --- a/hyde/engine.py +++ b/hyde/engine.py @@ -3,7 +3,74 @@ def init(args): print args.sitepath print args.force print args.template + # Ensure sitepath is okay (refer to force parameter) + # Find template by looking at the paths + # 1. Environment Variable + # 2. Hyde Data Directory + # Throw exception on failure + # Do not delete the site path, just overwrite existing files def gen(args): pass -def serve(args): pass \ No newline at end of file +def serve(args): pass + +from version import __version__ + +# """ +# Implements the hyde entry point commands +# """ +# class Command(object): +# """ +# Base class for hyde commands +# """ +# def __init__(self, **kwargs): +# super(Command, self).__init__(epilog='Use %(prog)s {command} -h to get help on individual commands', **kwargs) +# self.subcommands = None +# +# def compose(self, commands, **kwargs): +# self.subcommands = self.add_subparsers(**kwargs) +# for command in commands: +# self.subcommands.add_parser(command) +# +# def run(self, args=None): +# """ +# Executes the command +# """ +# options = {} +# options.update(self.defaults) +# if args: +# options.update(args) +# self.execute(options) +# +# def execute(self): +# """ +# Abstract method for the derived classes +# """ +# abstract +# +# class HydeCommand(Command): +# """ +# The parent command object. +# """ +# def __init__(self, **kwargs): +# super(HydeCommand, self).__init__(**kwargs) +# self.add_argument('--version', action='version', version='%(prog)s ' + __version__) +# self.add_argument('-s', '--sitepath', action='store', default='.', help="Location of the hyde site") +# +# +# class Initializer(Command): +# """ +# Represents the `hyde init` command +# """ +# def __init__(self, parent, **kwargs): +# super(Initializer, self).__init__(**kwargs) +# init_command.add_argument('-t', '--template', action='store', default='basic', dest='template', +# help='Overwrite the current site if it exists') +# init_command.add_argument('-f', '--force', action='store_true', default=False, dest='force', +# help='Overwrite the current site if it exists') +# +# def run(self): +# """ +# +# """ +# pass \ No newline at end of file diff --git a/hyde/ext/templates/jinja2Template.py b/hyde/ext/templates/jinja.py similarity index 52% rename from hyde/ext/templates/jinja2Template.py rename to hyde/ext/templates/jinja.py index af42aba..10f6ed2 100644 --- a/hyde/ext/templates/jinja2Template.py +++ b/hyde/ext/templates/jinja.py @@ -1,19 +1,28 @@ """ -Hyde Template interface realization for Jinja2 +Jinja template utilties """ from hyde.template import Template from jinja2 import Environment, FileSystemLoader +# pylint: disable-msg=W0104,E0602,W0613,R0201 class Jinja2Template(Template): - def configure(self, sitepath, config): + """ + The Jinja2 Template implementation + """ + + def __init__(self, sitepath): + super(Jinja2Template, self).__init__(sitepath) + self.env = Environment(loader=FileSystemLoader(sitepath)) + + def configure(self, config): """ Uses the config object to initialize the jinja environment. """ - self.env = Environment(loader=FileSystemLoader(sitepath)) - + pass + def render(self, template_name, context): """ Renders the given template using the context """ - t = self.env.get_template(template_name) - return t.render(context) \ No newline at end of file + template = self.env.get_template(template_name) + return template.render(context) diff --git a/hyde/template.py b/hyde/template.py index 96dfc64..7b13124 100644 --- a/hyde/template.py +++ b/hyde/template.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- +# pylint: disable-msg=W0104,E0602,W0613,R0201 """ -Interface for hyde template engines. To use a different template engine, -the following interface must be implemented. +Abstract classes and utilities for template engines """ class Template(object): + """ + Interface for hyde template engines. To use a different template engine, + the following interface must be implemented. + """ + def __init__(self, sitepath): + self.sitepath = sitepath + def configure(self, config): """ The config object is a simple YAML object with required settings. The template @@ -12,7 +19,7 @@ class Template(object): """ abstract - def render(template_name, context): + def render(self, template_name, context): """ Given the name of a template (partial path usually), and the context, this function must return the rendered string. diff --git a/hyde/tests/test_command_line.py b/hyde/tests/test_command_line.py new file mode 100644 index 0000000..e07323b --- /dev/null +++ b/hyde/tests/test_command_line.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +""" +Use nose +`$ pip install nose` +`$ nosetests` +""" + +from hyde.command_line import Application, command, param +from util import trap_exit +from mock import Mock, patch + +@trap_exit +def test_command_basic(): + + number_of_calls = 0 + class TestCommandLine(Application): + + @command(description='test') + @param('--force', action='store_true', dest='force1') + @param('--force2', action='store', dest='force2') + @param('--version', action='version', version='%(prog)s 1.0') + def main(self, params): + assert params.force1 == eval(params.force2) + self._main() + + def _main(): pass + + with patch.object(TestCommandLine, '_main') as _main: + c = TestCommandLine() + args = c.parse(['--force', '--force2', 'True']) + c.run(args) + + args = c.parse(['--force2', 'False']) + c.run(args) + + assert _main.call_count == 2 \ No newline at end of file diff --git a/hyde/tests/test_jinja2template.py b/hyde/tests/test_jinja2template.py index 0922719..8d419ae 100644 --- a/hyde/tests/test_jinja2template.py +++ b/hyde/tests/test_jinja2template.py @@ -7,7 +7,7 @@ Use nose Code borrowed from rwbench.py from the jinja2 examples """ from datetime import datetime -from hyde.ext.templates.jinja2Template import Jinja2Template +from hyde.ext.templates.jinja import Jinja2Template from hyde.fs import File, Folder from jinja2.utils import generate_lorem_ipsum from random import choice, randrange diff --git a/hyde/tests/util.py b/hyde/tests/util.py index 423ee00..d229f77 100644 --- a/hyde/tests/util.py +++ b/hyde/tests/util.py @@ -6,4 +6,15 @@ def assert_html_equals(expected, actual, sanitize=None): if sanitize: expected = sanitize(expected) actual = sanitize(actual) - assert expected == actual \ No newline at end of file + assert expected == actual + +def trap_exit(f): + def test_wrapper(*args): + try: + f(*args) + except SystemExit, e: + print "Error running test [%s]" % f.__name__ + print e.message + raise e + return test_wrapper + diff --git a/pylintrc b/pylintrc deleted file mode 100644 index 3f38a46..0000000 --- a/pylintrc +++ /dev/null @@ -1,267 +0,0 @@ -[MASTER] - -# Profiled execution. -profile=no - -# Add to the black list. It should be a base name, not a -# path. You may set this option multiple times. -#ignore=.svn - -# Pickle collected data for later comparisons. -persistent=yes - -# Set the cache size for astng objects. -cache-size=500 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - - -[MESSAGES CONTROL] - -# Enable only checker(s) with the given id(s). This option conflicts with the -# disable-checker option -#enable-checker= - -# Enable all checker(s) except those with the given id(s). This option -# conflicts with the enable-checker option -#disable-checker=design - -# Enable all messages in the listed categories. -#enable-msg-cat= - -# Disable all messages in the listed categories. -#disable-msg-cat= - -# Enable the message(s) with the given id(s). -#enable-msg= - - -# Disable the message(s) with the given id(s). -# List of all available ids: http://www.logilab.org/card/pylintfeatures - -# Disabled messages: -# I0011: Locally disabling %s Used when an inline option disable a message or a messages category. -disable-msg=I0011 - - -[REPORTS] - -# set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html -output-format=text - -# Include message's id in output -include-ids=yes - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - -# Tells wether to display a full report or only the messages -reports=yes - -# Python expression which should return a note less than 10 (10 is the highest -# note).You have access to the variables errors warning, statement which -# respectivly contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (R0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Add a comment according to your evaluation note. This is used by the global -# evaluation report (R0004). -comment=no - -# Enable the report(s) with the given id(s). -#enable-report= - -# Disable the report(s) with the given id(s). -#disable-report= - - -# checks for : -# * doc strings -# * modules / classes / functions / methods / arguments / variables name -# * number of arguments, local variables, branchs, returns and statements in -# functions, methods -# * required module attributes -# * dangerous default values as arguments -# * redefinition of function / method / class -# * uses of the global statement -# -[BASIC] - -# Required attributes for module, separated by a comma -required-attributes= - -# Regular expression which should only match functions or classes name which do -# not require a docstring -no-docstring-rgx=__.*__ - -# Regular expression which should only match correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression which should only match correct module level names -const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__)|([a-z_][a-z0-9_]*))$ - -# Regular expression which should only match correct class names -class-rgx=[_a-zA-Z0-9]+$ - -# Regular expression which should only match correct function names -function-rgx=[a-z_][a-zA-Z0-9_]{2,40}$ - -# Regular expression which should only match correct method names -method-rgx=[a-z_][a-zA-Z0-9_]{2,40}$ - -# Regular expression which should only match correct instance attribute names -attr-rgx=[a-z_][a-z0-9_]{1,30}$ -#alternative -#attr-rgx=([a-z_][a-z0-9_]{2,30}|([a-z_][a-zA-Z0-9]{2,30}))$ - -# Regular expression which should only match correct argument names -argument-rgx=[a-z_][a-z0-9_]{1,30}$ - -# Regular expression which should only match correct variable names -variable-rgx=[a-z_][a-zA-Z0-9_]{1,30}$ - -# Regular expression which should only match correct list comprehension / -# generator expression variable names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# List of builtins function names that should not be used, separated by a comma -bad-functions= - - -# try to find bugs in the code using type inference -# -[TYPECHECK] - -# Tells wether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamicaly set). -ignored-classes=SQLObject - -# When zope mode is activated, consider the acquired-members option to ignore -# access to some undefined attributes. -zope=no - -# List of members which are usually get through zope's acquisition mecanism and -# so shouldn't trigger E0201 when accessed (need zope=yes to be considered). -acquired-members=REQUEST,acl_users,aq_parent - - -# checks for -# * unused variables / imports -# * undefined variables -# * redefinition of variable from builtins or from an outer scope -# * use of variable before assigment -# -[VARIABLES] - -# Tells wether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching names used for dummy variables (i.e. not used). -dummy-variables-rgx=_|dummy - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - - -# checks for : -# * methods without self as first argument -# * overridden methods signature -# * access only to existant members via self -# * attributes not defined in the __init__ method -# * supported interfaces implementation -# * unreachable code -# -[CLASSES] - -# List of interface methods to ignore, separated by a comma. This is used for -# instance to not check methods defines in Zope's Interface base class. -ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - - -# checks for -# * external modules dependencies -# * relative / wildcard imports -# * cyclic imports -# * uses of deprecated modules -# -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,string,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report R0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report R0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report R0402 must -# not be disabled) -int-import-graph= - - -# checks for : -# * unauthorized constructions -# * strict indentation -# * line length -# * use of <> instead of != -# -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=120 - -# Maximum number of lines in a module -max-module-lines=1000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -#indent-string=' ' - - -# checks for: -# * warning notes in the code like TODO -# * PEP 263: source code with non ascii character but no encoding declaration -# -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=TODO - - -# checks for similarities and duplicated code. This computation may be -# memory / CPU intensive, so you should disable it if you experiments some -# problems. -# -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes \ No newline at end of file