-
Notifications
You must be signed in to change notification settings - Fork 10
/
main.py
150 lines (117 loc) · 4.95 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/bin/env python3
# Copyright 2015 Malcolm Inglis <http://minglis.id.au/>
import os
import sys
from datetime import datetime
from argparse import ArgumentParser, ArgumentTypeError
from subprocess import check_output, CalledProcessError, Popen, PIPE, DEVNULL
from contextlib import contextmanager
class FileExistsException(Exception):
def __init__(self, path):
self.path = path
def main():
args = parse_args(sys.argv[1:])
try:
path = jekyll_post(args)
except FileExistsException as ex:
print('A file already exists at \'{}\'.'.format(ex.path),
file=sys.stderr)
return 1
if path != '-':
print(path)
return 0
def parse_args(raw_args):
args = make_parser().parse_args(raw_args)
args.date = args.date or now()
args.attributes = args.attributes or []
return args
def make_parser():
p = ArgumentParser(description='Creates a new Jekyll post, and prints its '
'path to standard out.')
p.add_argument('title', type=escape_str,
help='The title for the new post.')
g = p.add_mutually_exclusive_group(required=True)
g.add_argument('-c', '--category',
help='The path of the category directory for the new post, '
'such that it will be written into '
'\'$JEKYLL_SITE_PATH/$category/_posts\'. ')
g.add_argument('-d', '--directory', type=directory_exists,
help='The path of the directory to write the new post '
'into.')
g.add_argument('-o', '--output', metavar='PATH',
help='The path to write the new post to. Provide \'-\' to '
'write to standard out.')
p.add_argument('-t', '--date', type=parse_datetime,
help='The date and time for the new post, in a format '
'accepted by the `date` utility. Default: now.')
p.add_argument('-x', '--extension', default='md',
help='The file extension for the new post. '
'Default: \'md\'.')
p.add_argument('-a', '--attributes', nargs="*", metavar='ATTR',
help='Extra attributes to put in the header, provided in a '
'format according to \'jekyll-post-header\'. The '
'\'layout\' attribute defaults to \'default\'.')
p.add_argument('-p', '--padding', type=int, default=10, metavar='NSPACES',
help='The number of spaces to left-align the attributes '
'by. Default: 10.')
return p
def escape_str(s):
return s.replace('\'', '\\\'')
def directory_exists(s):
if not os.path.isdir(s):
raise ArgumentTypeError('\'{}\' is not a directory.'.format(s))
return s
def parse_datetime(s):
try:
ds = check_output(['date', '--date={}'.format(s),
'--iso-8601=seconds'],
stderr=DEVNULL).decode().strip()
except CalledProcessError:
raise ArgumentTypeError(('\'{}\' is an invalid date. It must be in a '
'format accepted by the `date` utility\'s '
'`--date` argument.').format(s))
return datetime.strptime(ds, '%Y-%m-%dT%H:%M:%S%z')
def now():
return parse_datetime(datetime.now().isoformat())
def jekyll_post(args):
with header_proc(args) as proc:
path = get_post_path(args)
with open_post_file(path) as file:
for bline in proc.stdout:
line = bline.decode()[:-1]
print(line, file=file)
return path
def get_post_path(args):
if args.output:
return args.output
else:
filename = check_output(['jekyll-post-filename', args.title,
'--date', args.date.strftime('%Y-%m-%d'),
'--extension', args.extension],
stderr=DEVNULL).decode()[:-1]
dirname = (args.directory
or os.path.join(os.environ.get('JEKYLL_SITE_PATH', ''),
args.category,
'_posts'))
return os.path.join(dirname, filename)
@contextmanager
def open_post_file(path):
if path == '-':
yield sys.stdout
else:
if os.path.exists(path):
raise FileExistsException(path)
with open(path, 'w') as f:
yield f
def header_proc(args):
# TODO: this won't raise an exception if the script fails. Is there a way to
# check for errors, while still streaming the output?
return Popen(['jekyll-post-header', '--padding', str(args.padding),
'layout:"default"',
'date:"{}"'.format(args.date),
'title:"{}"'.format(args.title)]
+ args.attributes,
stdout=PIPE, stderr=DEVNULL)
if __name__ == '__main__':
rv = main()
sys.exit(rv)