forked from Akuli/python-tutorial
-
Notifications
You must be signed in to change notification settings - Fork 10
/
common.py
156 lines (127 loc) · 4.84 KB
/
common.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
151
152
153
154
155
156
# This is free and unencumbered software released into the public
# domain.
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a
# compiled binary, for any purpose, commercial or non-commercial, and
# by any means.
# In jurisdictions that recognize copyright laws, the author or
# authors of this software dedicate any and all copyright interest in
# the software to the public domain. We make this dedication for the
# benefit of the public at large and to the detriment of our heirs
# and successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to
# this software under copyright law.
# 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 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.
# For more information, please refer to <http://unlicense.org>
"""Things that other scripts import and use.
The markdown files use / as a path separator. That's why they need the
posixpath module for processing paths, but they use functions in this
file when actually opening files.
"""
import contextlib
import itertools
import os
import posixpath
import re
import shutil
import string
_LINK_REGEX = r'!?\[(.*?)\]\((.*?)\)'
def find_links(file):
"""Find all markdown links in a file object.
Yield (lineno, regexmatch) tuples.
"""
# don't yield same link twice
seen = set()
# we need to loop over the file two lines at a time to support
# multi-line (actually two-line) links, so this is kind of a mess
firsts, seconds = itertools.tee(file)
next(seconds) # first line is never second line
# we want 1-based indexing instead of 0-based and one-line links get
# caught from linepair[1], so we need to start at two
for lineno, linepair in enumerate(zip(firsts, seconds), start=2):
lines = linepair[0] + linepair[1]
for match in re.finditer(_LINK_REGEX, lines, flags=re.DOTALL):
if match.group(0) not in seen:
seen.add(match.group(0))
yield match, lineno
def get_markdown_files():
"""Yield the names of all markdown files in this tutorial.
The yielded paths use / as the path separator.
"""
for root, dirs, files in os.walk('.'):
for file in files:
if not file.endswith('.md'):
continue
path = os.path.normpath(os.path.join(root, file))
yield path.replace(os.sep, '/')
def header_link(title):
"""Return a github-style link target for a title.
>>> header_link('Hello there!')
'hello-there'
"""
# This doesn't do the-title-1, the-title-2 etc. with multiple titles
# with same text, but usually this doesn't matter.
result = ''
for character in title:
if character in string.whitespace:
result += '-'
elif character in string.punctuation:
pass
else:
result += character.lower()
return result
def askyesno(question, default=True):
"""Ask a yes/no question and return True or False.
The default answer is yes if default is True and no if default is
False.
"""
if default:
# yes by default
question += ' [Y/n] '
else:
# no by default
question += ' [y/N] '
while True:
result = input(question).upper().strip()
if result == 'Y':
return True
if result == 'N':
return False
if not result:
return default
print("Please type y, n or nothing at all.")
@contextlib.contextmanager
def backup(filename):
"""A context manager that backs up a file."""
shutil.copy(filename, filename + '.backup')
try:
yield
except Exception:
# It failed, we need to restore from the backup.
shutil.copy(filename + '.backup', filename)
else:
# Everything's fine, we can safely get rid of the backup.
os.remove(filename + '.backup')
def header_link(title):
"""Return a github-style link target for a title.
>>> header_link('Hello there!')
'hello-there'
"""
# This doesn't handle multiple titles with the same text in the
# same file, but usually that's not a problem. GitHub makes
# links like the-title, the-title-1, the-title-2 etc.
result = ''
for character in title:
if character in string.whitespace:
result += '-'
elif character in string.punctuation:
pass
else:
result += character.lower()
return result