def execute(cmd, ns, src, dest=None): """Execute `cmd` such as `yui-compressor %1 -o %2` in-place. If `dest` is none, you don't have to supply %2.""" assert '%1' in cmd cmd = cmd.replace('%1', src) if dest: assert '%2' in cmd cmd = cmd.replace('%2', dest) if not isdir(dirname(dest)): os.makedirs(dirname(dest)) try: rv = system(cmd, shell=True) except (AcrylamidException, OSError): log.exception("uncaught exception during execution") return if dest is None: fd, path = mkstemp() with io.open(fd, 'w', encoding='utf-8') as fp: fp.write(rv) shutil.move(path, src) log.info('update %s', src) else: log.info('create %s', dest)
def run(conf, env, options): """Subcommand: new -- create a new blog entry the easy way. Either run ``acrylamid new My fresh new Entry`` or interactively via ``acrylamid new`` and the file will be created using the preferred permalink format.""" # we need the actual defaults values commands.initialize(conf, env) ext = conf.get('content_extension', '.txt') fd, tmp = tempfile.mkstemp(suffix=ext, dir='.cache/') editor = os.getenv('VISUAL') if os.getenv('VISUAL') else os.getenv('EDITOR') tt = formats.get((conf.get('metastyle') == 'native', ext), yaml) if not options.title: options.title = raw_input("Entry's title: ") title = (' '.join(options.title)) if not PY3: title = title.decode('utf-8') with io.open(fd, 'w') as f: f.write(tt(title, datetime.now().strftime(conf['date_format']))) entry = readers.Entry(tmp, conf) p = join(conf['content_dir'], dirname(entry.permalink)[1:]) try: os.makedirs(p.rsplit('/', 1)[0]) except OSError: pass filepath = p + ext if isfile(filepath): raise AcrylamidException('Entry already exists %r' % filepath) shutil.move(tmp, filepath) event.create(filepath) if datetime.now().hour == 23 and datetime.now().minute > 45: log.info("notice consider editing entry.date-day after you passed mignight!") if log.level() >= log.WARN: return try: if editor: retcode = subprocess.call([editor, filepath]) elif sys.platform == 'darwin': retcode = subprocess.call(['open', filepath]) else: retcode = subprocess.call(['xdg-open', filepath]) except OSError: raise AcrylamidException('Could not launch an editor') # XXX process detaches... m( if retcode < 0: raise AcrylamidException('Child was terminated by signal %i' % -retcode) if os.stat(filepath)[6] == 0: raise AcrylamidException('File is empty!')
def autocompile(ws, conf, env, **options): """Subcommand: autocompile -- automatically re-compiles when something in content-dir has changed and parallel serving files.""" CONF_PY = './conf.py' mtime = -1 cmtime = getmtime(CONF_PY) while True: ntime = max( max( getmtime(e) for e in readers.filelist(conf['content_dir']) if utils.istext(e)), max(getmtime(p) for p in readers.filelist(conf['layout_dir']))) if mtime != ntime: try: compile(conf, env, **options) except AcrylamidException as e: log.fatal(e.args[0]) pass event.reset() mtime = ntime if cmtime != getmtime(CONF_PY): log.info(' * Restarting due to change in %s' % (CONF_PY)) # Kill the webserver ws.shutdown() # Force compilation since no template was changed argv = sys.argv if options['force'] else sys.argv[:] + ["--force"] # Restart acrylamid os.execvp(sys.argv[0], argv) time.sleep(1)
def autocompile(ws, conf, env): """Subcommand: autocompile -- automatically re-compiles when something in content-dir has changed and parallel serving files.""" mtime = -1 cmtime = getmtime('conf.py') while True: ws.wait = True ntime = max( max(getmtime(e) for e in readers.filelist( conf['content_dir'], conf.get('content_ignore', [])) if istext(e)), max(getmtime(p) for p in readers.filelist( conf['theme'], conf.get('theme_ignore', [])))) if mtime != ntime: try: compile(conf, env) except (SystemExit, KeyboardInterrupt) as e: raise e except Exception as e: log.fatal(e.args[0]) event.reset() mtime = ntime ws.wait = False if cmtime != getmtime('conf.py'): log.info(' * Restarting due to change in conf.py') # Kill the webserver ws.shutdown() # Restart acrylamid os.execvp(sys.argv[0], sys.argv) time.sleep(1)
def normalize(conf): # metastyle has been removed if 'metastyle' in conf: log.info('notice METASTYLE is no longer needed to determine the metadata format ' + \ 'and can be removed.') # deprecated since 0.8 if isinstance(conf['static'], list): conf['static'] = conf['static'][0] log.warn("multiple static directories has been deprecated, " + \ "Acrylamid continues with '%s'.", conf['static']) # deprecated since 0.8 for fx in 'Jinja2', 'Mako': try: conf['static_filter'].remove(fx) except ValueError: pass else: log.warn( "%s asset filter has been renamed to `Template` and is " "included by default.", fx) for key in 'content_dir', 'theme', 'static', 'output_dir': if conf[key] is not None and not conf[key].endswith('/'): conf[key] += '/' for key in 'views_dir', 'filters_dir': if isinstance(conf[key], compat.string_types): conf[key] = [ conf[key], ] return conf
def run(conf, env, options): """Subcommand: deploy -- run the shell command specified in DEPLOYMENT[task] using Popen. Each string value from :doc:`conf.py` is added to the execution environment. Every argument after ``acrylamid deploy task ARG1 ARG2`` is appended to cmd.""" if options.list: for task in conf.get('deployment', {}).keys(): print >>sys.stdout, task sys.exit(0) task, args = options.task or 'default', options.args cmd = conf.get('deployment', {}).get(task, None) if not cmd: raise AcrylamidException('no tasks named %r in conf.py' % task) # apply ARG1 ARG2 ... and -v --long-args to the command, e.g.: # $> acrylamid deploy task arg1 -b --foo cmd += ' ' + ' '.join(args) enc = sys.getfilesystemencoding() env = os.environ env.update(dict([(k.upper(), v.encode(enc, 'replace')) for k, v in conf.items() if isinstance(v, basestring)])) log.info('execute %s', cmd) p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: output = p.stdout.read(1) if output == '' and p.poll() != None: break if output != '': sys.stdout.write(output) sys.stdout.flush()
def run(cmd, ns, src, dest=None): """Execute `cmd` such as `yui-compressor %1 -o %2` in-place. If `dest` is none, you don't have to supply %2.""" assert '%1' in cmd cmd = cmd.replace('%1', src) if dest: assert '%2' in cmd cmd = cmd.replace('%2', dest) if not isdir(dirname(dest)): os.makedirs(dirname(dest)) try: rv = system(cmd, shell=True) except (AcrylamidException, OSError): log.exception("uncaught exception during execution") return if dest is None: fd, path = mkstemp() with io.open(fd, 'w', encoding='utf-8') as fp: fp.write(rv) shutil.move(path, src) log.info('update %s', src) else: log.info('create %s', dest)
def autocompile(ws, conf, env, **options): """Subcommand: autocompile -- automatically re-compiles when something in content-dir has changed and parallel serving files.""" CONF_PY = './conf.py' mtime = -1 cmtime = getmtime(CONF_PY) while True: ntime = max( max(getmtime(e) for e in readers.filelist(conf['content_dir']) if utils.istext(e)), max(getmtime(p) for p in readers.filelist(conf['layout_dir']))) if mtime != ntime: try: compile(conf, env, **options) except AcrylamidException as e: log.fatal(e.args[0]) pass event.reset() mtime = ntime if cmtime != getmtime(CONF_PY): log.info(' * Restarting due to change in %s' % (CONF_PY)) # Kill the webserver ws.shutdown() # Force compilation since no template was changed argv = sys.argv if options['force'] else sys.argv[:] + ["--force"] # Restart acrylamid os.execvp(sys.argv[0], argv) time.sleep(1)
def deploy(conf, env, task, *args): """Subcommand: deploy -- run the shell command specified in DEPLOYMENT[task] using Popen. Use ``%s`` inside your command to let acrylamid substitute ``%s`` with the output path, if no ``%s`` is set, the path is appended as first argument. Every argument after ``acrylamid deploy task ARG1 ARG2`` is appended to cmd.""" cmd = conf.get('deployment', {}).get(task, None) if not cmd: raise AcrylamidException('no tasks named %r in conf.py' % task) if '%s' in cmd: cmd = cmd.replace('%s', conf['output_dir']) else: # append output-string cmd += ' ' + conf['output_dir'] # apply ARG1 ARG2 ... and -v --long-args to the command, e.g.: # $> acrylamid deploy task arg1 -- -b --foo cmd += ' ' + ' '.join(args) log.info('execute %s', cmd) p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: output = p.stdout.read(1) if output == '' and p.poll() != None: break if output != '': sys.stdout.write(output) sys.stdout.flush()
def normalize(conf): # metastyle has been removed if 'metastyle' in conf: log.info('notice METASTYLE is no longer needed to determine the metadata format ' + \ 'and can be removed.') # deprecated since 0.8 if isinstance(conf['static'], list): conf['static'] = conf['static'][0] log.warn("multiple static directories has been deprecated, " + \ "Acrylamid continues with '%s'.", conf['static']) # deprecated since 0.8 for fx in 'Jinja2', 'Mako': try: conf['static_filter'].remove(fx) except ValueError: pass else: log.warn("%s asset filter has been renamed to `Template` and is " "included by default.", fx) for key in 'content_dir', 'theme', 'static', 'output_dir': if conf[key] is not None and not conf[key].endswith('/'): conf[key] += '/' for key in 'views_dir', 'filters_dir': if isinstance(conf[key], compat.string_types): conf[key] = [conf[key], ] return conf
def new(conf, env, title, prompt=True): """Subcommand: new -- create a new blog entry the easy way. Either run ``acrylamid new My fresh new Entry`` or interactively via ``acrylamid new`` and the file will be created using the preferred permalink format.""" # we need the actual defaults values initialize(conf, env) fd, tmp = tempfile.mkstemp(suffix='.txt', dir='.cache/') editor = os.getenv('VISUAL') if os.getenv('VISUAL') else os.getenv('EDITOR') if not title: title = raw_input("Entry's title: ") title = escape(title) with io.open(fd, 'w') as f: f.write(u'---\n') f.write(u'title: %s\n' % title) f.write(u'date: %s\n' % datetime.now().strftime(conf['date_format'])) f.write(u'---\n\n') entry = readers.Entry(tmp, conf) p = join(conf['content_dir'], dirname(entry.permalink)[1:]) try: os.makedirs(p.rsplit('/', 1)[0]) except OSError: pass filepath = p + '.txt' if isfile(filepath): raise AcrylamidException('Entry already exists %r' % filepath) shutil.move(tmp, filepath) event.create(filepath) if datetime.now().hour == 23 and datetime.now().minute > 45: log.info("notice consider editing entry.date-day after you passed mignight!") if not prompt: return try: if editor: retcode = subprocess.call([editor, filepath]) elif sys.platform == 'darwin': retcode = subprocess.call(['open', filepath]) else: retcode = subprocess.call(['xdg-open', filepath]) except OSError: raise AcrylamidException('Could not launch an editor') # XXX process detaches... m( if retcode < 0: raise AcrylamidException('Child was terminated by signal %i' % -retcode) if os.stat(filepath)[6] == 0: raise AcrylamidException('File is empty!')
def init(env, options): """Subcommand: init -- create the base structure of an Acrylamid blog or restore individual files and folders. If the destination directory is empty, it will create a new blog. If the destination directory is not empty it won't overwrite anything unless you supply -f, --force to re-initialize the whole theme. If you need to restore a single file, run $ cd blog && acrylamid init theme/main.html """ if not options.engine: try: import jinja2 options.engine = "jinja2" except ImportError: options.engine = "mako" theme, files = rollout(options.theme, options.engine) # if destination is part of theme, restore it! for src, dest in resolve(options, theme, files): if dest.endswith(options.dest): if ( not isfile(options.dest) or options.overwrite or raw_input("re-initialize %s ? [yn]: " % options.dest) == "y" ): write(src, options.dest) log.info("re-initialized " + options.dest) return if isfile(options.dest): raise AcrylamidException("%s already exists!" % options.dest) if isdir(options.dest) and len(os.listdir(options.dest)) > 0 and not options.overwrite: if raw_input("Destination directory not empty! Continue? [yn]: ") != "y": sys.exit(1) for src, dest in resolve(options, theme, files): if not isdir(dirname(dest)): os.makedirs(dirname(dest)) if options.overwrite or not exists(dest): write(src, dest) event.create(dest) else: event.skip(dest) log.info("Created your fresh new blog at %r. Enjoy!", options.dest)
def create(directory, path): """A shortcut for check if exists and shutil.copy to.""" dest = join(root, directory, basename(path)) if not isfile(dest) or options.overwrite == True: try: shutil.copy(path, dest) log.info('create %s', dest) except IOError as e: log.fatal(unicode(e)) else: log.info('skip %s already exists', dest)
def parse(content): failed = [] for method in (wp, rss20, atom): try: res = method(content) return next(res), res except ImportError: log.info('notice BeautifulSoup is required for WordPress import') except InvalidSource as e: failed.append(e.args[0]) else: raise AcrylamidException('unable to parse source')
def autocompile(ws, conf, env): """Subcommand: autocompile -- automatically re-compiles when something in content-dir has changed and parallel serving files.""" mtime = -1 cmtime = getmtime('conf.py') # config content_extension originally defined as string, not a list exts = conf.get('content_extension', ['.txt', '.rst', '.md']) if isinstance(exts, string_types): whitelist = (exts, ) else: whitelist = tuple(exts) while True: ws.wait = True ntime = max( max( getmtime(e) for e in readers.filelist(conf['content_dir'], conf['content_ignore']) if e.endswith(whitelist)), max( getmtime(p) for p in chain([ f for theme in conf['theme'] for f in readers.filelist(theme, conf['theme_ignore']) ], readers.filelist(conf['static'], conf['static_ignore'])))) if mtime != ntime: try: compile(conf, env) except (SystemExit, KeyboardInterrupt): raise except Exception: log.exception("uncaught exception during auto-compilation") else: conf = load(env.options.conf) env = Environment.new(env) event.reset() mtime = ntime ws.wait = False if cmtime != getmtime('conf.py'): log.info(' * Restarting due to change in conf.py') # Kill the webserver ws.shutdown() # Restart acrylamid os.execvp(sys.argv[0], sys.argv) time.sleep(1)
def autocompile(ws, conf, env): """Subcommand: autocompile -- automatically re-compiles when something in content-dir has changed and parallel serving files.""" mtime = -1 cmtime = getmtime('conf.py') # config content_extension originally defined as string, not a list exts = conf.get('content_extension',['.txt', '.rst', '.md']) if isinstance(exts, string_types): whitelist = (exts,) else: whitelist = tuple(exts) while True: ws.wait = True ntime = max( max(getmtime(e) for e in readers.filelist( conf['content_dir'], conf['content_ignore']) if e.endswith(whitelist)), max(getmtime(p) for p in chain( readers.filelist(conf['theme'], conf['theme_ignore']), readers.filelist(conf['static'], conf['static_ignore'])))) if mtime != ntime: try: compile(conf, env) except (SystemExit, KeyboardInterrupt): raise except Exception: log.exception("uncaught exception during auto-compilation") else: conf = load(env.options.conf) env = Environment.new(env) event.reset() mtime = ntime ws.wait = False if cmtime != getmtime('conf.py'): log.info(' * Restarting due to change in conf.py') # Kill the webserver ws.shutdown() # Restart acrylamid os.execvp(sys.argv[0], sys.argv) time.sleep(1)
def autocompile(ws, conf, env): """Subcommand: autocompile -- automatically re-compiles when something in content-dir has changed and parallel serving files.""" mtime = -1 cmtime = getmtime("conf.py") while True: ws.wait = True ntime = max( max(getmtime(e) for e in readers.filelist(conf["content_dir"], conf["content_ignore"]) if istext(e)), max( getmtime(p) for p in chain( readers.filelist(conf["theme"], conf["theme_ignore"]), readers.filelist(conf["static"], conf["static_ignore"]), ) ), ) if mtime != ntime: try: compile(conf, env) except (SystemExit, KeyboardInterrupt): raise except Exception: log.exception("uncaught exception during auto-compilation") else: conf = load(env.options.conf) env = Environment.new(env) event.reset() mtime = ntime ws.wait = False if cmtime != getmtime("conf.py"): log.info(" * Restarting due to change in conf.py") # Kill the webserver ws.shutdown() # Restart acrylamid os.execvp(sys.argv[0], sys.argv) time.sleep(1)
def run(conf, env, options): """Subcommand: deploy -- run the shell command specified in DEPLOYMENT[task] using Popen. Each string value from :doc:`conf.py` is added to the execution environment. Every argument after ``acrylamid deploy task ARG1 ARG2`` is appended to cmd.""" if options.task is None: for task in conf.get('deployment', {}).keys(): print >> sys.stdout, task sys.exit(0) task, args = options.task, options.args cmd = conf.get('deployment', {}).get(task, None) if not cmd: raise AcrylamidException('no tasks named %r in conf.py' % task) # apply ARG1 ARG2 ... and -v --long-args to the command, e.g.: # $> acrylamid deploy task arg1 -b --foo cmd += ' ' + ' '.join(args) if '%s' in cmd: log.warn("'%s' syntax is deprecated, use $OUTPUT_DIR variable.") cmd = cmd.replace('%s', '$OUTPUT_DIR') env = os.environ env.update( dict([(k.upper(), v) for k, v in conf.items() if isinstance(v, basestring)])) log.info('execute %s', cmd) p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: output = p.stdout.read(1) if output == '' and p.poll() != None: break if output != '': sys.stdout.write(output) sys.stdout.flush()
def init(self, cache_dir=None, mode=0600): """Initialize cache object by creating the cache_dir if non-existent, read all available cache objects and restore memoized key/values. :param cache_dir: the directory where cache files are stored. :param mode: the file mode wanted for the cache files, default 0600 """ if cache_dir: self.cache_dir = cache_dir if mode: self.mode = mode if not exists(self.cache_dir): try: os.mkdir(self.cache_dir, 0700) except OSError: raise AcrylamidException("could not create directory '%s'" % self.cache_dir) # get all cache objects for path in self._list_dir(): try: with io.open(path, 'rb') as fp: self.objects[path] = set(pickle.load(fp).keys()) except pickle.PickleError: os.remove(path) except (AttributeError, EOFError): # this may happen after a refactor log.info('notice invalid cache objects') for obj in self._list_dir(): cache.remove(obj) break except IOError: continue # load memorized items try: with io.open(join(cache.cache_dir, 'info'), 'rb') as fp: cache.memoize.update(pickle.load(fp)) except (IOError, pickle.PickleError): pass
def run(conf, env, options): """Subcommand: deploy -- run the shell command specified in DEPLOYMENT[task] using Popen. Each string value from :doc:`conf.py` is added to the execution environment. Every argument after ``acrylamid deploy task ARG1 ARG2`` is appended to cmd.""" if options.list: for task in iterkeys(conf.get('deployment', {})): print(task) sys.exit(0) task, args = options.task or 'default', options.args cmd = conf.get('deployment', {}).get(task, None) if not cmd: raise AcrylamidException('no tasks named %r in conf.py' % task) # apply ARG1 ARG2 ... and -v --long-args to the command, e.g.: # $> acrylamid deploy task arg1 -b --foo cmd += ' ' + ' '.join(args) enc = sys.getfilesystemencoding() env = os.environ env.update( dict([(k.upper(), v.encode(enc, 'replace') if PY2K else v) for k, v in iteritems(conf) if isinstance(v, string_types)])) log.info('execute %s', cmd) p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: output = p.stdout.read(1) if output == b'' and p.poll() != None: break if output != b'': sys.stdout.write(output.decode(enc)) sys.stdout.flush()
def init(self, cache_dir=None, mode=0600): """Initialize cache object by creating the cache_dir if non-existent, read all available cache objects and restore memoized key/values. :param cache_dir: the directory where cache files are stored. :param mode: the file mode wanted for the cache files, default 0600 """ if cache_dir: self.cache_dir = cache_dir if mode: self.mode = mode if not exists(self.cache_dir): try: os.mkdir(self.cache_dir, 0700) except OSError: raise AcrylamidException("could not create directory '%s'" % self.cache_dir) # get all cache objects for path in self._list_dir(): try: with io.open(path, 'rb') as fp: self.objects[path] = set(pickle.load(fp).keys()) except pickle.PickleError: os.remove(path) except (AttributeError, EOFError): # this may happen after a refactor log.info('notice stale cache objects') for obj in self._list_dir(): cache.remove(obj) break except IOError: continue # load memorized items try: with io.open(join(cache.cache_dir, 'info'), 'rb') as fp: cache.memoize.update(pickle.load(fp)) except (IOError, pickle.PickleError): pass
def normalize(conf): # metastyle has been removed if "metastyle" in conf: log.info("notice METASTYLE is no longer needed to determine the metadata format " + "and can be removed.") # deprecated since 0.8 if isinstance(conf["static"], list): conf["static"] = conf["static"][0] log.warn("multiple static directories has been deprecated, " + "Acrylamid continues with '%s'.", conf["static"]) # deprecated since 0.8 for fx in "Jinja2", "Mako": try: conf["static_filter"].remove(fx) except ValueError: pass else: log.warn("%s asset filter has been renamed to `Template` and is " "included by default.", fx) if not isinstance(conf["theme"], list): conf["theme"] = [conf["theme"]] for i, path in enumerate(conf["theme"]): if not path.endswith("/"): conf["theme"][i] = path + "/" for key in "content_dir", "static", "output_dir": if conf[key] is not None and not conf[key].endswith("/"): conf[key] += "/" for key in "views_dir", "filters_dir": if isinstance(conf[key], compat.string_types): conf[key] = [conf[key]] return conf
def update(self, path, ctime=None): if ctime: log.info("update [%.2fs] %s", ctime, path) else: log.info("update %s", path)
def initialize(conf, env): """Initializes Jinja2 environment, prepares locale and configure some minor things. Filter and View are inited with conf and env, a data dict is returned. """ # initialize cache, optional to cache_dir cache.init(conf.get("cache_dir")) env["version"] = type("Version", (str,), dict(zip(["major", "minor"], LooseVersion(dist.version).version[:2])))( dist.version ) # crawl through CHANGES.md and stop on breaking changes if history.breaks(env, cache.emptyrun): cache.shutdown() print "Detected version upgrade that might break your configuration. Run" print "Acrylamid a second time to get rid of this message and premature exit." raise SystemExit # register hooks if env.options.parser.startswith(("co", "gen", "auto", "aco")): hooks.initialize(conf, env) # set up templating environment env.engine = import_object(conf["engine"])() env.engine.init(conf["theme"], cache.cache_dir) env.engine.register("safeslug", helpers.safeslug) env.engine.register("tagify", lambda x: x) # try language set in LANG, if set correctly use it try: locale.setlocale(locale.LC_ALL, str(conf.get("lang", ""))) except (locale.Error, TypeError): # try if LANG is an alias try: locale.setlocale(locale.LC_ALL, locale.locale_alias[str(conf.get("lang", "")).lower()]) except (locale.Error, KeyError): # LANG is not an alias, so we use system's default try: locale.setlocale(locale.LC_ALL, "") except locale.Error: pass # hope this makes Travis happy log.info("notice your OS does not support %s, fallback to %s", conf.get("lang", ""), locale.getlocale()[0]) if locale.getlocale()[0] is not None: conf["lang"] = locale.getlocale()[0][:2] else: # getlocale() is (None, None) aka 'C' conf["lang"] = "en" if "www_root" not in conf: log.warn("no `www_root` specified, using localhost:8000") conf["www_root"] = "http://localhost:8000/" # figure out timezone and set offset, more verbose for 2.6 compatibility td = datetime.now() - datetime.utcnow() offset = round(total_seconds(td) / 3600.0) conf["tzinfo"] = readers.Timezone(offset) # determine http(s), host and path env["protocol"], env["netloc"], env["path"], x, y = urlsplit(conf["www_root"]) # take off the trailing slash for www_root and path conf["www_root"] = conf["www_root"].rstrip("/") env["path"] = env["path"].rstrip("/") if env["path"]: conf["output_dir"] = conf["output_dir"] + env["path"] lazy.enable() filters.initialize(conf["filters_dir"][:], conf, env) lazy.disable() # this has weird side effects with jinja2, so disabled after filters views.initialize(conf["views_dir"][:], conf, env) env.views = views.Views(view for view in views.get_views()) entryfmt, pagefmt = "/:year/:slug/", "/:slug/" for view in views.get_views(): if view.name == "entry": entryfmt = view.path if view.name == "page": pagefmt = view.path conf.setdefault("entry_permalink", entryfmt) conf.setdefault("page_permalink", pagefmt) return {"conf": conf, "env": env}
def compile(conf, env): """The compilation process.""" if env.options.force: cache.clear(conf.get('cache_dir')) # time measurement ctime = time.time() # populate env and corrects some conf things data = initialize(conf, env) # load pages/entries and store them in env rv = dict(zip(['entrylist', 'pages', 'translations', 'drafts'], map(HashableList, readers.load(conf)))) entrylist, pages = rv['entrylist'], rv['pages'] translations, drafts = rv['translations'], rv['drafts'] # load references refs.load(entrylist, pages, translations, drafts) data.update(rv) env.globals.update(rv) # here we store all found filter and their aliases ns = defaultdict(set) # get available filter list, something like with obj.get-function # list = [<class head_offset.Headoffset at 0x1014882c0>, <class html.HTML at 0x101488328>,...] aflist = filters.get_filters() # ... and get all configured views _views = views.get_views() # filters found in all entries, views and conf.py (skip translations, has no items) found = sum((x.filters for x in chain(entrylist, pages, drafts, _views, [conf])), []) for val in found: # first we for `no` and get the function name and arguments f = val[2:] if val.startswith('no') else val fname, fargs = f.split('+')[:1][0], f.split('+')[1:] try: # initialize the filter with its function name and arguments fx = aflist[fname](conf, env, val, *fargs) if val.startswith('no'): fx = filters.disable(fx) except ValueError: try: fx = aflist[val.split('+')[:1][0]](conf, env, val, *fargs) except ValueError: raise AcrylamidException('no such filter: %s' % val) ns[fx].add(val) for entry in chain(entrylist, pages, drafts): for v in _views: # a list that sorts out conflicting and duplicated filters flst = filters.FilterList() # filters found in this specific entry plus views and conf.py found = entry.filters + v.filters + data['conf']['filters'] for fn in found: fx, _ = next((k for k in ns.iteritems() if fn in k[1])) if fx not in flst: flst.append(fx) # sort them ascending because we will pop within filters.add entry.filters.add(sorted(flst, key=lambda k: (-k.priority, k.name)), context=v) # lets offer a last break to populate tags and such for v in _views: env = v.context(conf, env, data) # now teh real thing! for v in _views: for entry in chain(entrylist, pages, translations, drafts): entry.context = v for var in 'entrylist', 'pages', 'translations', 'drafts': data[var] = HashableList(filter(v.condition, locals()[var])) \ if v.condition else locals()[var] tt = time.time() for buf, path in v.generate(conf, env, data): try: helpers.mkfile(buf, path, time.time()-tt, **env.options.__dict__) finally: buf.close() tt = time.time() # copy modified/missing assets to output assets.compile(conf, env) # save conf/environment hash and new/changed/unchanged references helpers.memoize('Configuration', hash(conf)) helpers.memoize('Environment', hash(env)) refs.save() # remove abandoned cache files cache.shutdown() # print a short summary log.info('%i new, %i updated, %i skipped [%.2fs]', event.count('create'), event.count('update'), event.count('identical') + event.count('skip'), time.time() - ctime)
def compile(conf, env): """The compilation process.""" hooks.initialize(conf, env) hooks.run(conf, env, 'pre') if env.options.force: cache.clear(conf.get('cache_dir')) # time measurement ctime = time.time() # populate env and corrects some conf things data = initialize(conf, env) # load pages/entries and store them in env rv = dict( zip(['entrylist', 'pages', 'translations', 'drafts'], map(HashableList, readers.load(conf)))) entrylist, pages = rv['entrylist'], rv['pages'] translations, drafts = rv['translations'], rv['drafts'] # load references refs.load(entrylist, pages, translations, drafts) data.update(rv) env.globals.update(rv) # here we store all found filter and their aliases ns = defaultdict(set) # [<class head_offset.Headoffset at 0x1014882c0>, <class html.HTML at 0x101488328>,...] aflist = filters.get_filters() # ... and get all configured views _views = views.get_views() # filters found in all entries, views and conf.py (skip translations, has no items) found = sum((x.filters for x in chain(entrylist, pages, drafts, _views, [conf])), []) for val in found: # first we for `no` and get the function name and arguments f = val[2:] if val.startswith('no') else val fname, fargs = f.split('+')[:1][0], f.split('+')[1:] try: # initialize the filter with its function name and arguments fx = aflist[fname](conf, env, val, *fargs) if val.startswith('no'): fx = filters.disable(fx) except ValueError: try: fx = aflist[val.split('+')[:1][0]](conf, env, val, *fargs) except ValueError: raise AcrylamidException('no such filter: %s' % val) ns[fx].add(val) # include actual used filters to trigger modified state env.filters = HashableList(iterkeys(ns)) for entry in chain(entrylist, pages, drafts): for v in _views: # a list that sorts out conflicting and duplicated filters flst = filters.FilterList() # filters found in this specific entry plus views and conf.py found = entry.filters + v.filters + data['conf']['filters'] for fn in found: fx, _ = next((k for k in iteritems(ns) if fn in k[1])) if fx not in flst: flst.append(fx) # sort them ascending because we will pop within filters.add entry.filters.add(sorted(flst, key=lambda k: (-k.priority, k.name)), context=v) # lets offer a last break to populate tags and such for v in _views: env = v.context(conf, env, data) # now teh real thing! for v in _views: for entry in chain(entrylist, pages, translations, drafts): entry.context = v for var in 'entrylist', 'pages', 'translations', 'drafts': data[var] = HashableList(filter(v.condition, locals()[var])) \ if v.condition else locals()[var] tt = time.time() for buf, path in v.generate(conf, env, data): try: helpers.mkfile(buf, path, time.time() - tt, ns=v.name, force=env.options.force, dryrun=env.options.dryrun) except UnicodeError: log.exception(path) finally: buf.close() tt = time.time() # copy modified/missing assets to output assets.compile(conf, env) # wait for unfinished hooks hooks.shutdown() # run post hooks (blocks) hooks.run(conf, env, 'post') # save conf/environment hash and new/changed/unchanged references helpers.memoize('Configuration', hash(conf)) helpers.memoize('Environment', hash(env)) refs.save() # remove abandoned cache files cache.shutdown() # print a short summary log.info('%i new, %i updated, %i skipped [%.2fs]', event.count('create'), event.count('update'), event.count('identical') + event.count('skip'), time.time() - ctime)
def new(conf, env, title, prompt=True): """Subcommand: new -- create a new blog entry the easy way. Either run ``acrylamid new My fresh new Entry`` or interactively via ``acrylamid new`` and the file will be created using the preferred permalink format.""" # we need the actual defaults values initialize(conf, env) fd, tmp = tempfile.mkstemp(suffix='.txt', dir='.cache/') editor = os.getenv('VISUAL') if os.getenv('VISUAL') else os.getenv( 'EDITOR') if not title: title = raw_input("Entry's title: ") title = safe(title) with io.open(fd, 'w') as f: f.write(u'---\n') f.write(u'title: %s\n' % title) f.write(u'date: %s\n' % datetime.now().strftime(conf['date_format'])) f.write(u'---\n\n') entry = readers.Entry(tmp, conf) p = join(conf['content_dir'], dirname(entry.permalink)[1:]) try: os.makedirs(p.rsplit('/', 1)[0]) except OSError: pass filepath = p + '.txt' if isfile(filepath): raise AcrylamidException('Entry already exists %r' % filepath) shutil.move(tmp, filepath) event.create(filepath) if datetime.now().hour == 23 and datetime.now().minute > 45: log.info( "notice consider editing entry.date-day after you passed mignight!" ) if not prompt: return try: if editor: retcode = subprocess.call([editor, filepath]) elif sys.platform == 'darwin': retcode = subprocess.call(['open', filepath]) else: retcode = subprocess.call(['xdg-open', filepath]) except OSError: raise AcrylamidException('Could not launch an editor') # XXX process detaches... m( if retcode < 0: raise AcrylamidException('Child was terminated by signal %i' % -retcode) if os.stat(filepath)[6] == 0: raise AcrylamidException('File is empty!')
def run(conf, env, options): """Subcommand: new -- create a new blog entry the easy way. Either run ``acrylamid new My fresh new Entry`` or interactively via ``acrylamid new`` and the file will be created using the preferred permalink format.""" # we need the actual default values commands.initialize(conf, env) # config content_extension originally defined as string, not a list extlist = conf.get('content_extension', ['.txt']) if isinstance(extlist, string_types): ext = extlist else: ext = extlist[0] fd, tmp = tempfile.mkstemp(suffix=ext, dir='.cache/') editor = os.getenv('VISUAL') if os.getenv('VISUAL') else os.getenv( 'EDITOR') tt = formats.get(ext, yaml) if options.title: title = u(' '.join(options.title)) else: title = u(input("Entry's title: ")) with io.open(fd, 'w', encoding='utf-8') as f: f.write(tt(title, datetime.now().strftime(conf['date_format']))) entry = readers.Entry(tmp, conf) p = join(conf['content_dir'], splitext(entry.permalink.strip('/'))[0]) try: os.makedirs(p.rsplit('/', 1)[0]) except OSError: pass filepath = p + ext if isfile(filepath): raise AcrylamidException('Entry already exists %r' % filepath) shutil.move(tmp, filepath) event.create('new', filepath) if datetime.now().hour == 23 and datetime.now().minute > 45: log.info( "notice don't forget to update entry.date-day after mignight!") if log.level() >= log.WARN: return try: if editor: retcode = subprocess.call(shlex.split(editor) + [filepath]) elif sys.platform == 'darwin': retcode = subprocess.call(['open', filepath]) else: retcode = subprocess.call(['xdg-open', filepath]) except OSError: raise AcrylamidException('Could not launch an editor') # XXX process detaches... m( if retcode < 0: raise AcrylamidException('Child was terminated by signal %i' % -retcode) if os.stat(filepath)[6] == 0: raise AcrylamidException('File is empty!')
def compile(conf, env, force=False, **options): """The compilation process. Current API: #. when we require context #. when we called an event New API: #. before we start with view Initialization #. after we initialized views #. before we require context #. after we required context #. before we template #. before we write a file #. when we called an event #. when we finish """ # time measurement ctime = time.time() # populate env and corrects some conf things request = initialize(conf, env) # load pages/entries and store them in env entrylist, pages = readers.load(conf) env.globals['entrylist'] = entrylist env.globals['pages'] = pages # XXX translations should be moved out of core env.globals['translations'] = translations = [] if force: # acrylamid compile -f cache.clear() # here we store all found filter and their aliases ns = defaultdict(set) # get available filter list, something like with obj.get-function # list = [<class head_offset.Headoffset at 0x1014882c0>, <class html.HTML at 0x101488328>,...] aflist = filters.get_filters() # ... and get all configured views _views = views.get_views() # filters found in all entries, views and conf.py found = sum((x.filters for x in entrylist+pages+_views), []) + request['conf']['filters'] for val in found: # first we for `no` and get the function name and arguments f = val[2:] if val.startswith('no') else val fname, fargs = f.split('+')[:1][0], f.split('+')[1:] try: # initialize the filter with its function name and arguments fx = aflist[fname](conf, env, val, *fargs) if val.startswith('no'): fx = filters.disable(fx) except ValueError: try: fx = aflist[val.split('+')[:1][0]](conf, env, val, *fargs) except ValueError: raise AcrylamidException('no such filter: %s' % val) ns[fx].add(val) for entry in entrylist + pages: for v in _views: # a list that sorts out conflicting and duplicated filters flst = filters.FilterList() # filters found in this specific entry plus views and conf.py found = entry.filters + v.filters + request['conf']['filters'] for fn in found: fx, _ = next((k for k in ns.iteritems() if fn in k[1])) if fx not in flst: flst.append(fx) # sort them ascending because we will pop within filters.add entry.filters.add(sorted(flst, key=lambda k: (-k.priority, k.name)), context=v.__class__.__name__) # lets offer a last break to populate tags or so # XXX this API component needs a review for v in _views: env = v.context(env, {'entrylist': entrylist, 'pages': pages, 'translations': translations}) # now teh real thing! for v in _views: # XXX the entry should automatically determine its caller (using # some sys magic to recursively check wether the calling class is # derieved from `View`.) for entry in entrylist + pages + translations: entry.context = v.__class__.__name__ request['pages'], request['translations'] = pages, translations request['entrylist'] = filter(v.condition, entrylist) tt = time.time() for html, path in v.generate(request): helpers.mkfile(html, path, time.time()-tt, **options) tt = time.time() # remove abandoned cache files cache.shutdown() # print a short summary log.info('%i new, %i updated, %i skipped [%.2fs]', event.count('create'), event.count('update'), event.count('identical') + event.count('skip'), time.time() - ctime)
def update(self, path, ctime=None): """:param path: path\n:param ctime: computing time""" if ctime: log.info("update [%.2fs] %s", ctime, path) else: log.info("update %s", path)
def remove(self, path): """:param path: path""" log.info("remove %s", path)
def init(env, options): """Subcommand: init -- creates the base structure of an Acrylamid blog or restores individual files.""" root = options.dest if not options.engine: try: import jinja2 options.engine = 'jinja2' except ImportError: options.engine = 'mako' def create(directory, path): """A shortcut for check if exists and shutil.copy to.""" dest = join(root, directory, basename(path)) if not isfile(dest) or options.overwrite == True: try: shutil.copy(path, dest) log.info('create %s', dest) except IOError as e: log.fatal(unicode(e)) else: log.info('skip %s already exists', dest) default = defaults.conf default['output_dir'] = default['output_dir'].rstrip('/') default['content_dir'] = default['content_dir'].rstrip('/') default['layout_dir'] = default['layout_dir'].rstrip('/') dirs = [ '%(content_dir)s/', '%(layout_dir)s/', '%(output_dir)s/', '.cache/' ] files = [ p % { 'engine': options.engine, 'theme': options.theme } for p in [ '%(engine)s/%(theme)s/base.html', '%(engine)s/%(theme)s/main.html', '%(engine)s/%(theme)s/entry.html', '%(engine)s/%(theme)s/articles.html', '%(engine)s/rss.xml', '%(engine)s/atom.xml', 'misc/%(theme)s/style.css', 'misc/sample-entry.txt' ] ] files = [join(dirname(defaults.__file__), path) for path in files] # restore a given file from defaults # XXX restore folders, too if filter(lambda p: basename(p) == basename(root), files): for path in files: if basename(path) == basename(root): break if isfile(root) and (options.overwrite or raw_input( 're-initialize %r? [yn]: ' % root) == 'y'): shutil.copy(path, root) log.info('re-initialized %s' % root) else: shutil.copy(path, root) log.info('create %s' % root) sys.exit(0) # re-initialize conf.py if root == 'conf.py': if options.overwrite or raw_input( 're-initialize %r? [yn]: ' % root) == 'y': with io.open('conf.py', 'w') as fp: fp.write(confstring % {'engine': options.engine}) log.info('re-initialized %s' % root) sys.exit(0) # YO DAWG I HERD U LIEK BLOGS SO WE PUT A BLOG IN UR BLOG -- ask user before if isfile('conf.py') and not options.overwrite: q = raw_input("Create blog inside a blog? [yn]: ") if q != 'y': sys.exit(1) if exists(root) and len(os.listdir(root)) > 0 and not options.overwrite: if raw_input( "Destination directory not empty! Continue? [yn]: ") != 'y': sys.exit(1) if root != '.' and not exists(root): os.mkdir(root) for directory in dirs: directory = join(root, directory % default) if exists(directory) and not isdir(directory): log.critical('Unable to create %r. Please remove this file', directory) sys.exit(1) elif not exists(directory): os.mkdir(directory) with io.open(join(root, 'conf.py'), 'w') as fp: fp.write(confstring % {'engine': options.engine}) log.info('create %s', join(root, 'conf.py')) for path in files: if path.endswith(('.html', '.xml')): create(default['layout_dir'], path) elif path.endswith('.txt'): create(default['content_dir'], path) else: create(default['output_dir'], path) log.info('Created your fresh new blog at %r. Enjoy!', root)
def compile(conf, env, force=False, **options): """The compilation process. Current API: #. when we require context #. when we called an event New API: #. before we start with view Initialization #. after we initialized views #. before we require context #. after we required context #. before we template #. before we write a file #. when we called an event #. when we finish """ # time measurement ctime = time.time() # populate env and corrects some conf things request = initialize(conf, env) # load pages/entries and store them in env entrylist, pages = readers.load(conf) env.globals['entrylist'] = entrylist env.globals['pages'] = pages # XXX translations should be moved out of core env.globals['translations'] = translations = [] if force: # acrylamid compile -f cache.clear() # here we store all found filter and their aliases ns = defaultdict(set) # get available filter list, something like with obj.get-function # list = [<class head_offset.Headoffset at 0x1014882c0>, <class html.HTML at 0x101488328>,...] aflist = filters.get_filters() # ... and get all configured views _views = views.get_views() # filters found in all entries, views and conf.py found = sum( (x.filters for x in entrylist + pages + _views), []) + request['conf']['filters'] for val in found: # first we for `no` and get the function name and arguments f = val[2:] if val.startswith('no') else val fname, fargs = f.split('+')[:1][0], f.split('+')[1:] try: # initialize the filter with its function name and arguments fx = aflist[fname](conf, env, val, *fargs) if val.startswith('no'): fx = filters.disable(fx) except ValueError: try: fx = aflist[val.split('+')[:1][0]](conf, env, val, *fargs) except ValueError: raise AcrylamidException('no such filter: %s' % val) ns[fx].add(val) for entry in entrylist + pages: for v in _views: # a list that sorts out conflicting and duplicated filters flst = filters.FilterList() # filters found in this specific entry plus views and conf.py found = entry.filters + v.filters + request['conf']['filters'] for fn in found: fx, _ = next((k for k in ns.iteritems() if fn in k[1])) if fx not in flst: flst.append(fx) # sort them ascending because we will pop within filters.add entry.filters.add(sorted(flst, key=lambda k: (-k.priority, k.name)), context=v) # lets offer a last break to populate tags or so # XXX this API component needs a review for v in _views: env = v.context(env, { 'entrylist': entrylist, 'pages': pages, 'translations': translations }) # now teh real thing! for v in _views: # XXX the entry should automatically determine its caller (using # some sys magic to recursively check wether the calling class is # derieved from `View`.) for entry in entrylist + pages + translations: entry.context = v request['pages'], request['translations'] = pages, translations request['entrylist'] = filter(v.condition, entrylist) tt = time.time() for html, path in v.generate(request): helpers.mkfile(html, path, time.time() - tt, **options) tt = time.time() # remove abandoned cache files cache.shutdown() # print a short summary log.info('%i new, %i updated, %i skipped [%.2fs]', event.count('create'), event.count('update'), event.count('identical') + event.count('skip'), time.time() - ctime)
def initialize(conf, env): """Initializes Jinja2 environment, prepares locale and configure some minor things. Filter and View are inited with conf and env, a request dict is returned. """ # initialize cache, optional to cache_dir cache.init(conf.get('cache_dir', None)) # set up templating environment env.engine = utils.import_object(conf['engine'])() env.engine.init(conf['layout_dir'], cache.cache_dir) env.engine.register('safeslug', helpers.safeslug) env.engine.register('tagify', lambda x: x) # try language set in LANG, if set correctly use it try: locale.setlocale(locale.LC_ALL, str(conf.get('lang', ''))) except (locale.Error, TypeError): # try if LANG is an alias try: locale.setlocale( locale.LC_ALL, locale.locale_alias[str(conf.get('lang', '')).lower()]) except (locale.Error, KeyError): # LANG is not an alias, so we use system's default try: locale.setlocale(locale.LC_ALL, '') except locale.Error: pass # hope this makes Travis happy log.info('notice your OS does not support %s, fallback to %s', conf.get('lang', ''), locale.getlocale()[0]) if locale.getlocale()[0] is not None: conf['lang'] = locale.getlocale()[0][:2] else: # getlocale() is (None, None) aka 'C' conf['lang'] = 'en' if 'www_root' not in conf: log.warn('no `www_root` specified, using localhost:8000') conf['www_root'] = 'http://localhost:8000/' # figure out timezone and set offset, more verbose for 2.6 compatibility td = (datetime.now() - datetime.utcnow()) total_seconds = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 offset = round(total_seconds / 3600.0) conf['tzinfo'] = readers.Timezone(offset) # determine http(s), host and path env['protocol'], env['netloc'], env['path'], x, y = urlsplit( conf['www_root']) # take off the trailing slash for www_root and path conf['www_root'] = conf['www_root'].rstrip('/') env['path'] = env['path'].rstrip('/') # check if encoding is available try: codecs.lookup(conf['encoding']) except LookupError: raise AcrylamidException('no such encoding available: %r' % conf['encoding']) # prepare, import and initialize filters and views if isinstance(conf['filters_dir'], basestring): conf['filters_dir'] = [ conf['filters_dir'], ] if isinstance(conf['views_dir'], basestring): conf['views_dir'] = [ conf['views_dir'], ] lazy.enable() filters.initialize(conf["filters_dir"], conf, env, exclude=conf["filters_ignore"], include=conf["filters_include"]) lazy.disable( ) # this has weird side effects with jinja2, so disabled after filters views.initialize(conf["views_dir"], conf, env) env['views'] = dict([(v.view, v) for v in views.get_views()]) entryfmt, pagefmt = '/:year/:slug/', '/:slug/' for view in views.get_views(): if view.view == 'entry': entryfmt = view.path if view.view == 'page': pagefmt = view.path conf.setdefault('entry_permalink', entryfmt) conf.setdefault('page_permalink', pagefmt) return {'conf': conf, 'env': env}
def remove(self, path): log.info("remove %s", path)
def Acryl(): """The main function that dispatches the CLI. We use :class:`AcrylFormatter` as custom help formatter that ommits the useless list of available subcommands and their aliases. All flags from acrylamid --help are also available in subcommands altough not explicitely printed in their help.""" parser = argparse.ArgumentParser( parents=[], formatter_class=AcrylFormatter ) parser.add_argument("-v", "--verbose", action="store_const", dest="verbosity", help="more verbose", const=log.SKIP, default=log.INFO) parser.add_argument("-q", "--quiet", action="store_const", dest="verbosity", help="less verbose", const=log.WARN) parser.add_argument("-C", "--no-color", action="store_false", dest="colors", help="disable color", default=True) parser.add_argument("--version", action="version", version=colors.blue('acrylamid ') + __version__) subparsers = parser.add_subparsers(dest="parser") # a repeat yourself of default arguments but not visible on subcommand --help default = argparse.ArgumentParser(add_help=False) default.add_argument("-v", "--verbose", action="store_const", dest="verbosity", help=argparse.SUPPRESS, const=log.SKIP, default=log.INFO) default.add_argument("-q", "--quiet", action="store_const", dest="verbosity", help=argparse.SUPPRESS, const=log.WARN) default.add_argument("-C", "--no-color", action="store_false", dest="colors", help=argparse.SUPPRESS, default=True) # --- gen params --- # generate = subparsers.add_parser('compile', help='compile blog', parents=[default]) generate.add_argument("-f", "--force", action="store_true", dest="force", help="clear cache before compilation", default=False) generate.add_argument("-n", "--dry-run", dest="dryrun", action='store_true', help="show what would have been compiled", default=False) generate.add_argument("-i", "--ignore", dest="ignore", action="store_true", help="ignore critical errors", default=False) # --- webserver params --- # view = subparsers.add_parser('view', help="fire up built-in webserver", parents=[default]) view.add_argument("-p", "--port", dest="port", type=int, default=8000, help="webserver port") # --- aco params --- # autocompile = subparsers.add_parser('autocompile', help="automatic compilation and serving", parents=[default]) autocompile.add_argument("-f", "--force", action="store_true", dest="force", help="clear cache before compilation", default=False) autocompile.add_argument("-n", "--dry-run", dest="dryrun", action='store_true', help="show what would have been compiled", default=False) autocompile.add_argument("-i", "--ignore", dest="ignore", action="store_true", help="ignore critical errors", default=False) autocompile.add_argument("-p", "--port", dest="port", type=int, default=8000, help="webserver port") for alias in ('co', 'gen', 'generate'): subparsers._name_parser_map[alias] = generate for alias in ('serve', 'srv'): subparsers._name_parser_map[alias] = view subparsers._name_parser_map['aco'] = autocompile new = subparsers.add_parser('new', help="create a new entry", parents=[default], epilog=("Takes all leading [args] as title or prompt if none given. creates " "a new entry based on your PERMALINK_FORMAT and opens it with your " "favourite $EDITOR.")) new.add_argument("title", nargs="*", default='') # initialize other tasks tasks.initialize(subparsers, default) # parse args options = parser.parse_args() # initialize colored logger log.init('acrylamid', level=options.verbosity, colors=options.colors) env = Struct({'version': __version__, 'author': __author__, 'url': __url__}) env['options'] = options env['globals'] = Struct() # -- init -- # # TODO: acrylamid init --layout_dir=somedir to overwrite defaults if options.parser in ('init', ): tasks.collected[options.parser](env, options) sys.exit(0) # -- teh real thing -- # conf = Struct(defaults.conf) try: ns = dict([(k.upper(), v) for k, v in defaults.conf.iteritems()]) os.chdir(os.path.dirname(find('conf.py', os.getcwd()))) execfile('conf.py', ns) conf.update(dict([(k.lower(), ns[k]) for k in ns if k.upper() == k])) except IOError: log.critical('no conf.py found. Try "acrylamid init".') sys.exit(1) except Exception as e: log.critical("%s in `conf.py`" % e.__class__.__name__) traceback.print_exc(file=sys.stdout) sys.exit(1) conf['output_dir'] = conf.get('output_dir', 'output/') conf['content_dir'] = conf.get('content_dir', 'content/') conf['layout_dir'] = conf.get('layout_dir', 'layouts/') # -- run -- # if options.parser in ('gen', 'generate', 'co', 'compile'): log.setLevel(options.verbosity) try: commands.compile(conf, env, **options.__dict__) except AcrylamidException as e: log.fatal(e.args[0]) sys.exit(1) elif options.parser in ('new', 'create'): try: commands.new(conf, env, title=' '.join(options.title), prompt=log.level()<log.WARN) except AcrylamidException as e: log.fatal(e.args[0]) sys.exit(1) elif options.parser in ('srv', 'serve', 'view'): from acrylamid.lib.httpd import Webserver ws = Webserver(options.port, conf['output_dir']); ws.start() log.info(' * Running on http://127.0.0.1:%i/' % options.port) try: while True: time.sleep(1) except (SystemExit, KeyboardInterrupt, Exception) as e: ws.kill_received = True sys.exit(0) elif options.parser in ('aco', 'autocompile'): from acrylamid.lib.httpd import Webserver # XXX compile on request _or_ use inotify/fsevent ws = Webserver(options.port, conf['output_dir']); ws.start() log.info(' * Running on http://127.0.0.1:%i/' % options.port) try: commands.autocompile(ws, conf, env, **options.__dict__) except (SystemExit, KeyboardInterrupt, Exception) as e: ws.kill_received = True log.error(e.args[0]) traceback.print_exc(file=sys.stdout) sys.exit(0) elif options.parser in tasks.collected: try: tasks.collected[options.parser](conf, env, options) except AcrylamidException as e: log.critical(e.args[0]) sys.exit(1) else: log.critical('No such command!') sys.exit(2) sys.exit(0)
def compile(conf, env, force=False, **options): # time measurement ctime = time.time() # populate env and corrects some conf things request = initialize(conf, env) if force: # acrylamid compile -f cache.clear() # list of FileEntry-objects reverse sorted by date. entrylist = sorted([FileEntry(e, conf) for e in utils.filelist(conf['content_dir'], conf.get('entries_ignore', []))], key=lambda k: k.date, reverse=True) # here we store all possible filter configurations ns = set() # get available filter list, something like with obj.get-function # list = [<class head_offset.Headoffset at 0x1014882c0>, <class html.HTML at 0x101488328>,...] aflist = filters.get_filters() # ... and get all configured views _views = views.get_views() # filters found in all entries, views and conf.py found = sum((x.filters for x in entrylist+_views), []) + request['conf']['filters'] for val in found: # first we for `no` and get the function name and arguments f = val[2:] if val.startswith('no') else val fname, fargs = f.split('+')[:1][0], f.split('+')[1:] try: # initialize the filter with its function name and arguments fx = aflist[fname](val, *fargs) if val.startswith('no'): fx.transform = lambda x, y, *z: x fx.__hash__ = lambda : 0 except ValueError: try: fx = aflist[val.split('+')[:1][0]](val, *fargs) except ValueError: raise AcrylamidException('no such filter: %s' % val) ns.add(fx) for entry in entrylist: for v in _views: # a list that sorts out conflicting and duplicated filters flst = filters.FilterList() # filters found in this specific entry plus views and conf.py found = entry.filters + v.filters + request['conf']['filters'] for fn in found: fx = filter(lambda k: fn == k.name, ns)[0] if fx not in flst: flst.append(fx) # sort them ascending because we will pop within filters.add entry.filters.add(sorted(flst, key=lambda k: (-k.priority, k.name)), context=v.__class__.__name__) # lets offer a last break to populate tags or so # XXX this API component needs a review for v in _views: env = v.context(env, {'entrylist': filter(v.condition, entrylist)}) # now teh real thing! for v in _views: # XXX the entry should automatically determine its caller (using # some sys magic to recursively check wether the calling class is # derieved from `View`.) for entry in entrylist: entry.context = v.__class__.__name__ request['entrylist'] = filter(v.condition, entrylist) tt = time.time() for html, path in v.generate(request): helpers.mkfile(html, path, time.time()-tt, **options) tt = time.time() # remove abandoned cache files cache.shutdown() log.info('Blog compiled in %.2fs' % (time.time() - ctime))
def initialize(conf, env): """Initializes Jinja2 environment, prepares locale and configure some minor things. Filter and View are inited with conf and env, a data dict is returned. """ # initialize cache, optional to cache_dir cache.init(conf.get('cache_dir')) env['version'] = type('Version', (str, ), dict(zip( ['major', 'minor'], LooseVersion(dist.version).version[:2])))(dist.version) # crawl through CHANGES.md and stop on breaking changes if history.breaks(env, cache.emptyrun): cache.shutdown() print("Detected version upgrade that might break your configuration. Run") print("Acrylamid a second time to get rid of this message and premature exit.") raise SystemExit # set up templating environment env.engine = import_object(conf['engine'])() env.engine.init(conf['theme'], cache.cache_dir) env.engine.register('safeslug', helpers.safeslug) env.engine.register('tagify', lambda x: x) # try language set in LANG, if set correctly use it try: locale.setlocale(locale.LC_ALL, str(conf.get('lang', ''))) except (locale.Error, TypeError): # try if LANG is an alias try: locale.setlocale(locale.LC_ALL, locale.locale_alias[str(conf.get('lang', '')).lower()]) except (locale.Error, KeyError): # LANG is not an alias, so we use system's default try: locale.setlocale(locale.LC_ALL, '') except locale.Error: pass # hope this makes Travis happy log.info('notice your OS does not support %s, fallback to %s', conf.get('lang', ''), locale.getlocale()[0]) if locale.getlocale()[0] is not None: conf['lang'] = locale.getlocale()[0][:2] else: # getlocale() is (None, None) aka 'C' conf['lang'] = 'en' if 'www_root' not in conf: log.warn('no `www_root` specified, using localhost:8000') conf['www_root'] = 'http://localhost:8000/' # figure out timezone and set offset, more verbose for 2.6 compatibility td = (datetime.now() - datetime.utcnow()) offset = round(total_seconds(td) / 3600.0) conf['tzinfo'] = readers.Timezone(offset) # determine http(s), host and path env['protocol'], env['netloc'], env['path'], x, y = urlsplit(conf['www_root']) # take off the trailing slash for www_root and path conf['www_root'] = conf['www_root'].rstrip('/') env['path'] = env['path'].rstrip('/') if env['path']: conf['output_dir'] = conf['output_dir'] + env['path'] lazy.enable() filters.initialize(conf["filters_dir"][:], conf, env) lazy.disable() # this has weird side effects with jinja2, so disabled after filters views.initialize(conf["views_dir"][:], conf, env) env.views = views.Views(view for view in views.get_views()) entryfmt, pagefmt = '/:year/:slug/', '/:slug/' for view in views.get_views(): if view.name == 'entry': entryfmt = view.path if view.name == 'page': pagefmt = view.path conf.setdefault('entry_permalink', entryfmt) conf.setdefault('page_permalink', pagefmt) # register webassets to theme engine, make webassets available as env.webassets assets.initialize(conf, env) return {'conf': conf, 'env': env}
def Acryl(): """The main function that dispatches the CLI. We use :class:`AcrylFormatter` as custom help formatter that ommits the useless list of available subcommands and their aliases. All flags from acrylamid --help are also available in subcommands altough not explicitely printed in their help.""" parser = argparse.ArgumentParser( parents=[], formatter_class=AcrylFormatter ) parser.add_argument("-v", "--verbose", action="store_const", dest="verbosity", help="more verbose", const=log.SKIP, default=log.INFO) parser.add_argument("-q", "--quiet", action="store_const", dest="verbosity", help="less verbose", const=log.WARN) parser.add_argument("-C", "--no-color", action="store_false", dest="colors", help="disable color", default=True) parser.add_argument("--conf", dest="conf", help="alternate conf.py", default="conf.py", metavar="/path/to/conf") parser.add_argument("--version", action="version", version=colors.blue('Acrylamid ') + dist.version) subparsers = parser.add_subparsers(dest="parser") # a repeat yourself of default arguments but not visible on subcommand --help default = argparse.ArgumentParser(add_help=False) default.add_argument("-v", "--verbose", action="store_const", dest="verbosity", help=argparse.SUPPRESS, const=log.SKIP, default=log.INFO) default.add_argument("-q", "--quiet", action="store_const", dest="verbosity", help=argparse.SUPPRESS, const=log.WARN) default.add_argument("-C", "--no-color", action="store_false", dest="colors", help=argparse.SUPPRESS, default=True) # --- gen params --- # generate = subparsers.add_parser('compile', help='compile blog', parents=[default]) generate.add_argument("-f", "--force", action="store_true", dest="force", help="clear cache before compilation", default=False) generate.add_argument("-n", "--dry-run", dest="dryrun", action='store_true', help="show what would have been compiled", default=False) generate.add_argument("--ignore", dest="ignore", action="store_true", help="ignore critical errors", default=False) generate.add_argument("--search", dest="search", action="store_true", help="build search index", default=False) # --- webserver params --- # view = subparsers.add_parser('view', help="fire up built-in webserver", parents=[default]) view.add_argument("-p", "--port", dest="port", type=int, default=8000, help="webserver port") # --- aco params --- # autocompile = subparsers.add_parser('autocompile', help="automatic compilation and serving", parents=[default]) autocompile.add_argument("-f", "--force", action="store_true", dest="force", help="clear cache before compilation", default=False) autocompile.add_argument("-n", "--dry-run", dest="dryrun", action='store_true', help="show what would have been compiled", default=False) autocompile.add_argument("--ignore", dest="ignore", action="store_true", help="ignore critical errors", default=False) autocompile.add_argument("--search", dest="search", action="store_true", help="build search index", default=False) autocompile.add_argument("-p", "--port", dest="port", type=int, default=8000, help="webserver port") for alias in ('co', 'gen', 'generate'): subparsers._name_parser_map[alias] = generate for alias in ('serve', 'srv'): subparsers._name_parser_map[alias] = view subparsers._name_parser_map['aco'] = autocompile # temporary log to catch issues during task initialization log.init('temporary', level=log.WARN, colors=False) # initialize other tasks tasks.initialize(subparsers, default) # parse args options = parser.parse_args() # initialize colored logger log.init('acrylamid', level=options.verbosity, colors=options.colors) env = core.Environment({'author': __author__, 'url': __url__, 'options': options, 'globals': Struct()}) try: conf = core.load(options.conf) except IOError: log.critical('no conf.py found. Are you inside your blog?') sys.exit(1) except Exception as e: log.critical("%s in `conf.py`" % e.__class__.__name__) traceback.print_exc(file=sys.stdout) sys.exit(1) # -- run -- # if options.parser in ('gen', 'generate', 'co', 'compile'): log.setLevel(options.verbosity) try: commands.compile(conf, env) except AcrylamidException as e: log.exception(e.args[0]) sys.exit(1) elif options.parser in ('srv', 'serve', 'view'): from acrylamid.lib.httpd import Webserver ws = partial(Webserver, options.port, conf['output_dir']) ws = ws(log.info) if options.verbosity < 20 else ws(); ws.start() log.info(' * Running on http://127.0.0.1:%i/' % options.port) try: while True: time.sleep(1) except (SystemExit, KeyboardInterrupt) as e: ws.kill_received = True sys.exit(0) elif options.parser in ('aco', 'autocompile'): from acrylamid.lib.httpd import Webserver # XXX compile on request _or_ use inotify/fsevent ws = Webserver(options.port, conf['output_dir']); ws.start() log.info(' * Running on http://127.0.0.1:%i/' % options.port) try: commands.autocompile(ws, conf, env) except (SystemExit, KeyboardInterrupt) as e: ws.kill_received = True log.error(e.args[0]) traceback.print_exc(file=sys.stdout) sys.exit(0) elif options.parser in tasks.collected: try: tasks.collected[options.parser](conf, env, options) except AcrylamidException as e: log.exception('uncaught exception') sys.exit(1) else: log.critical('No such command!') sys.exit(2) sys.exit(0)
def compile(conf, env): """The compilation process.""" if env.options.force: cache.clear(conf.get("cache_dir")) # time measurement ctime = time.time() # populate env and corrects some conf things data = initialize(conf, env) # load pages/entries and store them in env rv = dict(zip(["entrylist", "pages", "translations", "drafts"], map(HashableList, readers.load(conf)))) entrylist, pages = rv["entrylist"], rv["pages"] translations, drafts = rv["translations"], rv["drafts"] # load references refs.load(entrylist, pages, translations, drafts) data.update(rv) env.globals.update(rv) # here we store all found filter and their aliases ns = defaultdict(set) # [<class head_offset.Headoffset at 0x1014882c0>, <class html.HTML at 0x101488328>,...] aflist = filters.get_filters() # ... and get all configured views _views = views.get_views() # filters found in all entries, views and conf.py (skip translations, has no items) found = sum((x.filters for x in chain(entrylist, pages, drafts, _views, [conf])), []) for val in found: # first we for `no` and get the function name and arguments f = val[2:] if val.startswith("no") else val fname, fargs = f.split("+")[:1][0], f.split("+")[1:] try: # initialize the filter with its function name and arguments fx = aflist[fname](conf, env, val, *fargs) if val.startswith("no"): fx = filters.disable(fx) except ValueError: try: fx = aflist[val.split("+")[:1][0]](conf, env, val, *fargs) except ValueError: raise AcrylamidException("no such filter: %s" % val) ns[fx].add(val) # include actual used filters to trigger modified state env.filters = HashableList(ns.keys()) for entry in chain(entrylist, pages, drafts): for v in _views: # a list that sorts out conflicting and duplicated filters flst = filters.FilterList() # filters found in this specific entry plus views and conf.py found = entry.filters + v.filters + data["conf"]["filters"] for fn in found: fx, _ = next((k for k in ns.iteritems() if fn in k[1])) if fx not in flst: flst.append(fx) # sort them ascending because we will pop within filters.add entry.filters.add(sorted(flst, key=lambda k: (-k.priority, k.name)), context=v) # lets offer a last break to populate tags and such for v in _views: env = v.context(conf, env, data) # now teh real thing! for v in _views: for entry in chain(entrylist, pages, translations, drafts): entry.context = v for var in "entrylist", "pages", "translations", "drafts": data[var] = HashableList(filter(v.condition, locals()[var])) if v.condition else locals()[var] tt = time.time() for buf, path in v.generate(conf, env, data): try: helpers.mkfile( buf, path, time.time() - tt, ns=v.name, force=env.options.force, dryrun=env.options.dryrun ) except UnicodeError: log.exception(path) finally: buf.close() tt = time.time() # copy modified/missing assets to output assets.compile(conf, env) # wait for unfinished hooks hooks.shutdown() # save conf/environment hash and new/changed/unchanged references helpers.memoize("Configuration", hash(conf)) helpers.memoize("Environment", hash(env)) refs.save() # remove abandoned cache files cache.shutdown() # print a short summary log.info( "%i new, %i updated, %i skipped [%.2fs]", event.count("create"), event.count("update"), event.count("identical") + event.count("skip"), time.time() - ctime, )
def initialize(conf, env): """Initializes Jinja2 environment, prepares locale and configure some minor things. Filter and View are inited with conf and env, a data dict is returned. """ # initialize cache, optional to cache_dir cache.init(conf.get('cache_dir')) env['version'] = type( 'Version', (str, ), dict(zip(['major', 'minor'], LooseVersion(dist.version).version[:2])))(dist.version) # crawl through CHANGES.md and stop on breaking changes if history.breaks(env, cache.emptyrun): cache.shutdown() print( "Detected version upgrade that might break your configuration. Run" ) print( "Acrylamid a second time to get rid of this message and premature exit." ) raise SystemExit # set up templating environment env.engine = import_object(conf['engine'])(conf['theme'], cache.cache_dir) env.engine.register('safeslug', helpers.safeslug) env.engine.register('tagify', lambda x: x) # try language set in LANG, if set correctly use it try: locale.setlocale(locale.LC_ALL, str(conf.get('lang', ''))) except (locale.Error, TypeError): # try if LANG is an alias try: locale.setlocale( locale.LC_ALL, locale.locale_alias[str(conf.get('lang', '')).lower()]) except (locale.Error, KeyError): # LANG is not an alias, so we use system's default try: locale.setlocale(locale.LC_ALL, '') except locale.Error: pass # hope this makes Travis happy log.info('notice your OS does not support %s, fallback to %s', conf.get('lang', ''), locale.getlocale()[0]) if locale.getlocale()[0] is not None: conf['lang'] = locale.getlocale()[0][:2] else: # getlocale() is (None, None) aka 'C' conf['lang'] = 'en' if 'www_root' not in conf: log.warn('no `www_root` specified, using localhost:8000') conf['www_root'] = 'http://localhost:8000/' # figure out timezone and set offset, more verbose for 2.6 compatibility td = (datetime.now() - datetime.utcnow()) offset = round(total_seconds(td) / 3600.0) conf['tzinfo'] = readers.Timezone(offset) # determine http(s), host and path env['protocol'], env['netloc'], env['path'], x, y = urlsplit( conf['www_root']) # take off the trailing slash for www_root and path conf['www_root'] = conf['www_root'].rstrip('/') env['path'] = env['path'].rstrip('/') if env['path']: conf['output_dir'] = conf['output_dir'] + env['path'] lazy.enable() filters.initialize(conf["filters_dir"][:], conf, env) lazy.disable( ) # this has weird side effects with jinja2, so disabled after filters views.initialize(conf["views_dir"][:], conf, env) env.views = views.Views(view for view in views.get_views()) entryfmt, pagefmt = '/:year/:slug/', '/:slug/' for view in views.get_views(): if view.name == 'entry': entryfmt = view.path if view.name == 'page': pagefmt = view.path conf.setdefault('entry_permalink', entryfmt) conf.setdefault('page_permalink', pagefmt) # register webassets to theme engine, make webassets available as env.webassets assets.initialize(conf, env) return {'conf': conf, 'env': env}
def initialize(conf, env): """Initializes Jinja2 environment, prepares locale and configure some minor things. Filter and View are inited with conf and env, a request dict is returned. """ # initialize cache, optional to cache_dir cache.init(conf.get('cache_dir', None)) # rewrite static directory assets.initialize(conf, env) # set up templating environment env.engine = utils.import_object(conf['engine'])() env.engine.init(conf['theme'], cache.cache_dir) env.engine.register('safeslug', helpers.safeslug) env.engine.register('tagify', lambda x: x) # try language set in LANG, if set correctly use it try: locale.setlocale(locale.LC_ALL, str(conf.get('lang', ''))) except (locale.Error, TypeError): # try if LANG is an alias try: locale.setlocale(locale.LC_ALL, locale.locale_alias[str(conf.get('lang', '')).lower()]) except (locale.Error, KeyError): # LANG is not an alias, so we use system's default try: locale.setlocale(locale.LC_ALL, '') except locale.Error: pass # hope this makes Travis happy log.info('notice your OS does not support %s, fallback to %s', conf.get('lang', ''), locale.getlocale()[0]) if locale.getlocale()[0] is not None: conf['lang'] = locale.getlocale()[0][:2] else: # getlocale() is (None, None) aka 'C' conf['lang'] = 'en' if 'www_root' not in conf: log.warn('no `www_root` specified, using localhost:8000') conf['www_root'] = 'http://localhost:8000/' # figure out timezone and set offset, more verbose for 2.6 compatibility td = (datetime.now() - datetime.utcnow()) total_seconds = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 offset = round(total_seconds / 3600.0) conf['tzinfo'] = readers.Timezone(offset) # determine http(s), host and path env['protocol'], env['netloc'], env['path'], x, y = urlsplit(conf['www_root']) # take off the trailing slash for www_root and path conf['www_root'] = conf['www_root'].rstrip('/') env['path'] = env['path'].rstrip('/') # check if encoding is available try: codecs.lookup(conf['encoding']) except LookupError: raise AcrylamidException('no such encoding available: %r' % conf['encoding']) # prepare, import and initialize filters and views if isinstance(conf['filters_dir'], basestring): conf['filters_dir'] = [conf['filters_dir'], ] if isinstance(conf['views_dir'], basestring): conf['views_dir'] = [conf['views_dir'], ] lazy.enable() filters.initialize(conf["filters_dir"], conf, env) lazy.disable() # this has weird side effects with jinja2, so disabled after filters views.initialize(conf["views_dir"], conf, env) env.views = views.Views(view for view in views.get_views()) entryfmt, pagefmt = '/:year/:slug/', '/:slug/' for view in views.get_views(): if view.name == 'entry': entryfmt = view.path if view.name == 'page': pagefmt = view.path conf.setdefault('entry_permalink', entryfmt) conf.setdefault('page_permalink', pagefmt) return {'conf': conf, 'env': env}
def initialize(conf, env): """Initializes Jinja2 environment, prepares locale and configure some minor things. Filter and View are inited with conf and env, a request dict is returned. """ # initialize cache, optional to cache_dir cache.init(conf.get('cache_dir', None)) # set up templating environment env.engine = utils.import_object(conf['engine'])() env.engine.init(conf['layout_dir'], cache.cache_dir) env.engine.register('safeslug', helpers.safeslug) env.engine.register('tagify', lambda x: x) # try language set in LANG, if set correctly use it try: locale.setlocale(locale.LC_ALL, conf.get('lang', '')) except (locale.Error, TypeError): # try if LANG is an alias try: locale.setlocale(locale.LC_ALL, locale.locale_alias[conf['lang'].lower()]) except (locale.Error, KeyError): # LANG is not an alias, so we use system's default locale.setlocale(locale.LC_ALL, '') log.info('notice your OS does not support %s, fallback to %s', conf['lang'], locale.getlocale()[0]) if locale.getlocale()[0] is not None: conf['lang'] = locale.getlocale()[0][:2] else: # getlocale() is (None, None) aka 'C' conf['lang'] = 'en' if 'www_root' not in conf: log.warn('no `www_root` specified, using localhost:8000') conf['www_root'] = 'http://localhost:8000/' env['protocol'], env['netloc'], env['path'], x, y = urlsplit(conf['www_root']) # take off the trailing slash for www_root and path conf['www_root'] = conf['www_root'].rstrip('/') env['path'] = env['path'].rstrip('/') # check if encoding is available try: codecs.lookup(conf['encoding']) except LookupError: raise AcrylamidException('no such encoding available: %r' % conf['encoding']) # prepare, import and initialize filters and views if isinstance(conf['filters_dir'], basestring): conf['filters_dir'] = [conf['filters_dir'], ] if isinstance(conf['views_dir'], basestring): conf['views_dir'] = [conf['views_dir'], ] lazy.enable() filters.initialize(conf["filters_dir"], conf, env, exclude=conf["filters_ignore"], include=conf["filters_include"]) lazy.disable() # this has weird side effects with jinja2, so disabled after filters views.initialize(conf["views_dir"], conf, env) env['views'] = dict([(v.view, v) for v in views.get_views()]) entryfmt, pagefmt = '/:year/:slug/', '/:slug/' for view in views.get_views(): if view.view == 'entry': entryfmt = view.path if view.view == 'page': pagefmt = view.path conf['entry_permalink'] = conf['entry_permalink'] or entryfmt conf['page_permalink'] = conf['page_permalink'] or pagefmt return {'conf': conf, 'env': env}
def run(conf, env, options): """Subcommand: new -- create a new blog entry the easy way. Either run ``acrylamid new My fresh new Entry`` or interactively via ``acrylamid new`` and the file will be created using the preferred permalink format.""" # we need the actual default values commands.initialize(conf, env) # config content_extension originally defined as string, not a list extlist = conf.get('content_extension',['.txt']) if isinstance(extlist, string_types): ext = extlist else: ext = extlist[0] fd, tmp = tempfile.mkstemp(suffix=ext, dir='.cache/') editor = os.getenv('VISUAL') if os.getenv('VISUAL') else os.getenv('EDITOR') tt = formats.get(ext, yaml) if options.title: title = u(' '.join(options.title)) else: title = u(input("Entry's title: ")) with io.open(fd, 'w', encoding='utf-8') as f: f.write(tt(title, datetime.now().strftime(conf['date_format']))) entry = readers.Entry(tmp, conf) p = join(conf['content_dir'], splitext(entry.permalink.strip('/'))[0]) try: os.makedirs(p.rsplit('/', 1)[0]) except OSError: pass filepath = p + ext if isfile(filepath): raise AcrylamidException('Entry already exists %r' % filepath) shutil.move(tmp, filepath) event.create('new', filepath) if datetime.now().hour == 23 and datetime.now().minute > 45: log.info("notice don't forget to update entry.date-day after mignight!") if log.level() >= log.WARN: return try: if editor: retcode = subprocess.call(shlex.split(editor) + [filepath]) elif sys.platform == 'darwin': retcode = subprocess.call(['open', filepath]) else: retcode = subprocess.call(['xdg-open', filepath]) except OSError: raise AcrylamidException('Could not launch an editor') # XXX process detaches... m( if retcode < 0: raise AcrylamidException('Child was terminated by signal %i' % -retcode) if os.stat(filepath)[6] == 0: raise AcrylamidException('File is empty!')