PK ѻF : : cahier/io.py#!/usr/bin/env python3
# Copyright 2014-2015 Louis Paternault
#
# Cahier is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Cahier is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with Cahier. If not, see .
"""Input/Output utils."""
import datetime
def ask(message, choices=None, default=None, casesensitive=True):
"""Ask for user input, and return it.
Arguments:
- message: message to print;
- default: returned value is no input is given;
- choices: if not None, keep asking user until input is an item of it;
- casesensitive: if True, choices argument is case sensitive.
"""
if choices:
message += " ({})".format(", ".join(choices))
else:
choices = []
if default:
print("TAGADA")
message += " [{default}]".format(
default=default,
)
message += ": "
if casesensitive:
lc_choices = {}
else:
lc_choices = {s.lower():s for s in choices}
while True:
try:
choice = input(message)
except EOFError:
print()
raise
if choice == "" and default:
return default
if not choices:
return choice
if choice in choices:
return choice
if (not casesensitive) and (choice in lc_choices):
return lc_choices[choice]
def ask_date(message, default):
"""Ask for user input, which is expected to be a date.
This function keeps asking until valid date is provided.
Arguments:
- message: message displayed to user;
- default: default date to return if no input is given.
"""
message += " (format: 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS')"
while True:
proposition = ask(
message,
default=default.strftime("%Y-%m-%d"),
)
try:
date = datetime.datetime.strptime(
proposition,
"%Y-%m-%d %H:%M:%S",
)
break
except ValueError:
try:
date = datetime.datetime.strptime(
proposition,
"%Y-%m-%d",
)
break
except ValueError:
pass
return date
def ask_yesno(message, default):
"""Ask for yes or no."""
if default:
default = 'yes'
else:
default = 'no'
while True:
proposition = ask(
message,
default=default,
)
if 'yes'.startswith(proposition.lower()):
return True
if 'no'.startswith(proposition.lower()):
return False
PK ʻF: cahier/config.py#!/usr/bin/env python3
# Copyright 2014-2015 Louis Paternault
#
# Cahier is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Cahier is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with Cahier. If not, see .
"""Management of configuration files."""
import configparser
import os
import shlex
from cahier import errors
class ConfigurationError(errors.CahierError):
"""Error in configuration files."""
def __init__(self, filename, message):
super(ConfigurationError, self).__init__()
self.filename = filename
self.message = message
def __str__(self):
return "Configuration error ({filename}): {message}".format(
filename=self.filename,
message=self.message,
)
def config_assert_has(config, name, section, option):
"""Assert that 'config' as 'section' defined, with 'option' key in it.
Raise a ConfigurationError() if not.
"""
if not config.has_section(section):
raise ConfigurationError(
filename=name,
message=(
"Missing section {section} in file {filename}."
).format(section=section, filename=name),
)
if option and (not config.has_option(section, option)):
raise ConfigurationError(
filename=name,
message=(
"Missing key {key} in section {section} in file {filename}."
).format(key=option, section=section, filename=name)
)
def load_cahierrc(filename):
"""Load ~/.cahier/cahier.cfg, and return it as a configparser object."""
config = configparser.ConfigParser()
config.read_dict({
'options': {
'casesensitive': "True",
},
'bin': {
'editor': "$EDITOR",
},
'wiki': {
'extension': "mdwn",
},
})
config.read([filename])
if config.has_section('wiki'):
if 'fileformat' not in config['wiki'].keys():
raise ConfigurationError(
filename=filename,
message="missing key 'fileformat' in section 'wiki'.",
)
else:
raise ConfigurationError(
filename=filename,
message="missing section 'wiki'.",
)
return config
def load_ftplugin(filename):
"""Load ftplugin.
Return plugin 'filename' as a configparser object.
"""
# Reading file
config = configparser.ConfigParser()
config.read_dict({
'preprocess': {},
})
config.read([filename])
# Checking arguments
for key in config['preprocess']:
if key == 'name' or key.startswith('cmd'):
continue
raise ConfigurationError(
filename=filename,
message=(
""""{key}" key (in section {section}) must """
"""be 'name' or start with 'cmd'."""
).format(key=key, section="preprocess")
)
return config
def load_profiles(dirname):
"""Load profiles of directory 'dirname'.
Return a dictionary of profiles, as configparser objects indexed by
basenames.
"""
profiles = {}
for root, __ignored, files in os.walk(dirname):
for filename in files:
if filename.endswith('.cfg'):
# Preprocessing
basename = filename[:-len('.cfg')]
fullname = os.path.join(root, filename)
# Reading configuration
config = configparser.ConfigParser()
config.read_dict({
'options': {
'workdays': "",
},
})
config.read([fullname])
# Checking for compulsory arguments
config_assert_has(config, fullname, 'directories', 'calendar')
config_assert_has(config, fullname, 'config', 'setup')
workdays = shlex.split(config['options']['workdays'])
if len(workdays) != len(set([
day.split(':')[0]
for day
in workdays
])):
raise ConfigurationError(
fullname,
(
"Only one item by day is allowed in "
"key 'workdays' of section 'options'."
),
)
profiles[basename] = config
return profiles
PK FDS
d d cahier/__init__.py#!/usr/bin/env python3
# Copyright 2014-2015 Louis Paternault
#
# Cahier is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Cahier is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with Cahier. If not, see .
"""Aide à la gestion de cahier de texte.
"""
import os
VERSION = "0.1.1"
CAHIERRC = os.path.expanduser('~/.cahier')
__COPYRIGHT__ = "(C) 2014-2015 Louis Paternault. GNU GPL 3 or later."
PK ػF cahier/errors.py#!/usr/bin/env python3
# Copyright 2014-2015 Louis Paternault
#
# Cahier is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Cahier is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with Cahier. If not, see .
"""Error definition."""
class CahierError(Exception):
"""General exception. Other custom exception should inherit from it."""
def __init__(self, message):
super().__init__()
self.message = message
def __str__(self):
return self.message
class UnknownProfile(CahierError):
"""Profile cannot be found."""
def __init__(self, profile):
super().__init__()
self.profile = profile
def __str__(self):
return """No profile named "{profile}".""".format(profile=self.profile)
PK F[ cahier/path.py#!/usr/bin/env python3
# Copyright 2014-2015 Louis Paternault
#
# Cahier is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Cahier is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with Cahier. If not, see .
"""Path utils."""
import os
import cahier
def expand(line, filename):
"""Return a line of a configuration file, while processing 'filename'.
The line is formatted with the following variables:
- configdir: configuration directory (something liki '~/.cahier');
- basename: basename of 'filename' (without extension);
- filename: same, with extension;
- dirname: directory of 'filename'.
"""
extension = filename.split('.')[-1]
if extension == filename:
extension = ""
else:
extension = '.{}'.format(extension)
return os.path.expandvars(os.path.expanduser(line.format(
configdir=cahier.CAHIERRC,
basename=filename[0:-len(extension)],
filename=filename,
dirname=absfullpath(os.path.dirname(filename)),
)))
def absfullpath(path):
"""Return an absolute version of 'path'.
User directory ('~') and environment variables are replaced by their values.
"""
return os.path.abspath(os.path.expanduser(os.path.expandvars(path)))
PK F_ʙ cahier/main.py#!/usr/bin/env python3
# Copyright 2014-2015 Louis Paternault
#
# Cahier is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Cahier is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with Cahier. If not, see .
"""Fonction principale"""
import argparse
import logging
import os
import shlex
import sys
from cahier import errors
import cahier
import cahier.cmd_plugins
import cahier.config
import cahier.io
import cahier.path
LOGGER = logging.getLogger(cahier.__name__)
LOGGER.addHandler(logging.StreamHandler())
def select_profile(option_profile, profiles, confirm, casesensitive=False):
"""Return the selected profile.
Arguments:
- option_profile: profile set in command line arguments (None if none
was given).
- profiles: dictionary of profiles, as returned by
cahier.config.load_profiles().
- confirm (boolean): True iff force user to confirm (unless profile is
set).
- casesensitive: is profile chooser case sensitive?
"""
# pylint: disable=too-many-branches
# Is command line argument profile a valid one?
if option_profile:
for item in profiles.keys():
if option_profile.lower() == item.lower():
return item
raise errors.UnknownProfile(option_profile)
# Looking for matching paths in profile configuration
cwd = os.path.abspath(os.getcwd())
match = {}
for (profile, config) in profiles.items():
if config.has_option('directories', "sources"):
for path in shlex.split(config['directories']['sources']):
fullpath = cahier.path.absfullpath(path)
if cwd.startswith(fullpath):
if profile in match:
if len(fullpath) > match[profile]:
match[profile] = len(fullpath)
else:
match[profile] = len(fullpath)
match = [t[0] for t in sorted(match.items(), key=lambda t: -t[1])]
# match now contains the list of matching directories. If only one matches,
# it is the one. Else, user has to choose.
if len(match) == 0:
return cahier.io.ask(
"Select profile",
choices=profiles.keys(),
default=None,
casesensitive=casesensitive,
)
elif len(match) == 1:
if confirm:
return cahier.io.ask(
"Select profile",
choices=profiles.keys(),
default=match[0],
casesensitive=casesensitive,
)
else:
return match[0]
else:
return cahier.io.ask(
"Select profile",
choices=match,
default=match[0],
casesensitive=casesensitive,
)
def commandline_parser():
"""Return an argument parser"""
parent = argparse.ArgumentParser(add_help=False)
parent.add_argument(
'-a', '--ask',
action='store_true',
help='Force asking profile and filename (if relevant).',
)
parent.add_argument(
'-p', '--profile',
action='store',
nargs=1,
help='Force profile',
)
parser = argparse.ArgumentParser(
description="Manage ikiwiki calendar items.",
parents=[parent],
)
parser.add_argument(
'--version',
action='version',
version=(
'%(prog)s ' + cahier.VERSION
)
)
cahier.cmd_plugins.load_commands(parser.add_subparsers(), [parent])
return parser
def main():
"""Main function."""
try:
LOGGER.setLevel(logging.INFO)
options = commandline_parser().parse_args(sys.argv[1:])
if not hasattr(options, "function"):
raise errors.CahierError("A subcommand is required.")
config = cahier.config.load_cahierrc(
os.path.join(cahier.CAHIERRC, 'cahier.cfg')
)
profiles = cahier.config.load_profiles(
os.path.join(cahier.CAHIERRC, 'profiles')
)
if options.profile:
options.profile = options.profile[0]
profile = select_profile(
options.profile,
profiles,
options.ask,
config['options'].getboolean('casesensitive'),
)
sys.exit(options.function(config, options, profiles[profile]))
except errors.CahierError as error:
LOGGER.error("Error: " + str(error) + "\n")
sys.exit(1)
except EOFError:
sys.exit(0)
if __name__ == "__main__":
main()
PK ۻF;a a cahier/cmd_plugins/__init__.py#!/usr/bin/env python3
# Copyright 2014-2015 Louis Paternault
#
# Cahier is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Cahier is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with Cahier. If not, see .
"""Command plugin management.
Subcommands are defined as plugins, which are submodules of this one. Function
'load_commands()' is called on each modules, to let it add subcommands to the
parser.
"""
import glob
import importlib
import os
def load_commands(subparsers, parent=None):
"""Load all commands."""
for name in glob.glob(os.path.join(os.path.dirname(__file__), "*.py")):
if name.endswith(".py") and os.path.basename(name) != "__init__.py":
plugin = importlib.import_module(
'cahier.cmd_plugins.{}'.format(os.path.basename(name[:-len('.py')])) #pylint: disable=line-too-long
)
plugin.load_plugin(subparsers, parent)
PK Fu&wE
cahier/cmd_plugins/ikiwiki.py#!/usr/bin/env python3
# Copyright 2014-2015 Louis Paternault
#
# Cahier is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Cahier is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with Cahier. If not, see .
"""Define ikiwiki related command line subcommands."""
import argparse
import os
import shlex
import subprocess
import cahier.path
def run_ikiwiki(config, profile, arguments):
"""Run ikiwiki command.
Arguments:
- config: configparser object corresponding to ~/.cahier/cahierrc.cfg;
- profile: current profile;
- arguments: arguments to pass to IkiWiki (may be None).
"""
# Building command
command = ['ikiwiki']
command.extend(['--setup', os.path.basename(profile['config']['setup'])])
if config.has_option('wiki', 'options'):
command.extend((shlex.split(config['wiki']['options'])))
if arguments:
command.extend(arguments)
# Running command
process = subprocess.Popen(
command,
cwd=cahier.path.absfullpath(
os.path.dirname(profile['config']['setup'])
),
)
return process.wait()
def do_wiki(config, options, profile):
"""Run 'wiki' command."""
return run_ikiwiki(config, profile, options.args)
def do_refresh(config, options, profile):
"""Run 'refresh' command."""
return run_ikiwiki(config, profile, ['--refresh'] + options.args)
def do_rebuild(config, options, profile):
"""Run 'rebuild' command."""
return run_ikiwiki(config, profile, ['--rebuild'] + options.args)
def load_plugin(subparsers, __ignored):
"""Load current plugin.
Add parsers to argument, with appropriate actions to make them effective.
Arguments:
- subparsers: object to complete with custom commands;
- parent: may be None.
"""
# Parser for 'wiki' subcommand
parser_wiki = subparsers.add_parser(
'wiki',
help='Run IkiWiki.',
)
parser_wiki.set_defaults(function=do_wiki)
parser_wiki.add_argument(
'args',
nargs=argparse.REMAINDER,
help="Arguments to pass to IkiWiki",
default=None,
)
# Parser for 'refresh' subcommand
parser_refresh = subparsers.add_parser(
'refresh',
help='Alias for "wiki --refresh"',
aliases=['fresh'],
)
parser_refresh.set_defaults(function=do_refresh)
parser_refresh.add_argument(
'args',
nargs=argparse.REMAINDER,
help="Arguments to pass to IkiWiki",
default=None,
)
# Parser for 'rebuild' subcommand
parser_rebuild = subparsers.add_parser(
'rebuild',
help='Alias for "wiki --rebuild"',
)
parser_rebuild.set_defaults(function=do_rebuild)
parser_rebuild.add_argument(
'args',
nargs=argparse.REMAINDER,
help="Arguments to pass to IkiWiki",
default=None,
)
PK F cahier/cmd_plugins/git.py#!/usr/bin/env python3
# Copyright 2014-2015 Louis Paternault
#
# Cahier is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Cahier is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with Cahier. If not, see .
"""Define git command line subcommand."""
import argparse
import subprocess
import cahier.path
def do_git(__ignored, options, profile):
"""Run git command."""
# Building command
command = ['git']
if options.args:
command.extend(options.args)
# Running command
process = subprocess.Popen(
command,
cwd=cahier.path.absfullpath(
profile['directories']['calendar']
),
)
return process.wait()
def load_plugin(subparsers, __ignored):
"""Load current plugin.
Add parsers to argument, with appropriate actions to make them effective.
Arguments:
- subparsers: object to complete with custom commands;
- _: unused.
"""
# Parser for 'git' subcommand
parser_git = subparsers.add_parser('git', help='Run git.')
parser_git.set_defaults(function=do_git)
parser_git.add_argument(
'args',
nargs=argparse.REMAINDER,
help="Arguments to pass to git",
default=None,
)
PK FsfK5 K5 cahier/cmd_plugins/date.py#!/usr/bin/env python3
# Copyright 2014-2015 Louis Paternault
#
# Cahier is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Cahier is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with Cahier. If not, see .
"""Date-related options (new, edit, show, attach)."""
import argparse
import datetime
import heapq
import logging
import os
import shlex
import shutil
import subprocess
import cahier
LOGGER = logging.getLogger(__name__)
WEEKDAYS = [
'monday',
'tuesday',
'wednesday',
'thursday',
'friday',
'saturday',
'sunday',
]
def last_items(profile, config, number=1):
"""Return the 'number' last items of 'profile'.
"""
recent = []
extension = config['wiki']['extension']
fileformat = config['wiki']['fileformat']
for filename in os.listdir(
cahier.path.absfullpath(profile['directories']['calendar'])
):
if filename.endswith('.' + extension):
if config.has_option('wiki', 'fileformat-length'):
length = config['wiki']['fileformat-length']
stripped_filename = filename[0:int(length)]
else:
stripped_filename = filename[:-(len(extension)+1)]
date = datetime.datetime.strptime(stripped_filename, fileformat)
heapq.heappush(recent, (date.timestamp(), (date, filename)))
if number != -1 and len(recent) > number:
heapq.heappop(recent)
return [
(
os.path.join(
cahier.path.absfullpath(profile['directories']['calendar']),
item[1][1],
),
item[1][0]
)
for item in [heapq.heappop(recent) for _ in range(len(recent))]
]
def make_hour(string):
"""Take an (maybe) incomplete hour, and return an hour matching "%H:%M:%S".
"""
if string:
string = ":".join(string)
else:
string = "00"
while string.count(':') < 2:
string += ":00"
return string
def next_date(date, workdays, skip):
"""Return the date following date, respecting workdays.
Arguments:
- date: start date (return the first one after this one, and after today.
- workdays: work days, with time.
- skip: numbers of matching days to skip.
"""
day = datetime.timedelta(days=1)
date += day
if not workdays:
today = datetime.date.today()
if (date + datetime.timedelta(days=skip)).date() < today:
return datetime.date.today()
else:
return date + datetime.timedelta(days=skip)
workdays = dict([
(day[0], make_hour(day[1:]))
for day
in [day.split(':') for day in shlex.split(workdays)]
])
date -= day
while skip > -1:
date += day
skip -= 1
while WEEKDAYS[date.isoweekday() - 1] not in workdays.keys():
date += day
return datetime.datetime.combine(
date.date(),
datetime.datetime.strptime(
workdays[WEEKDAYS[date.isoweekday() - 1]], "%H:%M:%S"
).time()
)
def copy_template(filename, date, config, dry):
"""Copy template for filename.
If an appropriate template is defined in configuration, copy it as
'filename', passing 'date' as an argument to string formatting.
If not, create an empty file as 'filename'.
"""
if config.has_option('wiki', 'template'):
if dry:
LOGGER.info(
"Copying template {src} to {dst}.\n".format(
src=cahier.path.expand(config['wiki']['template'], filename),
dst=filename,
)
)
else:
template = open(
cahier.path.expand(config['wiki']['template'], filename),
'r'
)
newfile = open(filename, 'a')
newfile.write(template.read().format(date=date))
template.close()
newfile.close()
else:
if dry:
LOGGER.info("Creating empty file {}.\n".format(filename))
else:
open(filename, 'a').close()
def attach_file(filename, destination, plugin_directory, dry):
"""Attach file to a calendar entry.
Arguments:
- filename: filename to attach;
- destination: directory in which 'filename' will be copied;
- plugin_directory: directory of ftplugins, used to (maybe) preprocess
filename,
- dry: if set, only print what wourd be done.
"""
extension = filename.split('.')[-1]
if extension == filename:
extension = None
if extension and os.path.exists(os.path.join(
plugin_directory,
"{}.cfg".format(extension)
)):
plugin = cahier.config.load_ftplugin(os.path.join(
plugin_directory,
"{}.cfg".format(extension),
))
for command in [option
for option
in plugin.options('preprocess')
if option.startswith('cmd')
]:
command = cahier.path.expand(
plugin['preprocess'][command],
filename
)
if dry:
LOGGER.info("Running '{}'.\n".format(command))
else:
subprocess.call(command, shell=True)
filename = cahier.path.expand(
plugin['preprocess']['name'],
filename
)
basename = os.path.basename(filename)
if os.path.exists(os.path.join(destination, basename)):
if 'n' == cahier.io.ask(
"""File {basename} already exist in directory {destination}. Overwrite?""".format( #pylint: disable=line-too-long
basename=basename,
destination=destination
),
choices=['y', 'n'],
default='n',
):
return
if dry:
LOGGER.info("Copying {} to {}.\n".format(filename, destination))
else:
shutil.copy2(filename, destination)
def run_editnew(command, config, options, profile):
"""Run edit or new command.
Arguments:
- command: command line subcommand to run;
- config: configparer object representing ~/.cahierrc;
- options: command line options;
- profile: current profile, as a configparser object.
"""
items = last_items(profile, config)
last_filename, last_date = items[0]
if command == 'edit':
filename = last_filename
if options.ask:
print("\n".join([
os.path.relpath(
items[0][:-len('.mdwn')],
cahier.path.absfullpath(profile['directories']['calendar']) #pylint: disable=line-too-long
)
for items
in last_items(profile, config, -1)
]))
filename = os.path.join(
os.path.dirname(filename),
"{}.mdwn".format(cahier.io.ask(
"Filename",
default=os.path.basename(filename[:-len('.mdwn')])
)),
)
elif command == 'new':
newdate = next_date(
last_date,
profile['options']['workdays'],
skip=options.skip,
)
if options.ask:
print("\n".join([
"{:%Y-%m-%d %H:%M:%S}".format(items[1])
for items
in last_items(profile, config, -1)
]))
cahier.io.ask_date('Date', newdate)
filename = os.path.join(
cahier.path.absfullpath(
profile['directories']['calendar']
),
"{}.mdwn".format(
newdate.strftime(config['wiki']['fileformat'])
),
)
copy_template(filename, newdate, config, options.dry)
if options.dry:
LOGGER.info(
"Running '{cmd}'.\n".format(
cmd=cahier.path.expand(config['bin']['editor'], filename)
)
)
else:
subprocess.call(
cahier.path.expand(config['bin']['editor'], filename),
shell=True,
)
return 0
def do_new(config, options, profile):
"""Run command "new"."""
return run_editnew('new', config, options, profile)
def do_edit(config, options, profile):
"""Run command "edit"."""
return run_editnew('edit', config, options, profile)
def do_show(config, options, profile):
"""Run command "show"."""
items = last_items(profile, config, options.s)
print('\n'.join([
"{0}: {1:%c}".format(*item)
for item
in [
(
os.path.relpath(
item[0],
cahier.path.absfullpath(profile['directories']['calendar'])
),
item[1],
)
for item
in items
]
]))
return 0
def do_attach(config, options, profile):
"""Run command "attach"."""
items = last_items(profile, config)
last_filename = items[0][0]
if options.ask:
print("\n".join([
os.path.relpath(
items[0][:-len('.mdwn')],
cahier.path.absfullpath(profile['directories']['calendar']),
)
for items
in last_items(profile, config, -1)
]))
last_filename = os.path.join(
os.path.dirname(last_filename),
"{}.mdwn".format(cahier.io.ask(
"Entry",
default=os.path.basename(last_filename[:-len('.mdwn')])
)),
)
dirname = last_filename[0:-len('.mdwn')]
if not os.path.exists(dirname):
if options.dry:
LOGGER.info("Creating directory {}.\n".format(dirname))
else:
os.mkdir(dirname)
for filename in options.files:
attach_file(
filename,
dirname,
os.path.join(cahier.CAHIERRC, "ftplugins"),
options.dry,
)
return 0
def do_rm(config, options, profile):
"""Run command "remove"."""
items = last_items(profile, config)
if options.dry:
LOGGER.info("Removing {}.\n".format(items[0][0]))
else:
if (
options.force
or ((not options.force) and
cahier.io.ask_yesno(
'Remove {} ?'.format(os.path.relpath(
items[0][0],
cahier.path.absfullpath(profile['directories']['calendar']) #pylint: disable=line-too-long
)),
True,
)
)
):
os.remove(items[0][0])
return 0
def load_plugin(subparsers, parent):
"""Load current plugin.
Add parsers to argument, with appropriate actions to make them effective.
Arguments:
- subparsers: object to complete with custom commands;
- parent: may be None.
"""
# Common options
common = argparse.ArgumentParser(add_help=False)
common.add_argument(
'-n',
'--dry-run',
action='store_true',
help='Does nothing, but print entry that would be done.',
dest='dry',
default=False,
)
# Parser for 'new' subcommand
parser_new = subparsers.add_parser(
'new',
help='Add new date item.',
parents=parent + [common],
)
parser_new.set_defaults(function=do_new)
parser_new.add_argument(
'-s',
'--skip',
metavar='NUMBER',
action='store',
type=int,
help='Number of entries to skip.',
default=0
)
# Parser for 'edit' subcommand
parser_edit = subparsers.add_parser(
'edit',
help='Edit last entry.',
parents=parent + [common],
)
parser_edit.set_defaults(function=do_edit)
# Parser for 'show' subcommand
parser_show = subparsers.add_parser(
'show',
help='Show last entry.',
parents=parent + [common],
)
parser_show.set_defaults(function=do_show)
parser_show.add_argument(
'-s',
metavar='NUMBER',
action='store',
type=int,
help='Number of entries to show.',
default=1
)
# Parser for 'attach' subcommand
parser_attach = subparsers.add_parser(
'attach',
help='Attach file to current entry.',
parents=parent + [common],
)
parser_attach.set_defaults(function=do_attach)
parser_attach.add_argument(
'files',
nargs='+',
help="Files to attach to latest date."
)
# Parser for 'rm' subcommand
parser_rm = subparsers.add_parser(
'rm',
help='Remove the last entry',
parents=parent + [common],
)
parser_rm.add_argument(
'-f',
dest='force',
action='store_true',
help='Force deletion.',
default=False
)
parser_rm.set_defaults(function=do_rm)
PK Fd cahier/cmd_plugins/cd.py#!/usr/bin/env python3
# Copyright 2014-2015 Louis Paternault
#
# Cahier is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Cahier is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with Cahier. If not, see .
"""Launch a shell in calendar directory"""
import os
import shlex
import subprocess
def do_cd(config, __ignored, profile):
"""Run a shell in calendar directory
Arguments:
- config: configparer object representing ~/.cahierrc;
- _: command line options;
- profile: current profile, as a configparser object.
"""
if not config.has_option('bin', 'shell'):
config['bin']['shell'] = '$SHELL'
command = [
os.path.expandvars(os.path.expanduser(item))
for item
in shlex.split(config['bin']['shell'])
]
process = subprocess.Popen(
command,
cwd=os.path.expandvars(os.path.expanduser(
profile['directories']['calendar']
)),
)
return process.wait()
def load_plugin(subparsers, parent):
"""Load current plugin.
Add parsers to argument, with appropriate actions to make them effective.
Arguments:
- subparsers: object to complete with custom commands;
- parent: may be None.
"""
# Parser for 'new' subcommand
parser_cd = subparsers.add_parser(
'cd',
help='Run a shell in calendar directory.',
parents=parent
)
parser_cd.set_defaults(function=do_cd)
PK ۳9HKݍT T &