forked from Akuli/python-tutorial
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommon.py
More file actions
129 lines (102 loc) · 4.19 KB
/
common.py
File metadata and controls
129 lines (102 loc) · 4.19 KB
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
# 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
_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.
This assumes that the README contains links to everything. The
yielded paths use / as the path separator.
"""
yield 'README.md'
with open('README.md', 'r') as f:
for match, lineno in find_links(f):
target = posixpath.normpath(match.group(2))
# Currently he README links to itself, but we don't want to
# break things if it will be modified not to link in the future.
if target.endswith('.md') and target != 'README.md':
yield target
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.")
def slashfix(path):
"""Replace / with os.sep."""
return path.replace('/', os.sep)
def slashfix_open(file, mode):
"""An easy way to use slashfix() and open() together."""
return open(slashfix(file), mode)
@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')