aboutsummaryrefslogtreecommitdiffstats
path: root/nikola/plugins/command
diff options
context:
space:
mode:
Diffstat (limited to 'nikola/plugins/command')
-rw-r--r--nikola/plugins/command/auto.py18
-rw-r--r--nikola/plugins/command/bootswatch_theme.py9
-rw-r--r--nikola/plugins/command/check.py56
-rw-r--r--nikola/plugins/command/console.py2
-rw-r--r--nikola/plugins/command/deploy.py29
-rw-r--r--nikola/plugins/command/import_blogger.py9
-rw-r--r--nikola/plugins/command/import_feed.py13
-rw-r--r--nikola/plugins/command/import_wordpress.py139
-rw-r--r--nikola/plugins/command/init.py110
-rw-r--r--nikola/plugins/command/install_plugin.py15
-rw-r--r--nikola/plugins/command/install_theme.py11
-rw-r--r--nikola/plugins/command/new_page.plugin9
-rw-r--r--nikola/plugins/command/new_page.py80
-rw-r--r--nikola/plugins/command/new_post.py61
-rw-r--r--nikola/plugins/command/planetoid/__init__.py14
-rw-r--r--nikola/plugins/command/serve.py19
16 files changed, 427 insertions, 167 deletions
diff --git a/nikola/plugins/command/auto.py b/nikola/plugins/command/auto.py
index 01116d1..d707d53 100644
--- a/nikola/plugins/command/auto.py
+++ b/nikola/plugins/command/auto.py
@@ -34,7 +34,7 @@ from nikola.plugin_categories import Command
from nikola.utils import req_missing
-class Auto(Command):
+class CommandAuto(Command):
"""Start debugging console."""
name = "auto"
doc_purpose = "automatically detect site changes, rebuild and optionally refresh a browser"
@@ -61,26 +61,26 @@ class Auto(Command):
try:
from livereload import Server
except ImportError:
- req_missing(['livereload>=2.0.0'], 'use the "auto" command')
+ req_missing(['livereload==2.1.0'], 'use the "auto" command')
return
- # Run an initial build so we are uptodate
+ # Run an initial build so we are up-to-date
subprocess.call(("nikola", "build"))
port = options and options.get('port')
server = Server()
- server.watch('conf.py')
- server.watch('themes/')
- server.watch('templates/')
+ server.watch('conf.py', 'nikola build')
+ server.watch('themes/', 'nikola build')
+ server.watch('templates/', 'nikola build')
server.watch(self.site.config['GALLERY_PATH'])
for item in self.site.config['post_pages']:
- server.watch(os.path.dirname(item[0]))
+ server.watch(os.path.dirname(item[0]), 'nikola build')
for item in self.site.config['FILES_FOLDERS']:
- server.watch(os.path.dirname(item))
+ server.watch(os.path.dirname(item), 'nikola build')
out_folder = self.site.config['OUTPUT_FOLDER']
if options and options.get('browser'):
webbrowser.open('http://localhost:{0}'.format(port))
- server.serve(port, out_folder)
+ server.serve(port, None, out_folder)
diff --git a/nikola/plugins/command/bootswatch_theme.py b/nikola/plugins/command/bootswatch_theme.py
index 94f37f2..82c47d2 100644
--- a/nikola/plugins/command/bootswatch_theme.py
+++ b/nikola/plugins/command/bootswatch_theme.py
@@ -86,12 +86,14 @@ class CommandBootswatchTheme(Command):
version = '2'
elif 'bootstrap' not in themes:
LOGGER.warn('"bootswatch_theme" only makes sense for themes that use bootstrap')
+ elif 'bootstrap3-gradients' in themes or 'bootstrap3-gradients-jinja' in themes:
+ LOGGER.warn('"bootswatch_theme" doesn\'t work well with the bootstrap3-gradients family')
- LOGGER.notice("Creating '{0}' theme from '{1}' and '{2}'".format(name, swatch, parent))
+ LOGGER.info("Creating '{0}' theme from '{1}' and '{2}'".format(name, swatch, parent))
utils.makedirs(os.path.join('themes', name, 'assets', 'css'))
for fname in ('bootstrap.min.css', 'bootstrap.css'):
url = '/'.join(('http://bootswatch.com', version, swatch, fname))
- LOGGER.notice("Downloading: " + url)
+ LOGGER.info("Downloading: " + url)
data = requests.get(url).text
with open(os.path.join('themes', name, 'assets', 'css', fname),
'wb+') as output:
@@ -99,5 +101,4 @@ class CommandBootswatchTheme(Command):
with open(os.path.join('themes', name, 'parent'), 'wb+') as output:
output.write(parent.encode('utf-8'))
- LOGGER.notice('Theme created. Change the THEME setting to "{0}" to use '
- 'it.'.format(name))
+ LOGGER.notice('Theme created. Change the THEME setting to "{0}" to use it.'.format(name))
diff --git a/nikola/plugins/command/check.py b/nikola/plugins/command/check.py
index a7e8c13..26db321 100644
--- a/nikola/plugins/command/check.py
+++ b/nikola/plugins/command/check.py
@@ -102,6 +102,14 @@ class CommandCheck(Command):
'default': False,
'help': 'List possible source files for files with broken links.',
},
+ {
+ 'name': 'verbose',
+ 'long': 'verbose',
+ 'short': 'v',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Be more verbose.',
+ },
]
def _execute(self, options, args):
@@ -112,6 +120,10 @@ class CommandCheck(Command):
if not options['links'] and not options['files'] and not options['clean']:
print(self.help())
return False
+ if options['verbose']:
+ self.logger.level = 1
+ else:
+ self.logger.level = 4
if options['links']:
failure = self.scan_links(options['find_sources'])
if options['files']:
@@ -126,6 +138,8 @@ class CommandCheck(Command):
def analyze(self, task, find_sources=False):
rv = False
self.whitelist = [re.compile(x) for x in self.site.config['LINK_CHECK_WHITELIST']]
+ base_url = urlparse(self.site.config['BASE_URL'])
+ url_type = self.site.config['URL_TYPE']
try:
filename = task.split(":")[-1]
d = lxml.html.fromstring(open(filename).read())
@@ -134,31 +148,51 @@ class CommandCheck(Command):
if target == "#":
continue
parsed = urlparse(target)
- if parsed.scheme or target.startswith('//'):
+
+ # Absolute links when using only paths, skip.
+ if (parsed.scheme or target.startswith('//')) and url_type in ('rel_path', 'full_path'):
continue
+
+ # Absolute links to other domains, skip
+ if (parsed.scheme or target.startswith('//')) and parsed.netloc != base_url.netloc:
+ continue
+
if parsed.fragment:
target = target.split('#')[0]
- target_filename = os.path.abspath(
- os.path.join(os.path.dirname(filename), unquote(target)))
+ if url_type == 'rel_path':
+ target_filename = os.path.abspath(
+ os.path.join(os.path.dirname(filename), unquote(target)))
+
+ elif url_type in ('full_path', 'absolute'):
+ target_filename = os.path.abspath(
+ os.path.join(os.path.dirname(filename), parsed.path))
+ if parsed.path.endswith('/'): # abspath removes trailing slashes
+ target_filename += '/{0}'.format(self.site.config['INDEX_FILE'])
+ if target_filename.startswith(base_url.path):
+ target_filename = target_filename[len(base_url.path):]
+ target_filename = os.path.join(self.site.config['OUTPUT_FOLDER'], target_filename)
+
if any(re.match(x, target_filename) for x in self.whitelist):
continue
elif target_filename not in self.existing_targets:
if os.path.exists(target_filename):
+ self.logger.notice("Good link {0} => {1}".format(target, target_filename))
self.existing_targets.add(target_filename)
else:
rv = True
- self.logger.warn("Broken link in {0}: ".format(filename), target)
+ self.logger.warn("Broken link in {0}: {1}".format(filename, target))
if find_sources:
self.logger.warn("Possible sources:")
self.logger.warn(os.popen('nikola list --deps ' + task, 'r').read())
self.logger.warn("===============================\n")
except Exception as exc:
- self.logger.error("Error with:", filename, exc)
+ self.logger.error("Error with: {0} {1}".format(filename, exc))
return rv
def scan_links(self, find_sources=False):
- self.logger.notice("Checking Links:")
- self.logger.notice("===============")
+ self.logger.info("Checking Links:")
+ self.logger.info("===============\n")
+ self.logger.notice("{0} mode".format(self.site.config['URL_TYPE']))
failure = False
for task in os.popen('nikola list --all', 'r').readlines():
task = task.strip()
@@ -170,13 +204,13 @@ class CommandCheck(Command):
if self.analyze(task, find_sources):
failure = True
if not failure:
- self.logger.notice("All links checked.")
+ self.logger.info("All links checked.")
return failure
def scan_files(self):
failure = False
- self.logger.notice("Checking Files:")
- self.logger.notice("===============\n")
+ self.logger.info("Checking Files:")
+ self.logger.info("===============\n")
only_on_output, only_on_input = real_scan_files(self.site)
# Ignore folders
@@ -195,7 +229,7 @@ class CommandCheck(Command):
for f in only_on_input:
self.logger.warn(f)
if not failure:
- self.logger.notice("All files checked.")
+ self.logger.info("All files checked.")
return failure
def clean_files(self):
diff --git a/nikola/plugins/command/console.py b/nikola/plugins/command/console.py
index e66b650..b0a8958 100644
--- a/nikola/plugins/command/console.py
+++ b/nikola/plugins/command/console.py
@@ -35,7 +35,7 @@ from nikola.utils import get_logger, STDERR_HANDLER
LOGGER = get_logger('console', STDERR_HANDLER)
-class Console(Command):
+class CommandConsole(Command):
"""Start debugging console."""
name = "console"
shells = ['ipython', 'bpython', 'plain']
diff --git a/nikola/plugins/command/deploy.py b/nikola/plugins/command/deploy.py
index eb5787e..bd1c15f 100644
--- a/nikola/plugins/command/deploy.py
+++ b/nikola/plugins/command/deploy.py
@@ -25,7 +25,6 @@
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function
-from ast import literal_eval
import codecs
from datetime import datetime
import os
@@ -40,8 +39,8 @@ from nikola.plugin_categories import Command
from nikola.utils import remove_file, get_logger
-class Deploy(Command):
- """Deploy site. """
+class CommandDeploy(Command):
+ """Deploy site."""
name = "deploy"
doc_usage = ""
@@ -76,7 +75,7 @@ class Deploy(Command):
undeployed_posts.append(post)
for command in self.site.config['DEPLOY_COMMANDS']:
- self.logger.notice("==> {0}".format(command))
+ self.logger.info("==> {0}".format(command))
try:
subprocess.check_call(command, shell=True)
except subprocess.CalledProcessError as e:
@@ -84,26 +83,22 @@ class Deploy(Command):
'returned {1}'.format(e.cmd, e.returncode))
sys.exit(e.returncode)
- self.logger.notice("Successful deployment")
- tzinfo = pytz.timezone(self.site.config['TIMEZONE'])
+ self.logger.info("Successful deployment")
try:
- with open(timestamp_path, 'rb') as inf:
- last_deploy = literal_eval(inf.read().strip())
- if tzinfo:
- last_deploy = last_deploy.replace(tzinfo=tzinfo)
+ with codecs.open(timestamp_path, 'rb', 'utf8') as inf:
+ last_deploy = datetime.strptime(inf.read().strip(), "%Y-%m-%dT%H:%M:%S.%f")
clean = False
- except Exception:
+ except (IOError, Exception) as e:
+ self.logger.debug("Problem when reading `{0}`: {1}".format(timestamp_path, e))
last_deploy = datetime(1970, 1, 1)
- if tzinfo:
- last_deploy = last_deploy.replace(tzinfo=tzinfo)
clean = True
- new_deploy = datetime.now()
+ new_deploy = datetime.utcnow()
self._emit_deploy_event(last_deploy, new_deploy, clean, undeployed_posts)
# Store timestamp of successful deployment
with codecs.open(timestamp_path, 'wb+', 'utf8') as outf:
- outf.write(repr(new_deploy))
+ outf.write(new_deploy.isoformat())
def _emit_deploy_event(self, last_deploy, new_deploy, clean=False, undeployed=None):
""" Emit events for all timeline entries newer than last deploy.
@@ -129,9 +124,11 @@ class Deploy(Command):
'undeployed': undeployed
}
+ tzinfo = pytz.timezone(self.site.config['TIMEZONE'])
+
deployed = [
entry for entry in self.site.timeline
- if entry.date > last_deploy and entry not in undeployed
+ if entry.date > (last_deploy.replace(tzinfo=tzinfo) if tzinfo else last_deploy) and entry not in undeployed
]
event['deployed'] = deployed
diff --git a/nikola/plugins/command/import_blogger.py b/nikola/plugins/command/import_blogger.py
index ea12b4a..dd629c4 100644
--- a/nikola/plugins/command/import_blogger.py
+++ b/nikola/plugins/command/import_blogger.py
@@ -43,6 +43,7 @@ from nikola.plugin_categories import Command
from nikola import utils
from nikola.utils import req_missing
from nikola.plugins.basic_import import ImportMixin
+from nikola.plugins.command.init import SAMPLE_CONF, prepare_config
LOGGER = utils.get_logger('import_blogger', utils.STDERR_HANDLER)
@@ -95,8 +96,8 @@ class CommandImportBlogger(Command, ImportMixin):
conf_out_path = self.get_configuration_output_path()
# if it tracebacks here, look a comment in
# basic_import.Import_Mixin.generate_base_site
- conf_termplate_render = conf_template.render(**self.context)
- self.write_configuration(conf_out_path, conf_termplate_render)
+ conf_template_render = conf_template.render(**prepare_config(self.context))
+ self.write_configuration(conf_out_path, conf_template_render)
@classmethod
def get_channel_from_file(cls, filename):
@@ -106,8 +107,7 @@ class CommandImportBlogger(Command, ImportMixin):
@staticmethod
def populate_context(channel):
- # may need changes when the template conf.py.in changes
- context = {}
+ context = SAMPLE_CONF.copy()
context['DEFAULT_LANG'] = 'en' # blogger doesn't include the language
# in the dump
context['BLOG_TITLE'] = channel.feed.title
@@ -131,7 +131,6 @@ class CommandImportBlogger(Command, ImportMixin):
"html": ('.html', '.htm')
}
'''
- context['THEME'] = 'bootstrap3'
return context
diff --git a/nikola/plugins/command/import_feed.py b/nikola/plugins/command/import_feed.py
index 70a5cd5..ee59277 100644
--- a/nikola/plugins/command/import_feed.py
+++ b/nikola/plugins/command/import_feed.py
@@ -43,6 +43,7 @@ from nikola.plugin_categories import Command
from nikola import utils
from nikola.utils import req_missing
from nikola.plugins.basic_import import ImportMixin
+from nikola.plugins.command.init import SAMPLE_CONF, prepare_config
LOGGER = utils.get_logger('import_feed', utils.STDERR_HANDLER)
@@ -82,7 +83,7 @@ class CommandImportFeed(Command, ImportMixin):
self.import_posts(channel)
self.write_configuration(self.get_configuration_output_path(
- ), conf_template.render(**self.context))
+ ), conf_template.render(**prepare_config(self.context)))
@classmethod
def get_channel_from_file(cls, filename):
@@ -90,7 +91,7 @@ class CommandImportFeed(Command, ImportMixin):
@staticmethod
def populate_context(channel):
- context = {}
+ context = SAMPLE_CONF.copy()
context['DEFAULT_LANG'] = channel.feed.title_detail.language \
if channel.feed.title_detail.language else 'en'
context['BLOG_TITLE'] = channel.feed.title
@@ -100,9 +101,11 @@ class CommandImportFeed(Command, ImportMixin):
context['BLOG_EMAIL'] = channel.feed.author_detail.get('email', '') if 'author_detail' in channel.feed else ''
context['BLOG_AUTHOR'] = channel.feed.author_detail.get('name', '') if 'author_detail' in channel.feed else ''
- context['POST_PAGES'] = '''(
- ("posts/*.html", "posts", "post.tmpl", True),
- ("stories/*.html", "stories", "story.tmpl", False),
+ context['POSTS'] = '''(
+ ("posts/*.html", "posts", "post.tmpl"),
+ )'''
+ context['PAGES'] = '''(
+ ("stories/*.html", "stories", "story.tmpl"),
)'''
context['COMPILERS'] = '''{
"rest": ('.txt', '.rst'),
diff --git a/nikola/plugins/command/import_wordpress.py b/nikola/plugins/command/import_wordpress.py
index 0c9915a..b567c77 100644
--- a/nikola/plugins/command/import_wordpress.py
+++ b/nikola/plugins/command/import_wordpress.py
@@ -50,6 +50,8 @@ from nikola.plugin_categories import Command
from nikola import utils
from nikola.utils import req_missing
from nikola.plugins.basic_import import ImportMixin, links
+from nikola.nikola import DEFAULT_TRANSLATIONS_PATTERN
+from nikola.plugins.command.init import SAMPLE_CONF, prepare_config
LOGGER = utils.get_logger('import_wordpress', utils.STDERR_HANDLER)
@@ -84,6 +86,23 @@ class CommandImportWordpress(Command, ImportMixin):
'type': bool,
'help': "Do not try to download files for the import",
},
+ {
+ 'name': 'separate_qtranslate_content',
+ 'long': 'qtranslate',
+ 'default': False,
+ 'type': bool,
+ 'help': "Look for translations generated by qtranslate plugin",
+ # WARNING: won't recover translated titles that actually
+ # don't seem to be part of the wordpress XML export at the
+ # time of writing :(
+ },
+ {
+ 'name': 'translations_pattern',
+ 'long': 'translations_pattern',
+ 'default': None,
+ 'type': str,
+ 'help': "The pattern for translation files names",
+ },
]
def _execute(self, options={}, args=[]):
@@ -114,6 +133,9 @@ class CommandImportWordpress(Command, ImportMixin):
self.exclude_drafts = options.get('exclude_drafts', False)
self.no_downloads = options.get('no_downloads', False)
+ self.separate_qtranslate_content = options.get('separate_qtranslate_content')
+ self.translations_pattern = options.get('translations_pattern')
+
if not self.no_downloads:
def show_info_about_mising_module(modulename):
LOGGER.error(
@@ -135,15 +157,21 @@ class CommandImportWordpress(Command, ImportMixin):
self.context = self.populate_context(channel)
conf_template = self.generate_base_site()
+ # If user has specified a custom pattern for translation files we
+ # need to fix the config
+ if self.translations_pattern:
+ self.context['TRANSLATIONS_PATTERN'] = self.translations_pattern
+
self.import_posts(channel)
self.context['REDIRECTIONS'] = self.configure_redirections(
self.url_map)
self.write_urlmap_csv(
os.path.join(self.output_folder, 'url_map.csv'), self.url_map)
- rendered_template = conf_template.render(**self.context)
+ rendered_template = conf_template.render(**prepare_config(self.context))
rendered_template = re.sub('# REDIRECTIONS = ', 'REDIRECTIONS = ',
rendered_template)
+
if self.timezone:
rendered_template = re.sub('# TIMEZONE = \'UTC\'',
'TIMEZONE = \'' + self.timezone + '\'',
@@ -194,8 +222,9 @@ class CommandImportWordpress(Command, ImportMixin):
def populate_context(channel):
wordpress_namespace = channel.nsmap['wp']
- context = {}
+ context = SAMPLE_CONF.copy()
context['DEFAULT_LANG'] = get_text_tag(channel, 'language', 'en')[:2]
+ context['TRANSLATIONS_PATTERN'] = DEFAULT_TRANSLATIONS_PATTERN
context['BLOG_TITLE'] = get_text_tag(channel, 'title',
'PUT TITLE HERE')
context['BLOG_DESCRIPTION'] = get_text_tag(
@@ -205,9 +234,10 @@ class CommandImportWordpress(Command, ImportMixin):
base_site_url = channel.find('{{{0}}}author'.format(wordpress_namespace))
context['BASE_URL'] = get_text_tag(base_site_url,
None,
- "http://foo.com")
+ "http://foo.com/")
+ if not context['BASE_URL'].endswith('/'):
+ context['BASE_URL'] += '/'
context['SITE_URL'] = context['BASE_URL']
- context['THEME'] = 'bootstrap3'
author = channel.find('{{{0}}}author'.format(wordpress_namespace))
context['BLOG_EMAIL'] = get_text_tag(
@@ -253,7 +283,7 @@ class CommandImportWordpress(Command, ImportMixin):
+ list(path.split('/'))))
dst_dir = os.path.dirname(dst_path)
utils.makedirs(dst_dir)
- LOGGER.notice("Downloading {0} => {1}".format(url, dst_path))
+ LOGGER.info("Downloading {0} => {1}".format(url, dst_path))
self.download_url_content_to_file(url, dst_path)
dst_url = '/'.join(dst_path.split(os.sep)[2:])
links[link] = '/' + dst_url
@@ -288,7 +318,7 @@ class CommandImportWordpress(Command, ImportMixin):
# your blogging into another site or system its not.
# Why don't they just use JSON?
if sys.version_info[0] == 2:
- metadata = phpserialize.loads(meta_value.text)
+ metadata = phpserialize.loads(utils.sys_encode(meta_value.text))
size_key = 'sizes'
file_key = 'file'
else:
@@ -307,7 +337,7 @@ class CommandImportWordpress(Command, ImportMixin):
+ list(path.split('/'))))
dst_dir = os.path.dirname(dst_path)
utils.makedirs(dst_dir)
- LOGGER.notice("Downloading {0} => {1}".format(url, dst_path))
+ LOGGER.info("Downloading {0} => {1}".format(url, dst_path))
self.download_url_content_to_file(url, dst_path)
dst_url = '/'.join(dst_path.split(os.sep)[2:])
links[url] = '/' + dst_url
@@ -350,14 +380,17 @@ class CommandImportWordpress(Command, ImportMixin):
# link is something like http://foo.com/2012/09/01/hello-world/
# So, take the path, utils.slugify it, and that's our slug
link = get_text_tag(item, 'link', None)
- path = unquote(urlparse(link).path)
+ path = unquote(urlparse(link).path.strip('/'))
# In python 2, path is a str. slug requires a unicode
# object. According to wikipedia, unquoted strings will
# usually be UTF8
if isinstance(path, utils.bytes_str):
path = path.decode('utf8')
- slug = utils.slugify(path)
+ pathlist = path.split('/')
+ if len(pathlist) > 1:
+ out_folder = os.path.join(*([out_folder] + pathlist[:-1]))
+ slug = utils.slugify(pathlist[-1])
if not slug: # it happens if the post has no "nice" URL
slug = get_text_tag(
item, '{{{0}}}post_name'.format(wordpress_namespace), None)
@@ -395,21 +428,43 @@ class CommandImportWordpress(Command, ImportMixin):
continue
tags.append(text)
+ if '$latex' in content:
+ tags.append('mathjax')
+
if is_draft and self.exclude_drafts:
LOGGER.notice('Draft "{0}" will not be imported.'.format(title))
elif content.strip():
# If no content is found, no files are written.
- self.url_map[link] = self.context['SITE_URL'] + '/' + \
- out_folder + '/' + slug + '.html'
-
- content = self.transform_content(content)
-
- self.write_metadata(os.path.join(self.output_folder, out_folder,
- slug + '.meta'),
- title, slug, post_date, description, tags)
- self.write_content(
- os.path.join(self.output_folder, out_folder, slug + '.wp'),
- content)
+ self.url_map[link] = (self.context['SITE_URL'] + out_folder + '/'
+ + slug + '.html')
+ if hasattr(self, "separate_qtranslate_content") \
+ and self.separate_qtranslate_content:
+ content_translations = separate_qtranslate_content(content)
+ else:
+ content_translations = {"": content}
+ default_language = self.context["DEFAULT_LANG"]
+ for lang, content in content_translations.items():
+ if lang:
+ out_meta_filename = slug + '.meta'
+ if lang == default_language:
+ out_content_filename = slug + '.wp'
+ else:
+ out_content_filename \
+ = utils.get_translation_candidate(self.context,
+ slug + ".wp", lang)
+ meta_slug = slug
+ else:
+ out_meta_filename = slug + '.meta'
+ out_content_filename = slug + '.wp'
+ meta_slug = slug
+ content = self.transform_content(content)
+ self.write_metadata(os.path.join(self.output_folder, out_folder,
+ out_meta_filename),
+ title, meta_slug, post_date, description, tags)
+ self.write_content(
+ os.path.join(self.output_folder,
+ out_folder, out_content_filename),
+ content)
else:
LOGGER.warn('Not going to import "{0}" because it seems to contain'
' no content.'.format(title))
@@ -441,3 +496,47 @@ def get_text_tag(tag, name, default):
return t.text
else:
return default
+
+
+def separate_qtranslate_content(text):
+ """Parse the content of a wordpress post or page and separate
+ the various language specific contents when they are delimited
+ with qtranslate tags: <!--:LL-->blabla<!--:-->"""
+ # TODO: uniformize qtranslate tags <!--/en--> => <!--:-->
+ qt_start = "<!--:"
+ qt_end = "-->"
+ qt_end_with_lang_len = 5
+ qt_chunks = text.split(qt_start)
+ content_by_lang = {}
+ common_txt_list = []
+ for c in qt_chunks:
+ if not c.strip():
+ continue
+ if c.startswith(qt_end):
+ # just after the end of a language specific section, there may
+ # be some piece of common text or tags, or just nothing
+ lang = "" # default language
+ c = c.lstrip(qt_end)
+ if not c:
+ continue
+ elif c[2:].startswith(qt_end):
+ # a language specific section (with language code at the begining)
+ lang = c[:2]
+ c = c[qt_end_with_lang_len:]
+ else:
+ # nowhere specific (maybe there is no language section in the
+ # currently parsed content)
+ lang = "" # default language
+ if not lang:
+ common_txt_list.append(c)
+ for l in content_by_lang.keys():
+ content_by_lang[l].append(c)
+ else:
+ content_by_lang[lang] = content_by_lang.get(lang, common_txt_list) + [c]
+ # in case there was no language specific section, just add the text
+ if common_txt_list and not content_by_lang:
+ content_by_lang[""] = common_txt_list
+ # Format back the list to simple text
+ for l in content_by_lang.keys():
+ content_by_lang[l] = " ".join(content_by_lang[l])
+ return content_by_lang
diff --git a/nikola/plugins/command/init.py b/nikola/plugins/command/init.py
index 96caad8..d7eeed7 100644
--- a/nikola/plugins/command/init.py
+++ b/nikola/plugins/command/init.py
@@ -28,18 +28,71 @@ from __future__ import print_function
import os
import shutil
import codecs
+import json
from mako.template import Template
import nikola
+from nikola.nikola import DEFAULT_TRANSLATIONS_PATTERN
from nikola.plugin_categories import Command
from nikola.utils import get_logger, makedirs, STDERR_HANDLER
from nikola.winutils import fix_git_symlinked
LOGGER = get_logger('init', STDERR_HANDLER)
+SAMPLE_CONF = {
+ 'BLOG_AUTHOR': "Your Name",
+ 'BLOG_TITLE': "Demo Site",
+ 'SITE_URL': "http://getnikola.com/",
+ 'BLOG_EMAIL': "joe@demo.site",
+ 'BLOG_DESCRIPTION': "This is a demo site for Nikola.",
+ 'DEFAULT_LANG': "en",
+ 'THEME': 'bootstrap3',
+ 'COMMENT_SYSTEM': 'disqus',
+ 'COMMENT_SYSTEM_ID': 'nikolademo',
+ 'TRANSLATIONS_PATTERN': DEFAULT_TRANSLATIONS_PATTERN,
+ 'POSTS': """(
+("posts/*.rst", "posts", "post.tmpl"),
+("posts/*.txt", "posts", "post.tmpl"),
+)""",
+ 'PAGES': """(
+("stories/*.rst", "stories", "story.tmpl"),
+("stories/*.txt", "stories", "story.tmpl"),
+)""",
+ 'COMPILERS': """{
+"rest": ('.rst', '.txt'),
+"markdown": ('.md', '.mdown', '.markdown'),
+"textile": ('.textile',),
+"txt2tags": ('.t2t',),
+"bbcode": ('.bb',),
+"wiki": ('.wiki',),
+"ipynb": ('.ipynb',),
+"html": ('.html', '.htm'),
+# PHP files are rendered the usual way (i.e. with the full templates).
+# The resulting files have .php extensions, making it possible to run
+# them without reconfiguring your server to recognize them.
+"php": ('.php',),
+# Pandoc detects the input from the source filename
+# but is disabled by default as it would conflict
+# with many of the others.
+# "pandoc": ('.rst', '.md', '.txt'),
+}""",
+ 'REDIRECTIONS': [],
+}
+
+
+# In order to ensure proper escaping, all variables but the three
+# pre-formatted ones are handled by json.dumps().
+def prepare_config(config):
+ """Parse sample config with JSON."""
+ p = config.copy()
+ p.update(dict((k, json.dumps(v)) for k, v in p.items()
+ if k not in ('POSTS', 'PAGES', 'COMPILERS')))
+ return p
+
class CommandInit(Command):
+
"""Create a new site."""
name = "init"
@@ -57,40 +110,6 @@ class CommandInit(Command):
}
]
- SAMPLE_CONF = {
- 'BLOG_AUTHOR': "Your Name",
- 'BLOG_TITLE': "Demo Site",
- 'SITE_URL': "http://getnikola.com/",
- 'BLOG_EMAIL': "joe@demo.site",
- 'BLOG_DESCRIPTION': "This is a demo site for Nikola.",
- 'DEFAULT_LANG': "en",
- 'THEME': 'bootstrap3',
-
- 'POSTS': """(
- ("posts/*.rst", "posts", "post.tmpl"),
- ("posts/*.txt", "posts", "post.tmpl"),
-)""",
- 'PAGES': """(
- ("stories/*.rst", "stories", "story.tmpl"),
- ("stories/*.txt", "stories", "story.tmpl"),
-)""",
- 'COMPILERS': """{
- "rest": ('.rst', '.txt'),
- "markdown": ('.md', '.mdown', '.markdown'),
- "textile": ('.textile',),
- "txt2tags": ('.t2t',),
- "bbcode": ('.bb',),
- "wiki": ('.wiki',),
- "ipynb": ('.ipynb',),
- "html": ('.html', '.htm'),
- # Pandoc detects the input from the source filename
- # but is disabled by default as it would conflict
- # with many of the others.
- # "pandoc": ('.rst', '.md', '.txt'),
-}""",
- 'REDIRECTIONS': '[]',
- }
-
@classmethod
def copy_sample_site(cls, target):
lib_path = cls.get_path_to_nikola_modules()
@@ -105,7 +124,7 @@ class CommandInit(Command):
conf_template = Template(filename=template_path)
conf_path = os.path.join(target, 'conf.py')
with codecs.open(conf_path, 'w+', 'utf8') as fd:
- fd.write(conf_template.render(**cls.SAMPLE_CONF))
+ fd.write(conf_template.render(**prepare_config(SAMPLE_CONF)))
@classmethod
def create_empty_site(cls, target):
@@ -122,16 +141,13 @@ class CommandInit(Command):
print("Usage: nikola init folder [options]")
return False
target = args[0]
- if target is None:
- print(self.usage)
+ if not options or not options.get('demo'):
+ self.create_empty_site(target)
+ LOGGER.info('Created empty site at {0}.'.format(target))
else:
- if not options or not options.get('demo'):
- self.create_empty_site(target)
- LOGGER.notice('Created empty site at {0}.'.format(target))
- else:
- self.copy_sample_site(target)
- LOGGER.notice("A new site with example data has been created at "
- "{0}.".format(target))
- LOGGER.notice("See README.txt in that folder for more information.")
-
- self.create_configuration(target)
+ self.copy_sample_site(target)
+ LOGGER.info("A new site with example data has been created at "
+ "{0}.".format(target))
+ LOGGER.info("See README.txt in that folder for more information.")
+
+ self.create_configuration(target)
diff --git a/nikola/plugins/command/install_plugin.py b/nikola/plugins/command/install_plugin.py
index 1d6584d..34223c0 100644
--- a/nikola/plugins/command/install_plugin.py
+++ b/nikola/plugins/command/install_plugin.py
@@ -27,7 +27,6 @@
from __future__ import print_function
import codecs
import os
-import sys
import json
import shutil
import subprocess
@@ -123,10 +122,10 @@ class CommandInstallPlugin(Command):
def do_install(self, name, data):
if name in data:
utils.makedirs(self.output_dir)
- LOGGER.notice('Downloading: ' + data[name])
+ LOGGER.info('Downloading: ' + data[name])
zip_file = BytesIO()
zip_file.write(requests.get(data[name]).content)
- LOGGER.notice('Extracting: {0} into plugins'.format(name))
+ LOGGER.info('Extracting: {0} into plugins'.format(name))
utils.extract_all(zip_file, 'plugins')
dest_path = os.path.join('plugins', name)
else:
@@ -142,13 +141,13 @@ class CommandInstallPlugin(Command):
LOGGER.error("{0} is already installed".format(name))
return False
- LOGGER.notice('Copying {0} into plugins'.format(plugin_path))
+ LOGGER.info('Copying {0} into plugins'.format(plugin_path))
shutil.copytree(plugin_path, dest_path)
reqpath = os.path.join(dest_path, 'requirements.txt')
if os.path.exists(reqpath):
LOGGER.notice('This plugin has Python dependencies.')
- LOGGER.notice('Installing dependencies with pip...')
+ LOGGER.info('Installing dependencies with pip...')
try:
subprocess.check_call(('pip', 'install', '-r', reqpath))
except subprocess.CalledProcessError:
@@ -159,7 +158,7 @@ class CommandInstallPlugin(Command):
print('You have to install those yourself or through a '
'package manager.')
else:
- LOGGER.notice('Dependency installation succeeded.')
+ LOGGER.info('Dependency installation succeeded.')
reqnpypath = os.path.join(dest_path, 'requirements-nonpy.txt')
if os.path.exists(reqnpypath):
LOGGER.notice('This plugin has third-party '
@@ -177,10 +176,10 @@ class CommandInstallPlugin(Command):
'manager.')
confpypath = os.path.join(dest_path, 'conf.py.sample')
if os.path.exists(confpypath):
- LOGGER.notice('This plugin has a sample config file.')
+ LOGGER.notice('This plugin has a sample config file. Integrate it with yours in order to make this plugin work!')
print('Contents of the conf.py.sample file:\n')
with codecs.open(confpypath, 'rb', 'utf-8') as fh:
- if sys.platform == 'win32':
+ if self.site.colorful:
print(indent(pygments.highlight(
fh.read(), PythonLexer(), TerminalFormatter()),
4 * ' '))
diff --git a/nikola/plugins/command/install_theme.py b/nikola/plugins/command/install_theme.py
index 569397b..47c73b4 100644
--- a/nikola/plugins/command/install_theme.py
+++ b/nikola/plugins/command/install_theme.py
@@ -26,7 +26,6 @@
from __future__ import print_function
import os
-import sys
import codecs
import json
import shutil
@@ -137,10 +136,10 @@ class CommandInstallTheme(Command):
def do_install(self, name, data):
if name in data:
utils.makedirs(self.output_dir)
- LOGGER.notice('Downloading: ' + data[name])
+ LOGGER.info('Downloading: ' + data[name])
zip_file = BytesIO()
zip_file.write(requests.get(data[name]).content)
- LOGGER.notice('Extracting: {0} into themes'.format(name))
+ LOGGER.info('Extracting: {0} into themes'.format(name))
utils.extract_all(zip_file)
dest_path = os.path.join('themes', name)
else:
@@ -156,14 +155,14 @@ class CommandInstallTheme(Command):
LOGGER.error("{0} is already installed".format(name))
return False
- LOGGER.notice('Copying {0} into themes'.format(theme_path))
+ LOGGER.info('Copying {0} into themes'.format(theme_path))
shutil.copytree(theme_path, dest_path)
confpypath = os.path.join(dest_path, 'conf.py.sample')
if os.path.exists(confpypath):
- LOGGER.notice('This plugin has a sample config file. Integrate it with yours in order to make this theme work!')
+ LOGGER.notice('This theme has a sample config file. Integrate it with yours in order to make this theme work!')
print('Contents of the conf.py.sample file:\n')
with codecs.open(confpypath, 'rb', 'utf-8') as fh:
- if sys.platform == 'win32':
+ if self.site.colorful:
print(indent(pygments.highlight(
fh.read(), PythonLexer(), TerminalFormatter()),
4 * ' '))
diff --git a/nikola/plugins/command/new_page.plugin b/nikola/plugins/command/new_page.plugin
new file mode 100644
index 0000000..1f1c84c
--- /dev/null
+++ b/nikola/plugins/command/new_page.plugin
@@ -0,0 +1,9 @@
+[Core]
+Name = new_page
+Module = new_page
+
+[Documentation]
+Author = Roberto Alsina, Chris Warrick
+Version = 0.1
+Website = http://getnikola.com
+Description = Create a new page.
diff --git a/nikola/plugins/command/new_page.py b/nikola/plugins/command/new_page.py
new file mode 100644
index 0000000..39c0c1d
--- /dev/null
+++ b/nikola/plugins/command/new_page.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2012-2014 Roberto Alsina, Chris Warrick and others.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from __future__ import unicode_literals, print_function
+
+from nikola.plugin_categories import Command
+
+
+class CommandNewPage(Command):
+ """Create a new page."""
+
+ name = "new_page"
+ doc_usage = "[options] [path]"
+ doc_purpose = "create a new page in the site"
+ cmd_options = [
+ {
+ 'name': 'title',
+ 'short': 't',
+ 'long': 'title',
+ 'type': str,
+ 'default': '',
+ 'help': 'Title for the page.'
+ },
+ {
+ 'name': 'onefile',
+ 'short': '1',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Create the page with embedded metadata (single file format)'
+ },
+ {
+ 'name': 'twofile',
+ 'short': '2',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Create the page with separate metadata (two file format)'
+ },
+ {
+ 'name': 'content_format',
+ 'short': 'f',
+ 'long': 'format',
+ 'type': str,
+ 'default': '',
+ 'help': 'Markup format for the page, one of rest, markdown, wiki, '
+ 'bbcode, html, textile, txt2tags',
+ },
+ ]
+
+ def _execute(self, options, args):
+ """Create a new page."""
+ options['tags'] = ''
+ options['schedule'] = False
+ options['is_page'] = True
+ # Even though stuff was split into `new_page`, it’s easier to do it
+ # there not to duplicate the code.
+ p = self.site.plugin_manager.getPluginByName('new_post', 'Command').plugin_object
+ return p.execute(options, args)
diff --git a/nikola/plugins/command/new_post.py b/nikola/plugins/command/new_post.py
index a5c551d..cd37a75 100644
--- a/nikola/plugins/command/new_post.py
+++ b/nikola/plugins/command/new_post.py
@@ -35,7 +35,9 @@ from blinker import signal
from nikola.plugin_categories import Command
from nikola import utils
-LOGGER = utils.get_logger('new_post', utils.STDERR_HANDLER)
+POSTLOGGER = utils.get_logger('new_post', utils.STDERR_HANDLER)
+PAGELOGGER = utils.get_logger('new_page', utils.STDERR_HANDLER)
+LOGGER = POSTLOGGER
def filter_post_pages(compiler, is_post, compilers, post_pages):
@@ -57,8 +59,8 @@ def filter_post_pages(compiler, is_post, compilers, post_pages):
type_name = "post" if is_post else "page"
raise Exception("Can't find a way, using your configuration, to create "
"a {0} in format {1}. You may want to tweak "
- "COMPILERS or POSTS/PAGES in conf.py".format(
- type_name, compiler))
+ "COMPILERS or {2}S in conf.py".format(
+ type_name, compiler, type_name.upper()))
return filtered[0]
@@ -134,7 +136,7 @@ class CommandNewPost(Command):
'long': 'page',
'type': bool,
'default': False,
- 'help': 'Create a page instead of a blog post.'
+ 'help': 'Create a page instead of a blog post. (see also: `nikola new_page`)'
},
{
'name': 'title',
@@ -142,36 +144,36 @@ class CommandNewPost(Command):
'long': 'title',
'type': str,
'default': '',
- 'help': 'Title for the page/post.'
+ 'help': 'Title for the post.'
},
{
'name': 'tags',
'long': 'tags',
'type': str,
'default': '',
- 'help': 'Comma-separated tags for the page/post.'
+ 'help': 'Comma-separated tags for the post.'
},
{
'name': 'onefile',
'short': '1',
'type': bool,
'default': False,
- 'help': 'Create post with embedded metadata (single file format)'
+ 'help': 'Create the post with embedded metadata (single file format)'
},
{
'name': 'twofile',
'short': '2',
'type': bool,
'default': False,
- 'help': 'Create post with separate metadata (two file format)'
+ 'help': 'Create the post with separate metadata (two file format)'
},
{
- 'name': 'post_format',
+ 'name': 'content_format',
'short': 'f',
'long': 'format',
'type': str,
'default': '',
- 'help': 'Markup format for post, one of rest, markdown, wiki, '
+ 'help': 'Markup format for the post, one of rest, markdown, wiki, '
'bbcode, html, textile, txt2tags',
},
{
@@ -179,13 +181,14 @@ class CommandNewPost(Command):
'short': 's',
'type': bool,
'default': False,
- 'help': 'Schedule post based on recurrence rule'
+ 'help': 'Schedule the post based on recurrence rule'
},
]
def _execute(self, options, args):
"""Create a new post or page."""
+ global LOGGER
compiler_names = [p.name for p in
self.site.plugin_manager.getPluginsOfCategory(
"PageCompiler")]
@@ -198,38 +201,46 @@ class CommandNewPost(Command):
else:
path = None
+ # Even though stuff was split into `new_page`, it’s easier to do it
+ # here not to duplicate the code.
is_page = options.get('is_page', False)
is_post = not is_page
+ content_type = 'page' if is_page else 'post'
title = options['title'] or None
tags = options['tags']
onefile = options['onefile']
twofile = options['twofile']
+ if is_page:
+ LOGGER = PAGELOGGER
+ else:
+ LOGGER = POSTLOGGER
+
if twofile:
onefile = False
if not onefile and not twofile:
onefile = self.site.config.get('ONE_FILE_POSTS', True)
- post_format = options['post_format']
+ content_format = options['content_format']
- if not post_format: # Issue #400
- post_format = get_default_compiler(
+ if not content_format: # Issue #400
+ content_format = get_default_compiler(
is_post,
self.site.config['COMPILERS'],
self.site.config['post_pages'])
- if post_format not in compiler_names:
- LOGGER.error("Unknown post format " + post_format)
+ if content_format not in compiler_names:
+ LOGGER.error("Unknown {0} format {1}".format(content_type, content_format))
return
compiler_plugin = self.site.plugin_manager.getPluginByName(
- post_format, "PageCompiler").plugin_object
+ content_format, "PageCompiler").plugin_object
# Guess where we should put this
- entry = filter_post_pages(post_format, is_post,
+ entry = filter_post_pages(content_format, is_post,
self.site.config['COMPILERS'],
self.site.config['post_pages'])
- print("Creating New Post")
+ print("Creating New {0}".format(content_type.title()))
print("-----------------\n")
if title is None:
print("Enter title: ", end='')
@@ -247,7 +258,7 @@ class CommandNewPost(Command):
if isinstance(path, utils.bytes_str):
path = path.decode(sys.stdin.encoding)
slug = utils.slugify(os.path.splitext(os.path.basename(path))[0])
- # Calculate the date to use for the post
+ # Calculate the date to use for the content
schedule = options['schedule'] or self.site.config['SCHEDULE_ALL']
rule = self.site.config['SCHEDULE_RULE']
force_today = self.site.config['SCHEDULE_FORCE_TODAY']
@@ -275,7 +286,7 @@ class CommandNewPost(Command):
metadata = self.site.config['ADDITIONAL_METADATA']
compiler_plugin.create_post(
txt_path, onefile, title=title,
- slug=slug, date=date, tags=tags, **metadata)
+ slug=slug, date=date, tags=tags, is_page=is_page, **metadata)
event = dict(path=txt_path)
@@ -283,9 +294,9 @@ class CommandNewPost(Command):
with codecs.open(meta_path, "wb+", "utf8") as fd:
fd.write('\n'.join(data))
with codecs.open(txt_path, "wb+", "utf8") as fd:
- fd.write("Write your post here.")
- LOGGER.notice("Your post's metadata is at: {0}".format(meta_path))
+ fd.write("Write your {0} here.".format(content_type))
+ LOGGER.info("Your {0}'s metadata is at: {1}".format(content_type, meta_path))
event['meta_path'] = meta_path
- LOGGER.notice("Your post's text is at: {0}".format(txt_path))
+ LOGGER.info("Your {0}'s text is at: {1}".format(content_type, txt_path))
- signal('new_post').send(self, **event)
+ signal('new_' + content_type).send(self, **event)
diff --git a/nikola/plugins/command/planetoid/__init__.py b/nikola/plugins/command/planetoid/__init__.py
index ff5dd13..fe1a59b 100644
--- a/nikola/plugins/command/planetoid/__init__.py
+++ b/nikola/plugins/command/planetoid/__init__.py
@@ -162,12 +162,12 @@ class Planetoid(Command, Task):
# TODO: log failure
return
if parsed.feed.get('title'):
- LOGGER.notice(parsed.feed.title)
+ LOGGER.info(parsed.feed.title)
else:
- LOGGER.notice(feed.url)
+ LOGGER.info(feed.url)
feed.etag = parsed.get('etag', 'foo')
modified = tuple(parsed.get('date_parsed', (1970, 1, 1)))[:6]
- LOGGER.notice("==========>", modified)
+ LOGGER.info("==========>", modified)
modified = datetime.datetime(*modified)
feed.last_modified = modified
feed.save()
@@ -176,14 +176,14 @@ class Planetoid(Command, Task):
# TODO log failure
return
for entry_data in parsed.entries:
- LOGGER.notice("=========================================")
+ LOGGER.info("=========================================")
date = entry_data.get('published_parsed', None)
if date is None:
date = entry_data.get('updated_parsed', None)
if date is None:
LOGGER.error("Can't parse date from:\n", entry_data)
return False
- LOGGER.notice("DATE:===>", date)
+ LOGGER.info("DATE:===>", date)
date = datetime.datetime(*(date[:6]))
title = "%s: %s" % (feed.name, entry_data.get('title', 'Sin título'))
content = entry_data.get('content', None)
@@ -195,9 +195,9 @@ class Planetoid(Command, Task):
content = entry_data.get('summary', 'Sin contenido')
guid = str(entry_data.get('guid', entry_data.link))
link = entry_data.link
- LOGGER.notice(repr([date, title]))
+ LOGGER.info(repr([date, title]))
e = list(Entry.select().where(Entry.guid == guid))
- LOGGER.notice(
+ LOGGER.info(
repr(dict(
date=date,
title=title,
diff --git a/nikola/plugins/command/serve.py b/nikola/plugins/command/serve.py
index 2dd15c1..f27d1f7 100644
--- a/nikola/plugins/command/serve.py
+++ b/nikola/plugins/command/serve.py
@@ -26,6 +26,7 @@
from __future__ import print_function
import os
+import webbrowser
try:
from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
@@ -37,7 +38,7 @@ from nikola.plugin_categories import Command
from nikola.utils import get_logger
-class CommandBuild(Command):
+class CommandServe(Command):
"""Start test server."""
name = "serve"
@@ -57,11 +58,19 @@ class CommandBuild(Command):
{
'name': 'address',
'short': 'a',
- 'long': '--address',
+ 'long': 'address',
'type': str,
'default': '127.0.0.1',
'help': 'Address to bind (default: 127.0.0.1)',
},
+ {
+ 'name': 'browser',
+ 'short': 'b',
+ 'long': 'browser',
+ 'type': bool,
+ 'default': False,
+ 'help': 'Open the test server in a web browser',
+ }
)
def _execute(self, options, args):
@@ -75,7 +84,11 @@ class CommandBuild(Command):
httpd = HTTPServer((options['address'], options['port']),
OurHTTPRequestHandler)
sa = httpd.socket.getsockname()
- self.logger.notice("Serving HTTP on {0} port {1} ...".format(*sa))
+ self.logger.info("Serving HTTP on {0} port {1} ...".format(*sa))
+ if options['browser']:
+ server_url = "http://{0}:{1}/".format(options['address'], options['port'])
+ self.logger.info("Opening {0} in the default web browser ...".format(server_url))
+ webbrowser.open(server_url)
httpd.serve_forever()