PK ! H] blogger_cli/__init__.pyimport os
__version__ = '1.0.1'
ROOT_DIR = os.path.join(os.path.split(__file__)[0])
RESOURCE_DIR = os.path.join(os.path.split(__file__)[0], 'resources')
HOME = os.path.expanduser('~')
CONFIG_DIR = os.path.join(HOME, '.config', 'blogger_cli')
PK ! $ blogger_cli/blog_manager/__init__.pyPK ! o! ! $ blogger_cli/blog_manager/add_post.pyimport os
import jinja2
import json
import shutil
from pathlib import Path
from collections import OrderedDict
from pkg_resources import resource_string
from bs4 import BeautifulSoup as BS
def add(ctx, filename_meta):
filename, meta = Path(filename_meta[0]), filename_meta[1]
ctx.log(":: Resolving", filename)
destination_dir = Path(ctx.conversion['destination_dir'])
file_path = destination_dir / filename
topic = os.path.dirname(str(filename))
meta['topic'] = topic
snippet = get_snippet_content_map(ctx, meta)
snippet['link'] = str(filename)
html_page = insert_html_snippets(ctx, file_path, meta, snippet)
ctx.log(":: Writing finished html to", filename)
file_path.write_text(html_page, encoding='utf-8')
update_posts_index(ctx, snippet, meta)
def get_snippet_content_map(ctx, meta):
templates_dir = ctx.conversion.get('templates_dir')
snippet_names = [
'layout','disqus', 'css', 'li_tag', 'google_analytics',
'navbar_data', 'navbar', 'js', 'mathjax', 'light_theme',
'dark_theme',
]
snippet_content_map = {}
for snippet in snippet_names:
file_name = snippet + '.html'
file_content = get_internal_resource(file_name)
snippet_content_map[snippet] = file_content
if templates_dir:
template_files = Path(templates_dir).glob('*')
all_filenames = [i.resolve() for i in template_files if i.is_file()]
html_filenames = [i for i in all_filenames if i.suffix == '.html']
for file in html_filenames:
custom_snippet = os.path.splitext(str(file.name))[0]
snippet_content_map[custom_snippet] = file.read_text(encoding='utf-8')
resolve_templates(ctx, snippet_content_map, meta)
return snippet_content_map
def read_file(file_path):
with open(file_path, 'r', encoding='utf-8') as rf:
content = rf.read()
return content
def get_internal_resource(file_name):
internal_resource_path = 'resources/' + file_name
file_content = resource_string('blogger_cli', internal_resource_path)
return file_content.decode('utf-8')
def insert_html_snippets(ctx, file_path, meta, snippet_content_map):
iscode = ctx.conversion['iscode']
ctx.log(":: Inserting html_snippets to file")
html_body = file_path.read_text(encoding='utf-8')
ctx.vlog(":: iscode =", iscode)
if not iscode:
snippet_content_map['css'] = ''
snippet_content_map['mathjax'] = ''
snippet_content_map['dark_theme'] = ''
snippet_content_map['light_theme'] = ''
snippet_content_map['body'] = html_body
snippet_content_map['title'] = get_page_title(ctx, html_body)
layout = snippet_content_map['layout']
template = jinja2.Template(layout)
snippet_content_map.pop('layout')
final_page = template.render(snippet=snippet_content_map, meta=meta)
if iscode:
html_page = insert_prettyprint_class(ctx, final_page)
return html_page
def insert_prettyprint_class(ctx, html_page):
soup = BS(html_page, features='html.parser')
pre_tags = soup.find_all('pre')
if not pre_tags:
ctx.log(":: WARNING: No pre tags found in code")
return html_page
for pre_tag in pre_tags:
pre_tag['class'] = 'prettyprint'
return soup.prettify(formatter='html')
def resolve_templates(ctx, snippet_content_map, meta):
config = dict()
blog = ctx.current_blog
topic = meta.get('topic')
navbar_dict = get_navbar_dict(ctx, snippet_content_map, topic)
layout_renderer_map = {
'disqus': config,
'google_analytics': config,
'navbar': navbar_dict
}
config_names = ['disqus_username', 'google_analytics_id']
for config_name in config_names:
config_key = blog + ":" + config_name
config[config_name] = ctx.config.read(key=config_key)
exclude_snippet = ['layout', 'li_tag']
for snippet, content in snippet_content_map.items():
if snippet not in exclude_snippet:
renderer = layout_renderer_map.get(snippet)
content_template = jinja2.Template(content)
html_snippet = content_template.render(config=renderer, meta=meta)
snippet_content_map[snippet] = html_snippet
def get_navbar_dict(ctx, snippet_content_map, topic):
try:
navbar_dict = json.loads(snippet_content_map['navbar_data'],
object_pairs_hook=OrderedDict)
except Exception as E:
ctx.log("Couldnot parse your custom navbar", E)
raise SystemExit("ERROR: INVALID NAVBAR TEMPLATE")
if topic:
for nav_topic, nav_link in navbar_dict.items():
nav_link = '../' + nav_link
navbar_dict[nav_topic] = nav_link
return navbar_dict
def get_page_title(ctx, page):
current_blog = ctx.current_blog
filter_ = ctx.config.read(key=current_blog +':filter_post_without_title')
if filter_ in ['true', 'True']:
ctx.log(":: Filtering this post as it doesnot have title")
current_blog = ''
soup = BS(page, 'html.parser')
try:
title = soup.find_all('h1')[0].contents[0]
if title is None:
title = current_blog
except IndexError:
title = current_blog
title = title.strip()
ctx.log(":: Got page title as", title)
return title
def update_posts_index(ctx, snippet_content_map, meta):
post_li_tag_div = prepare_post_list(meta, snippet_content_map)
destination_dir = ctx.conversion['destination_dir']
index_path = os.path.join(destination_dir, 'index.html')
index_div_class = 'posts_list'
index_class = ctx.config.read(key=ctx.current_blog + ': index_div_name')
if index_class:
index_div_class = index_class
topic = meta['topic']
if not os.path.exists(index_path):
ctx.log("Cannot find index file in", index_path)
ctx.log("WARNING: NO INDEX FILE. \nSEE blogger export --help")
return None
soup = BS(read_file(index_path), features='html.parser')
posts_list_div = soup.find('div', class_=index_div_class)
if not posts_list_div:
ctx.log("Cannot update blog index. No div with", index_div_class, "class")
ctx.log("WARNING: INVALID INDEX.", index_path)
return None
ctx.log(":: Updating index file at", index_path)
if topic and post_li_tag_div:
update_under_topic(posts_list_div, post_li_tag_div, topic)
ctx.log(":: Linking under topic", topic)
elif post_li_tag_div:
update_without_topic(posts_list_div, post_li_tag_div)
snippet = snippet_content_map
ctx.log(":: File link and title", snippet['link'], '->', snippet['title'])
with open(index_path, 'w', encoding='utf8') as wf:
wf.write(soup.prettify(formatter='html'))
ctx.log("Index successfully updated\n")
def update_under_topic(posts_list_div, post_li_tag_div, topic):
div_topic = 'meta-' + topic
topic_tag = posts_list_div.find('div', class_=div_topic)
if topic_tag:
file_link = post_li_tag_div.ul.li.a['href']
topic_tag = check_and_remove_duplicate_tag(topic_tag, file_link)
ul_tag = post_li_tag_div.ul.extract()
topic_tag.append(ul_tag)
else:
post_li_tag_div['class'] = div_topic
h3_soup = BS('
'+topic+'
\n', features='html.parser')
h3_tag = h3_soup.find('h3')
post_li_tag_div.insert(0, h3_tag)
posts_list_div.insert(0, post_li_tag_div)
def update_without_topic(posts_list_div, post_li_tag):
file_link = post_li_tag.ul.li.a['href']
posts_list_div = check_and_remove_duplicate_tag(posts_list_div, file_link)
li_tag_ul = post_li_tag.ul.extract()
posts_list_div.insert(0, li_tag_ul)
def prepare_post_list(meta, snippet):
if not snippet['title']:
return None
li_tag_layout = snippet['li_tag']
li_tag_template = jinja2.Template(li_tag_layout)
li_tag_html = li_tag_template.render(meta=meta,
snippet=snippet)
li_tag = BS(li_tag_html, features='html.parser').find('li')
new_div = '''\
'''
new_div_tag = BS(new_div, features='html.parser').find('div')
new_div_tag.ul.append(li_tag)
return new_div_tag
def check_and_remove_duplicate_tag(div_tag, file_link):
try:
ul_tags = div_tag.find_all('ul')
except AttributeError:
return div_tag
for ul_tag in ul_tags:
li_tag_link = ul_tag.li.a['href']
if li_tag_link == file_link:
ul_tag.decompose()
return div_tag
PK ! %
blogger_cli/cli.pyimport os
import sys
import click
from blogger_cli.cli_utils.json_writer import Config
from blogger_cli import CONFIG_DIR
class Context(object):
def __init__(self):
self.verbose = False
config_path = os.path.join(CONFIG_DIR, 'blog_config.cfg')
self.config = Config(config_path,
backup_dir='~/.blogger/backup/')
self.blog_list = self.config.read(all_keys=True)
self.config_keys = [
'google_analytics_id', 'disqus_username', 'blog_images_dir',
'templates_dir', 'blog_posts_dir', 'default',
'working_dir', 'blog_dir'
]
self.optional_config = [
'meta_format', 'post_extract_list', 'index_div_name',
'filter_post_without_title', 'working_dir_timestamp',
'create_nbdata_file', 'delete_ipynb_meta'
]
self.SUPPORTED_EXTENSIONS = ['md', 'ipynb', 'html']
self.current_blog = ''
def log(self, msg, *args):
"""Logs a message to stderr."""
if args:
for arg in args:
msg += ' ' + str(arg)
click.echo(msg, file=sys.stderr)
def vlog(self, msg, *args):
"""Logs a message to stderr only if verbose is enabled."""
if self.verbose:
self.log(msg, *args)
def blog_exists(self, blog):
return False if not self.config.read(key=blog) else True
@property
def default_blog(self):
cfg = self.config.get_dict()
for i in cfg:
if 'default' in cfg[i]:
return i
pass_context = click.make_pass_decorator(Context, ensure=True)
cmd_folder = os.path.abspath(os.path.join(os.path.dirname(__file__),
'commands'))
class ComplexCLI(click.MultiCommand):
def list_commands(self, ctx):
rv = []
for filename in os.listdir(cmd_folder):
if filename.endswith('.py') and \
filename.startswith('cmd_'):
rv.append(filename[4:-3])
rv.sort()
return rv
def get_command(self, ctx, name):
try:
if sys.version_info[0] == 2:
name = name.encode('ascii', 'replace')
mod = __import__('blogger_cli.commands.cmd_' + name,
None, None, ['cli'])
except ImportError as e:
print(e)
return
return mod.cli
@click.command(cls=ComplexCLI)
@click.option('-v', '--verbose', is_flag=True,
help='enables verbose command')
@pass_context
def cli(ctx, verbose):
'''
A CLI tool to maintain your blogger blog. Sync, convert and upload :).
'''
ctx.verbose = verbose
ctx.vlog("Started the main command")
if __name__ == "__main__":
cli()
PK ! ! blogger_cli/cli_utils/__init__.pyPK ! RO RO % blogger_cli/cli_utils/installation.py"""
This script will install blogger-cli and its dependencies
in isolation from the rest of the system.
It does, in order:
- Downloads the latest stable (or pre-release) version of blogger-cli.
- Downloads all its dependencies in the blogger-cli/venv directory.
- Copies it and all extra files in $BLOGGER_CLI_HOME.
- Updates the PATH in a system-specific way.
There will be a `blogger` script that will be installed in $BLOGGER_CLI_HOME/bin
which will act as the blogger command but is slightly different in the sense
that it will use the current Python installation.
What this means is that one blogger-cli installation can serve for multiple
Python versions.
"""
import argparse
import json
import os
import platform
import re
import shutil
import stat
import subprocess
import sys
import tarfile
import tempfile
from contextlib import closing
from contextlib import contextmanager
from functools import cmp_to_key
from gzip import GzipFile
from io import UnsupportedOperation
try:
from urllib.error import HTTPError
from urllib.request import Request
from urllib.request import urlopen
except ImportError:
from urllib2 import HTTPError
from urllib2 import Request
from urllib2 import urlopen
try:
input = raw_input
except NameError:
pass
try:
try:
import winreg
except ImportError:
import _winreg as winreg
except ImportError:
winreg = None
WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")
FOREGROUND_COLORS = {
"black": 30,
"red": 31,
"green": 32,
"yellow": 33,
"blue": 34,
"magenta": 35,
"cyan": 36,
"white": 37,
}
BACKGROUND_COLORS = {
"black": 40,
"red": 41,
"green": 42,
"yellow": 43,
"blue": 44,
"magenta": 45,
"cyan": 46,
"white": 47,
}
OPTIONS = {"bold": 1, "underscore": 4, "blink": 5, "reverse": 7, "conceal": 8}
def style(fg, bg, options):
codes = []
if fg:
codes.append(FOREGROUND_COLORS[fg])
if bg:
codes.append(BACKGROUND_COLORS[bg])
if options:
if not isinstance(options, (list, tuple)):
options = [options]
for option in options:
codes.append(OPTIONS[option])
return "\033[{}m".format(";".join(map(str, codes)))
STYLES = {
"info": style("green", None, None),
"comment": style("yellow", None, None),
"error": style("red", None, None),
"warning": style("yellow", None, None),
}
def is_decorated():
if platform.system().lower() == "windows":
return (
os.getenv("ANSICON") is not None
or "ON" == os.getenv("ConEmuANSI")
or "xterm" == os.getenv("Term")
)
if not hasattr(sys.stdout, "fileno"):
return False
try:
return os.isatty(sys.stdout.fileno())
except UnsupportedOperation:
return False
def is_interactive():
if not hasattr(sys.stdin, "fileno"):
return False
try:
return os.isatty(sys.stdin.fileno())
except UnsupportedOperation:
return False
def colorize(style, text):
if not is_decorated():
return text
return "{}{}\033[0m".format(STYLES[style], text)
def string_to_bool(value):
value = value.lower()
return value in {"true", "1", "y", "yes"}
def expanduser(path):
"""
Expand ~ and ~user constructions.
Includes a workaround for http://bugs.python.org/issue14768
"""
expanded = os.path.expanduser(path)
if path.startswith("~/") and expanded.startswith("//"):
expanded = expanded[1:]
return expanded
HOME = expanduser("~")
LINUX_HOME = os.path.join(HOME, 'local', '.blogger_cli')
WINDOWS_HOME = os.path.join(HOME, ".blogger_cli")
BLOGGER_CLI_HOME = WINDOWS_HOME if WINDOWS else LINUX_HOME
BLOGGER_CLI_BIN = os.path.join(BLOGGER_CLI_HOME, "bin")
BLOGGER_CLI_ENV = os.path.join(BLOGGER_CLI_HOME, "env")
BLOGGER_CLI_VENV = os.path.join(BLOGGER_CLI_HOME, "venv")
BLOGGER_CLI_VENV_BACKUP = os.path.join(BLOGGER_CLI_HOME, "venv-backup")
BIN = """#!{python_path}
from blogger_cli.cli import cli
if __name__ == "__main__":
cli()
"""
BAT = '@echo off\r\n{python_path} "{blogger_cli_bin}" %*\r\n'
PRE_MESSAGE = """# Welcome to {blogger-cli}!
This will download and install the latest version of {blogger-cli},
a ipynb converter and blog manager.
It will add the `blogger` command to {blogger-cli}'s bin directory, located at:
{blogger_cli_home_bin}
{platform_msg}
You can uninstall at any time by executing this script
with the --uninstall option,
and these changes will be reverted.
"""
PRE_UNINSTALL_MESSAGE = """# We are sorry to see you go!
This will uninstall {blogger-cli}.
It will remove the `blogger` command from {blogger-cli}'s bin directory, located at:
{blogger_cli_home_bin}
This will also remove {blogger-cli} from your system's PATH.
"""
PRE_MESSAGE_UNIX = """This path will then be added to your `PATH` environment variable by
modifying the profile file{plural} located at:
{rcfiles}"""
PRE_MESSAGE_WINDOWS = """This path will then be added to your `PATH` environment variable by
modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key."""
PRE_MESSAGE_NO_MODIFY_PATH = """This path needs to be in your `PATH` environment variable,
but will not be added automatically."""
POST_MESSAGE_UNIX = """{blogger-cli} is installed now. Great!
To get started you need {blogger-cli}'s bin directory ({blogger_cli_home_bin}) in your `PATH`
environment variable. Next time you log in this will be done
automatically.
You have to run blogger-cli using the 'blogger' command!
If 'blogger' command doesnot work place {linux_addition} in your bashrc/bash_profile
To configure your current shell run `source {blogger_cli_home_env}`
"""
POST_MESSAGE_WINDOWS = """{blogger-cli} is installed now. Great!
To get started you need blogger-cli's bin directory ({blogger_cli_home_bin}) in your `PATH`
environment variable. Future applications will automatically have the
correct environment, but you may need to restart your current shell.
You have to run blogger-cli using the 'blogger' command!
"""
POST_MESSAGE_WINDOWS_NO_MODIFY_PATH = """{blogger-cli} is installed now. Great!
To get started you need Blogger-cli's bin directory ({blogger_cli_home_bin}) in your `PATH`
environment variable. This has not been done automatically.
You have to run blogger-cli using the 'blogger' command!
"""
class Installer:
CURRENT_PYTHON = sys.executable
CURRENT_PYTHON_VERSION = sys.version_info[:2]
def __init__(
self,
version=None,
force=False,
accept_all=False,
):
self._version = version
self._force = force
self._modify_path = True
self._accept_all = accept_all
def run(self):
self.customize_install()
self.display_pre_message()
self.ensure_python_version()
self.ensure_home()
try:
self.install()
except subprocess.CalledProcessError as e:
print(colorize("error", "An error has occured: {}".format(str(e))))
print(e.output.decode())
return e.returncode
self.display_post_message(self._version)
return 0
def uninstall(self):
self.display_pre_uninstall_message()
if not self.customize_uninstall():
return
self.remove_home()
self.remove_from_path()
def ensure_python_version(self):
major, minor = self.CURRENT_PYTHON_VERSION
if major < 3 or minor < 5:
print("SORRY BLOGGER CLI IS NOT SUPPORTED ONLY FOR 3.5 AND ABOVE!")
sys.exit(1)
def customize_install(self):
if not self._accept_all and WINDOWS:
print("Before we start, please answer the following questions.")
print("You may simply press the Enter key to leave unchanged.")
modify_path = input("Modify PATH variable? ([y]/n) ") or "y"
if modify_path.lower() in {"n", "no"}:
self._modify_path = False
print("")
def customize_uninstall(self):
if not self._accept_all:
print()
uninstall = (
input("Are you sure you want to uninstall Blogger-cli? (y/[n]) ") or "n"
)
if uninstall.lower() not in {"y", "yes"}:
return False
print("")
return True
def ensure_home(self):
"""
Ensures that $BLOGGER_CLI_HOME exists or create it.
"""
if not os.path.exists(BLOGGER_CLI_HOME):
os.makedirs(BLOGGER_CLI_HOME, 0o755)
def remove_home(self):
"""
Removes $BLOGGER_CLI_HOME.
"""
if not os.path.exists(BLOGGER_CLI_HOME):
return
shutil.rmtree(BLOGGER_CLI_HOME)
def install(self):
"""
Installs Blogger-cli in $BLOGGER_CLI_HOME.
"""
version = self._version if self._version else 'Latest'
print("Installing version: " + colorize("info", version))
self.make_venv()
self.make_bin()
self.make_env()
self.update_path()
return 0
def make_venv(self):
"""
Packs everything into a single lib/ directory.
"""
if os.path.exists(BLOGGER_CLI_VENV_BACKUP):
shutil.rmtree(BLOGGER_CLI_VENV_BACKUP)
# Backup the current installation
if os.path.exists(BLOGGER_CLI_VENV):
shutil.copytree(BLOGGER_CLI_VENV, BLOGGER_CLI_VENV_BACKUP)
if self._force:
shutil.rmtree(BLOGGER_CLI_VENV)
try:
self._make_venv()
except Exception:
if not os.path.exists(BLOGGER_CLI_VENV_BACKUP):
raise
shutil.copytree(BLOGGER_CLI_VENV_BACKUP, BLOGGER_CLI_VENV)
shutil.rmtree(BLOGGER_CLI_VENV_BACKUP)
raise
finally:
if os.path.exists(BLOGGER_CLI_VENV_BACKUP):
shutil.rmtree(BLOGGER_CLI_VENV_BACKUP)
def _make_venv(self):
global BIN, BAT
major, minor = self.CURRENT_PYTHON_VERSION
if not os.path.exists(BLOGGER_CLI_VENV):
import venv
print("Making virtualenv in", BLOGGER_CLI_VENV)
venv.create(BLOGGER_CLI_VENV, with_pip=True)
windows_path = os.path.join(BLOGGER_CLI_VENV, 'Scripts', 'python')
linux_path = os.path.join(BLOGGER_CLI_VENV, 'bin', 'python')
new_python = windows_path if WINDOWS else linux_path
new_pip = new_python + ' -m pip'
if self._version:
install_cmd = new_pip + ' install blogger-cli==' + self._version
else:
install_cmd = new_pip + ' install blogger-cli'
BIN = BIN.format(python_path=new_python)
BAT = BAT.format(python_path=new_python,
blogger_cli_bin='{blogger_cli_bin}')
os.system(install_cmd)
def make_bin(self):
if not os.path.exists(BLOGGER_CLI_BIN):
os.mkdir(BLOGGER_CLI_BIN, 0o755)
if WINDOWS:
with open(os.path.join(BLOGGER_CLI_BIN, "blogger.bat"), "w") as f:
f.write(
BAT.format(
blogger_cli_bin=os.path.join(BLOGGER_CLI_BIN, "blogger").replace(
os.environ["USERPROFILE"], "%USERPROFILE%"
)
)
)
with open(os.path.join(BLOGGER_CLI_BIN, "blogger"), "w") as f:
f.write(BIN)
if not WINDOWS:
# Making the file executable
st = os.stat(os.path.join(BLOGGER_CLI_BIN, "blogger"))
os.chmod(os.path.join(BLOGGER_CLI_BIN, "blogger"), st.st_mode | stat.S_IEXEC)
def make_env(self):
if WINDOWS:
return
with open(os.path.join(BLOGGER_CLI_HOME, "env"), "w") as f:
f.write(self.get_export_string())
def update_path(self):
"""
Tries to update the $PATH automatically.
"""
if WINDOWS:
return self.add_to_windows_path()
# Updating any profile we can on UNIX systems
export_string = self.get_export_string()
self.linux_addition = "\n{}\n".format(export_string)
updated = []
profiles = self.get_unix_profiles()
for profile in profiles:
if not os.path.exists(profile):
continue
with open(profile, "r") as f:
content = f.read()
if self.linux_addition not in content:
with open(profile, "a") as f:
f.write(self.linux_addition)
updated.append(os.path.relpath(profile, HOME))
def add_to_windows_path(self):
try:
old_path = self.get_windows_path_var()
except WindowsError:
old_path = None
if old_path is None:
print(
colorize(
"warning",
"Unable to get the PATH value. It will not be updated automatically",
)
)
self._modify_path = False
return
new_path = BLOGGER_CLI_BIN
if BLOGGER_CLI_BIN in old_path:
old_path = old_path.replace(BLOGGER_CLI_BIN + ";", "")
if old_path:
new_path += ";"
new_path += old_path
self.set_windows_path_var(new_path)
def get_windows_path_var(self):
with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root:
with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key:
path, _ = winreg.QueryValueEx(key, "PATH")
return path
def set_windows_path_var(self, value):
import ctypes
with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root:
with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key:
winreg.SetValueEx(key, "PATH", 0, winreg.REG_EXPAND_SZ, value)
# Tell other processes to update their environment
HWND_BROADCAST = 0xFFFF
WM_SETTINGCHANGE = 0x1A
SMTO_ABORTIFHUNG = 0x0002
result = ctypes.c_long()
SendMessageTimeoutW = ctypes.windll.user32.SendMessageTimeoutW
SendMessageTimeoutW(
HWND_BROADCAST,
WM_SETTINGCHANGE,
0,
u"Environment",
SMTO_ABORTIFHUNG,
5000,
ctypes.byref(result),
)
def remove_from_path(self):
if WINDOWS:
return self.remove_from_windows_path()
return self.remove_from_unix_path()
def remove_from_windows_path(self):
path = self.get_windows_path_var()
blogger_cli_path = BLOGGER_CLI_BIN
if blogger_cli_path in path:
path = path.replace(BLOGGER_CLI_BIN + ";", "")
if blogger_cli_path in path:
path = path.replace(BLOGGER_CLI_BIN, "")
self.set_windows_path_var(path)
def remove_from_unix_path(self):
# Updating any profile we can on UNIX systems
export_string = self.get_export_string()
addition = "{}\n".format(export_string)
profiles = self.get_unix_profiles()
for profile in profiles:
if not os.path.exists(profile):
continue
with open(profile, "r") as f:
content = f.readlines()
if addition not in content:
continue
new_content = []
for line in content:
if line == addition:
if new_content and not new_content[-1].strip():
new_content = new_content[:-1]
continue
new_content.append(line)
with open(profile, "w") as f:
f.writelines(new_content)
def get_export_string(self):
path = BLOGGER_CLI_BIN.replace(os.getenv("HOME", ""), "$HOME")
export_string = 'export PATH="{}:$PATH"'.format(path)
return export_string
def get_unix_profiles(self):
profiles = [os.path.join(HOME, ".profile")]
shell = os.getenv("SHELL", "")
if "zsh" in shell:
zdotdir = os.getenv("ZDOTDIR", HOME)
profiles.append(os.path.join(zdotdir, ".zprofile"))
bash_profile = os.path.join(HOME, ".bash_profile")
if os.path.exists(bash_profile):
profiles.append(bash_profile)
return profiles
def display_pre_message(self):
if WINDOWS:
home = BLOGGER_CLI_BIN.replace(os.getenv("USERPROFILE", ""), "%USERPROFILE%")
else:
home = BLOGGER_CLI_BIN.replace(os.getenv("HOME", ""), "$HOME")
kwargs = {
"blogger-cli": colorize("info", "blogger-cli"),
"blogger_cli_home_bin": colorize("comment", home),
}
if not self._modify_path:
kwargs["platform_msg"] = PRE_MESSAGE_NO_MODIFY_PATH
else:
if WINDOWS:
kwargs["platform_msg"] = PRE_MESSAGE_WINDOWS
else:
profiles = [
colorize("comment", p.replace(os.getenv("HOME", ""), "$HOME"))
for p in self.get_unix_profiles()
]
kwargs["platform_msg"] = PRE_MESSAGE_UNIX.format(
rcfiles="\n".join(profiles), plural="s" if len(profiles) > 1 else ""
)
print(PRE_MESSAGE.format(**kwargs))
def display_pre_uninstall_message(self):
home_bin = BLOGGER_CLI_BIN
if WINDOWS:
home_bin = home_bin.replace(os.getenv("USERPROFILE", ""), "%USERPROFILE%")
else:
home_bin = home_bin.replace(os.getenv("HOME", ""), "$HOME")
kwargs = {
"blogger-cli": colorize("info", "blogger-cli"),
"blogger_cli_home_bin": colorize("comment", home_bin),
}
print(PRE_UNINSTALL_MESSAGE.format(**kwargs))
def display_post_message(self, version):
print("")
kwargs = {
"blogger-cli": colorize("info", "blogger-cli"),
"version": colorize("comment", version),
}
if WINDOWS:
message = POST_MESSAGE_WINDOWS
if not self._modify_path:
message = POST_MESSAGE_WINDOWS_NO_MODIFY_PATH
blogger_cli_home_bin = BLOGGER_CLI_BIN.replace(
os.getenv("USERPROFILE", ""), "%USERPROFILE%"
)
else:
message = POST_MESSAGE_UNIX
blogger_cli_home_bin = BLOGGER_CLI_BIN.replace(os.getenv("HOME", ""), "$HOME")
kwargs["blogger_cli_home_env"] = colorize(
"comment", BLOGGER_CLI_ENV.replace(os.getenv("HOME", ""), "$HOME")
)
kwargs['linux_addition'] = self.linux_addition
kwargs["blogger_cli_home_bin"] = colorize("comment", blogger_cli_home_bin)
print(message.format(**kwargs))
def call(self, *args):
return subprocess.check_output(args, stderr=subprocess.STDOUT)
def _get(self, url):
request = Request(url, headers={"User-Agent": "Python Blogger-cli"})
with closing(urlopen(request)) as r:
return r.read()
def main():
parser = argparse.ArgumentParser(
description="Installs the latest (or given) version of blogger-cli"
)
parser.add_argument("--version", dest="version")
parser.add_argument(
"-f", "--force", dest="force", action="store_true", default=False
)
parser.add_argument(
"-y", "--yes", dest="accept_all", action="store_true", default=False
)
parser.add_argument(
"--uninstall", dest="uninstall", action="store_true", default=False
)
args = parser.parse_args()
installer = Installer(
version=args.version or os.getenv("BLOGGER_CLI_VERSION"),
force=args.force,
accept_all=args.accept_all
or string_to_bool(os.getenv("BLOGGER_CLI_ACCEPT", "0"))
or not is_interactive(),
)
if args.uninstall or string_to_bool(os.getenv("BLOGGER_CLI_UNINSTALL", "0")):
return installer.uninstall()
return installer.run()
if __name__ == "__main__":
sys.exit(main())
PK ! `Sȴ $ blogger_cli/cli_utils/json_writer.py'''
Config manager file to quickly write app configurations
'''
import json
import datetime
import os
class Config:
'''
Config class with methods
write(key, value):
eg Config.write('parent:child:sub_child', 'value')
Config.write('key','value')
read(key=None, value=None, all_keys=False, all_values=False)
get_dict(): gives you config dict
delete_key(key): deletes a key
delete_file(backup=True): deletes the config file
'''
def __init__(self, file, backup_dir='~/.cli_backup/'):
self.file_path = os.path.abspath(os.path.expanduser(file))
self.file = os.path.basename(self.file_path)
self.file_dir = os.path.dirname(self.file_path)
self.backup_dir = os.path.abspath(os.path.expanduser(backup_dir))
# check if file exists and json readable otherwise create one
self.handle_file()
def __str__(self):
return self.file_path
def __repr__(self):
return "file: {0}, backup_dir: {1}".format(
self.file_path, self.backup_dir)
def handle_file(self):
file_exists = os.path.exists(self.file_path)
folder_exists = os.path.exists(self.file_dir)
if file_exists:
try:
self.get_dict()
except (Exception, ValueError):
file_exists = False
if not file_exists:
if not folder_exists:
os.makedirs(self.file_dir)
self.write_dict({})
def get_dict(self):
'''
Gives you the whole config dictionary from config file
'''
with open(self.file_path, 'r')as rf:
data = json.load(rf)
return data
def write_dict(self, new_dict):
'''
params: dictionary containing configs
returns : nothing
'''
with open(self.file_path, 'w')as rf:
json.dump(new_dict, rf, indent=2)
def __dict_accesor(self, dict_name, key_list):
'''
Makes multi level dict easy to access name['a']['b']['c']......
Params:
dict_name(str) = name of dictionary
key_list(list) = list of keys to use as ['key1']['key2']....
Returns:
A string like name['k1']['k2'] Maybe use it with exec/eval
'''
for i in key_list:
dict_name += "['{0}']".format(i)
return dict_name
def write(self, key, value):
'''
writes to a config file as a dictionary
params:
key:name of setting, path of setting
value: value of setting
Usage:
Config.write('user.email','a@a.com')
Config.write('
'''
def make_depth(key_list):
'''
Creates depth assuming nothing existed before
Returns:
A dictionary with all list items in nested form and last
item being assigned the given value
'''
value = None
key_list.reverse()
for key in key_list:
value = {key: value}
return value
def ensure_path(key_list, data_dict):
first_half = ''
second_half = ' = temp_dict'
temp_dict = data_dict
for index, item in enumerate(key_list):
if item in temp_dict:
temp_dict = temp_dict[item]
else:
temp_dict.update(
make_depth(key_list[index:])
)
first_half = self.__dict_accesor(
'data_dict', key_list[:index]
)
exec(first_half + second_half)
break
with open(self.file_path, 'r')as rf:
data_dict = json.load(rf)
key_list = [ i.strip() for i in key.split(":") ]
first_half = self.__dict_accesor('data_dict', key_list)
second_half = '= value'
ensure_path(key_list, data_dict)
exec(first_half + second_half)
self.write_dict(data_dict)
def read(self, key=None, value=None, all_keys=False, all_values=False):
'''
Reads and return key or value from config file
(returns config dict if no parameter)
Params:
[o] key: key of dict. Use (:) eg: key:nkey for depth
[o] value : value of dictionary to get key of
[o] all_keys [bool] : True returns all keys dict object
[o] all_values [bool]: True returns all values dict obj.
'''
# Check if more than 1 kwargs given
arguments = (key, value, all_keys, all_values)
given = [1 for i in arguments if i]
if len(given) >= 2:
raise ValueError("More than 1 arguments given")
# load the dictionary from config
configs = self.get_dict()
if all_keys:
return list(configs.keys())
elif all_values:
return list(configs.values())
elif key:
cfg_dict = configs
key_list = key.split(':')
for i in key_list:
key = i.strip()
cfg_dict = cfg_dict.get(key)
if not cfg_dict:
return None
return cfg_dict
elif value:
key = [k for k, v in configs.items() if v == value]
if len(key) == 1:
return key[0]
return key
def delete_key(self, key):
'''
Deletes a given key from the config
'''
key_list = key.split(':')
config_dict = self.get_dict()
dict_accesor = self.__dict_accesor('config_dict', key_list)
exec('del ' + dict_accesor)
self.write_dict(config_dict)
def delete_file(self, backup=True):
'''
Deletes config_file to backup_dir (both specified in Config class)
Params:
backup [boolean] : Defaults to True
'''
# manage name acc to current datetime
date_today = datetime.datetime.now()
str_format = '%d_%b_%Y_%H_%M_%S'
name = date_today.strftime(str_format) + '.cfg'
if backup:
if not os.path.exists(self.backup_dir):
os.makedirs(self.backup_dir)
new_name = os.path.join(self.backup_dir, name)
os.rename(self.file_path, new_name)
else:
os.remove(self.file_path)
PK ! blogger_cli/commands/__init__.pyPK ! # blogger_cli/commands/cmd_addblog.pyimport click
from blogger_cli.cli import pass_context
from blogger_cli.commands.cmd_setupblog import setup
@click.command('addblog', short_help="Register a new blog")
@click.argument('blog')
@click.option('-v', '--verbose', is_flag=True)
@click.option('-s', '--silent', is_flag=True,
help="Do not load the setup.")
@pass_context
def cli(ctx, blog, silent, verbose):
""" Add a new blog.\n
Usage:\n
blogger addblog blogname\n
blogger addblog -s blogname
"""
ctx.verbose = verbose
add_blog_if_valid(ctx, blog)
if not silent:
ctx.log("Running setup for", blog)
setup(ctx, blog)
ctx.log("Setup completed succesfully")
def add_blog_if_valid(ctx, blog, blog_dir=None):
layout = {
'blog_dir': blog_dir,
'blog_posts_dir': None,
'blog_images_dir':None,
'templates_dir':None,
'working_dir': None,
'google_analytics_id': None,
'disqus_username':None,
}
if not ctx.blog_exists(blog):
ctx.config.write(blog, layout)
ctx.log("Blog added succesfully")
ctx.vlog("Blog", ctx.config.read(blog))
else:
ctx.log("Blog already exists!")
ctx.vlog('Blogs', ctx.config.read(all_keys=True))
raise SystemExit(0)
PK ! Q#P
P
" blogger_cli/commands/cmd_config.pyimport json
import click
from blogger_cli.cli import pass_context
@click.command('config', short_help="Change a blog's configurations")
@click.argument('configs', required=False, nargs=-1)
@click.option('-b', '--blog', type=str, help="Name of the blog to use")
@click.option('-rm', '--remove', is_flag=True, help="Enable delete key")
@click.option('-re', '--restore', type=click.Path(exists=True), help="Restore a blog's config")
@click.option('-v', '--verbose', is_flag=True)
@pass_context
def cli(ctx, remove, blog, configs, restore, verbose):
"""
Change a blogs configurations.\n
Examples:\n
blogger config -b html_dir C:Users/foldername/\n
blogger config -b txt_dir ~/foldername/\n
Tip: You can set a defalut blog to avoid using -b option everytime!
"""
ctx.verbose = verbose
blog = __get_blog(ctx, blog)
if restore:
with open(restore, 'r') as rf:
try:
config_dict = json.load(rf)
except Exception as E:
ctx.log('ERROR: Invalid config file.', E)
raise SystemExit(0)
ctx.config.write(blog, config_dict)
ctx.log('Configurations for', blog, 'restored')
raise SystemExit(0)
if not configs:
raise SystemExit("ERROR: MISSING ARGUMENT 'CONFIG KEY'"+
" See blogger config --help")
__validate(ctx, blog, configs)
key = configs[0]
blog_dict = ctx.config.read(key=blog)
try:
value = configs[1]
ctx.config.write(blog + ":" + key, value)
ctx.log(key, "->", value)
except IndexError:
if remove:
ctx.config.delete_key(blog + ':' + key)
ctx.log(key, "->", "deleted")
else:
ctx.log(blog_dict.get(key))
raise SystemExit(0)
def __get_blog(ctx, blog):
if blog is None:
default = ctx.default_blog
if default is None:
ctx.log("Try 'blogger config --help' for help.")
raise SystemExit("\nError: Missing option -b ")
else:
ctx.vlog("\nUsing default blog ->", default)
blog = default
return blog
def __validate(ctx, blog, configs):
if len(configs) > 2:
raise SystemExit("\nInvalid input arguments")
if not ctx.blog_exists(blog):
raise SystemExit("\nBlog " + str(blog) + " doesnot exist")
key = configs[0]
blog_dict = ctx.config.read(key=blog)
allowed_keys = ctx.config_keys + ctx.optional_config
if key not in allowed_keys and key not in blog_dict:
raise SystemExit("\nInvalid config key.")
PK ! # # # blogger_cli/commands/cmd_convert.pyimport os
import shutil
import click
from pathlib import Path
from datetime import datetime
from blogger_cli.commands.convert_utils.classifier import convert_and_copyfiles
from blogger_cli.blog_manager import add_post
from blogger_cli.cli import pass_context
@click.command('convert', short_help='Convert files to html')
@click.argument('path', nargs=-1, required=False,
type=click.Path(exists=True))
@click.option('--recursive', '-r', 'recursive', is_flag=True,
help="Recusively search folder for files to convert. USE WITH CAUTION")
@click.option('--not-code', 'iscode', is_flag=True, default=True,
help="Do not add mathjax and code support")
@click.option('-o', 'destination_dir', type=click.Path(exists=True),
help="Destination for converted files,DEFAULT from blog_config")
@click.option('-b', '--blog',
help="Name of the blog")
@click.option('-ex-html', '--exclude-html', 'exclude_html', is_flag=True,
help='Ignore html files from conversion')
@click.option('--img-dir', 'img_dir', type=click.Path(exists=True),
help="Folder for post images. Default: blog's config, Destination dir")
@click.option('-no-ex', '--no-extract', 'extract_static', is_flag=True, default=True,
help="Disable resource extraction from files like images from ipynbs")
@click.option('--topic', 'topic', type=str,
help="Topic in which this post should be placed in index")
@click.option('-temp', '--template','templates_dir', type=click.Path(exists=True),
help="Folder path of custom templates")
@click.option('--override-meta','override_meta', is_flag=True,
help="Ignore meta topic in favour of --topic option")
@click.option('-v', '--verbose', is_flag=True,
help="Enable verbose flag")
@pass_context
def cli(ctx, path, iscode, blog, exclude_html, extract_static,
destination_dir, img_dir, topic, templates_dir,
recursive, override_meta, verbose):
"""
Convert from diffrent file format to html
Usage:\n
blogger convert filename.ipynb\n
blogger convert file1 file2 file3 -b blog1 -no-imgex\n
blogger convert file1 file2 file3 -b blog1 --topic Tech\n
blogger convert filename --not-code -o ~/username.github.io
blogger convert ../folder1 file1 ../folder2 -v \n
blogger convert -r ../folder1 file1 ../folder2 -v \n
blogger convert ../folder1 file1 ../folder2 --exclude-html -v
"""
ctx.verbose = verbose
set_current_blog(ctx, blog)
path = path if path else get_files_from_working_dir(ctx, recursive)
if not path:
ctx.log(":: All files synced.")
raise SystemExit(0)
resolved_files = get_files_being_converted(path, recursive=recursive)
file_ext_map = get_file_ext_map(ctx, exclude_html, resolved_files)
destination_dir = check_and_ensure_destination_dir(ctx, destination_dir)
img_dir = check_and_ensure_img_dir(ctx, destination_dir, img_dir)
templates_dir = resolve_templates_dir(ctx, templates_dir)
ctx.log("\nCONVERTING", len(file_ext_map), 'FILES')
ctx.vlog("Got files and ext:", file_ext_map, 'img_dir:', img_dir,
"templates_dir:", templates_dir)
ctx.conversion = {
'file_ext_map': file_ext_map,
'destination_dir': destination_dir,
'iscode': iscode,
'img_dir': img_dir,
'extract_static': extract_static,
'templates_dir':templates_dir,
'override_meta': override_meta,
'topic': topic
}
filenames_meta = convert_and_copyfiles(ctx)
ctx.log("Converted files successfully.\n\nADDING FILES TO BLOG")
for filename_meta in filenames_meta:
add_post.add(ctx, filename_meta)
def get_files_from_working_dir(ctx, recursive):
ctx.log("\n:: No input files given. Scanning working folder for changes..")
blog = ctx.current_blog
last_checked = ctx.config.read(key=blog + ': working_dir_timestamp')
working_dir = ctx.config.read(key=blog + ': working_dir')
working_dir = Path(str(working_dir))
if not working_dir or not working_dir.exists():
ctx.log(":: Working folder doesnot exist")
raise SystemExit(":: ERROR: No input files")
current_timestamp = datetime.today().timestamp()
ctx.config.write(blog + ':working_dir_timestamp', current_timestamp)
if not last_checked:
return str(working_dir)
try:
last_checked = float(last_checked)
except:
ctx.log("Parse error for last sync. Please convert",
"files manually or convert all files in your working_dir")
raise SystemExit("ERROR: Last sync date invalid")
def is_modified_file(path):
if not path.is_file():
return False
if path.lstat().st_mtime >= last_checked:
return True
return False
all_files = []
for item in working_dir.iterdir():
if item.is_file():
if is_modified_file(item):
all_files.append( str(item.resolve()) )
else:
continue
elif recursive:
items = item.rglob('*')
files = [str(i.resolve()) for i in items if is_modified_file(i)]
all_files += files
return all_files
def get_files_being_converted(path, recursive=False):
isfolder = lambda x: True if os.path.isdir(x) else False
all_files = []
for item in path:
if not isfolder(item):
abs_file_path = os.path.abspath(item)
all_files.append(abs_file_path)
continue
if recursive:
items = Path(item).rglob('*')
files = [ str(i.resolve()) for i in items if i.is_file() ]
all_files += files
elif not recursive:
items = get_all_files(item)
all_files += items
return set(all_files)
def get_all_files(folder):
files = []
for file in os.listdir(folder):
file_path = os.path.join(folder, file)
if os.path.isfile(file_path):
abs_file_path = os.path.abspath(file_path)
files.append(abs_file_path)
return files
def check_and_ensure_destination_dir(ctx, output_dir):
blog = ctx.current_blog
blog_dir = ctx.config.read(key=blog+': blog_dir')
posts_dir = ctx.config.read(key=blog + ' : blog_posts_dir')
if not posts_dir and not output_dir:
ctx.log("No target folder set. Specify one with -o option or",
"setup in your", blog, "blog's config")
raise SystemExit("ERROR: NO OUTPUT FOLDER")
if posts_dir:
destination_dir = os.path.join(blog_dir, posts_dir)
if output_dir:
destination_dir = output_dir
destination_dir = os.path.normpath(os.path.expanduser(destination_dir))
if not os.path.exists(destination_dir):
os.makedirs(destination_dir)
return destination_dir
def check_and_ensure_img_dir(ctx, destination_dir, output_img_dir):
blog = ctx.current_blog
blog_dir = ctx.config.read(key=blog+': blog_dir')
blog_img_dir = ctx.config.read(key=blog+': blog_images_dir')
if not blog_img_dir and not output_img_dir:
ctx.log("No images folder given. Specify one with -o option or",
"setup in your", blog, "blog's config")
ctx.log("If you want to avoid extracting images use -no-ex option.")
if click.confirm("Put images dir in same folder as blog posts?"):
img_dir = os.path.join(destination_dir, 'images')
return img_dir
else:
raise SystemExit("ERROR: NO OUTPUT FOLDER")
if blog_img_dir:
img_dir = os.path.join(blog_dir, blog_img_dir)
if output_img_dir:
img_dir = output_img_dir
img_dir = os.path.normpath(os.path.expanduser(img_dir))
if not os.path.exists(img_dir):
os.makedirs(img_dir)
return img_dir
def resolve_templates_dir(ctx, templates_dir_from_cmd):
blog = ctx.current_blog
blog_dir = ctx.config.read(key=blog+': blog_dir')
blog_templates_dir = ctx.config.read(key=blog + ': templates_dir')
templates_dir = blog_templates_dir
if templates_dir_from_cmd:
templates_dir = templates_dir_from_cmd
if templates_dir:
templates_dir = os.path.normpath(os.path.expanduser(templates_dir))
return templates_dir
def set_current_blog(ctx, blog):
current_blog = ctx.default_blog
if blog:
current_blog = blog
if not ctx.blog_exists(current_blog):
ctx.log("Blog name not given. Use --blog option or set default blog")
raise SystemExit("ERROR: Blogname unavailable. SEE blogger convert --help")
ctx.current_blog = current_blog
def get_file_ext_map(ctx, exclude_html, files_being_converted):
file_ext_map = {}
if exclude_html:
ctx.SUPPORTED_EXTENSIONS.remove('html')
for file in files_being_converted:
ext = get_file_ext(file)
if ext in ctx.SUPPORTED_EXTENSIONS:
file_ext_map[file] = ext
else:
ctx.log("Unsupported ext", ext, "Skipping")
continue
return file_ext_map
def get_file_ext(file):
extension = os.path.splitext(file)[1]
return extension[1:]
PK !
IJ " blogger_cli/commands/cmd_export.pyimport os
import click
from blogger_cli.cli import pass_context
from blogger_cli.commands.export_utils.copier import (copy_design_assets,
copy_blog_index, copy_blog_config, copy_blog_template,
copy_blog_layout)
@click.command('export', short_help="Export default design to your blog")
@click.argument('resource', type=str)
@click.option('-b', '--blog', type=str, help="Name of the blog to use")
@click.option('-o', '--to', 'relative_path',
help="Relative path from blog root")
@click.option('-v', '--verbose', is_flag=True,
help='Enable verbose flag')
@pass_context
def cli(ctx, resource, blog, relative_path, verbose):
"""
Export necessary resources to bootstrap your blog\n
Syntax: \n
blogger export [OPTION} [RESOURCE} [RELATIVE BLOG PATH}\n
Examples:\n
blogger export -b blogs/\n
Tip: You can set a defalut blog to avoid using -b option everytime!\n
blogger export assets/css/\n
RESOURCES:\n
design_assets,
blog_template,
blog_index,
blog_config
blog_layout
"""
ctx.verbose = verbose
validate_blog_and_settings(ctx, blog, relative_path)
export_path = resolve_export_path(ctx, relative_path)
resource_map = {
'design_assets': copy_design_assets,
'blog_template': copy_blog_template,
'blog_index': copy_blog_index,
'blog_config': copy_blog_config,
'blog_layout':copy_blog_layout,
}
transfer = resource_map.get(resource)
if not transfer:
ctx.log("No such resource. See blogger export --help")
raise SystemExit("ERROR: INVALID RESOURCE NAME")
ctx.vlog("Using function", transfer)
transfer(ctx, export_path)
def validate_blog_and_settings(ctx, input_blog, input_path):
blog = ctx.default_blog
if input_blog:
blog = input_blog
blog_exists = ctx.blog_exists(blog)
if blog_exists:
settings = ctx.config.read(key=blog + ': blog_dir')
if not settings and not input_path:
ctx.log("Set config value for blog_dir in", blog, "blog")
raise SystemExit("ERROR: MISSING CONFIG: blog_dir")
elif not blog or not blog_exists:
ctx.log("Pass a correct blog with -b option",
"or set a default blog")
raise SystemExit("ERROR: INVALID_BLOG_NAME: " + str(blog))
ctx.current_blog = blog
def resolve_export_path(ctx, relative_path):
blog = ctx.current_blog
blog_dir = ctx.config.read(key=blog+': blog_dir')
if not blog_dir:
if not os.path.exists(os.path.dirname(relative_path)):
ctx.log("You have used relative path", relative_path,
'without setting blog_dir value in config!')
raise SystemExit("Use full path to export in this folder")
else:
blog_dir = ''
blog_dir = os.path.normpath(os.path.expanduser((blog_dir)))
export_path = blog_dir
if relative_path:
export_path = os.path.join(blog_dir, relative_path)
try:
os.makedirs(export_path)
except FileExistsError:
pass
ctx.vlog("Got export path", export_path)
return export_path
PK ! hS S blogger_cli/commands/cmd_info.pyfrom itertools import zip_longest
import click
from blogger_cli import __version__
from blogger_cli.cli import pass_context
@click.command('info', short_help="Show blog's properties")
@click.argument('blog', required=False)
@click.option('--all', 'show_all', is_flag=True)
@click.option('-V', '--version', is_flag=True,
help='Show version of blogger-cli and exit')
@click.option('-v', '--verbose', is_flag=True)
@pass_context
def cli(ctx, blog, show_all, version, verbose):
"""
Get details about blogs and app itself\n
Usage:\n
bloggger info\n
blogger info
"""
ctx.verbose = verbose
blog_exists = ctx.blog_exists(blog)
if version:
ctx.log(__version__)
raise SystemExit(0)
ctx.log("\nBlogger-cli version:", __version__)
if blog and not blog_exists:
ctx.log('Invalid blog name. No such blog', blog)
elif not blog:
# List all blogs
ctx.log("\nRegistered Blogs:")
for i in ctx.blog_list:
default = ctx.default_blog
ctx.log(' ', i) if i != default else ctx.log(' ', i, '[default]')
if len(ctx.blog_list) == 0:
ctx.log(' ', "No blog registered yet!")
ctx.log("\nBlog:configs [standard]")
for key in ctx.config_keys:
ctx.log(' ', key)
if show_all:
ctx.log("\nOptional:configs [Advanced]")
for key in ctx.optional_config:
ctx.log(' ', key)
ctx.log("\nTip: Use blogger info blogname for blog details\n")
else:
if blog == ctx.default_blog:
ctx.log("\nBlog Name:", blog, '[Default]')
else:
ctx.log("\nBlog Name:", blog)
ctx.log('Configurations:')
blog_dict = ctx.config.read(blog)
for k, v in sorted(blog_dict.items()):
ctx.log(' ', k, "->", v)
PK ! s " blogger_cli/commands/cmd_rmblog.pyimport click
from blogger_cli.cli import pass_context
@click.command('rmblog', short_help="Remove a blog")
@click.argument('blog')
@click.option('-v', '--verbose', is_flag=True)
@pass_context
def cli(ctx, blog, verbose):
""" Remove a blog"""
ctx.verbose = verbose
if not ctx.blog_exists(blog):
ctx.log("Blog doesnot exist! so not removed")
ctx.vlog('Blogs: ', ctx.blog_list)
else:
ctx.config.delete_key(blog)
ctx.log("Blog removed succesfully")
PK ! 5 5 ! blogger_cli/commands/cmd_serve.pyimport os
import socketserver
import http.server
import click
from blogger_cli.cli import pass_context
@click.command('serve', short_help="Serve your blog locally")
@click.argument('blog', required=False )
@click.option('--port', '-p', 'port', type=int, default=8000)
@click.option('-d', '--dir', 'dir',
help="Folder path to serve. Default: blog_dir")
@click.option('-v', '--verbose', is_flag=True,
help='Enable verbosity')
@pass_context
def cli(ctx, blog, port, dir, verbose):
ctx.verbose = verbose
ctx.vlog("\n:: GOT blog:", blog, "port:", str(port), "(", type(port), ")")
if not blog:
blog = ctx.default_blog
ctx.vlog(":: No blog name given using default blog:", str(blog))
if not blog:
ctx.log("Use blogger serve or set a default blog in configs")
raise SystemExit("ERROR: Missing required argument 'BLOG' ")
blog_dir = ctx.config.read(key=blog + ':blog_dir')
if dir:
blog_dir = dir
if not blog_dir:
ctx.log("CNo blog_dir set blog_dir in config or use --dir option")
raise SystemExit("ERROR: No folder specified")
blog_dir = os.path.abspath(os.path.expanduser(blog_dir))
ctx.vlog(":: Got blog_dir:", blog_dir)
ctx.log(":: Serving at http://localhost:" + str(port) + '/')
ctx.log(":: Exit using CTRL+C")
serve_locally(blog_dir, port)
def serve_locally(dir, PORT):
web_dir = dir
os.chdir(web_dir)
Handler = http.server.SimpleHTTPRequestHandler
httpd = socketserver.TCPServer(("", PORT), Handler)
httpd.serve_forever()
PK ! 8 8 &