5 from subprocess import Popen, PIPE
7 # Import this way to produce a more useful error message.
11 print('Unable to import YAML module: please install PyYAML', file=sys.stderr)
15 # Things an image file's name can end with.
23 # Files that shouldn't be present.
28 # Marker to show that an expected value hasn't been provided.
29 # (Can't use 'None' because that might be a legitimate value.)
33 class Reporter(object):
34 """Collect and report errors."""
39 super(Reporter, self).__init__()
42 def check_field(self, filename, name, values, key, expected=REPORTER_NOT_SET):
43 """Check that a dictionary has an expected value."""
46 self.add(filename, '{0} does not contain {1}', name, key)
47 elif expected is REPORTER_NOT_SET:
49 elif type(expected) in (tuple, set, list):
50 if values[key] not in expected:
52 filename, '{0} {1} value {2} is not in {3}', name, key, values[key], expected)
53 elif values[key] != expected:
54 self.add(filename, '{0} {1} is {2} not {3}',
55 name, key, values[key], expected)
57 def check(self, condition, location, fmt, *args):
58 """Append error if condition not met."""
61 self.add(location, fmt, *args)
63 def add(self, location, fmt, *args):
64 """Append error unilaterally."""
66 self.messages.append((location, fmt.format(*args)))
70 location, message = item
71 if isinstance(location, type(None)):
73 elif isinstance(location, str):
74 return location + ': ' + message
75 elif isinstance(location, tuple):
76 return '{0}:{1}: '.format(*location) + message
78 print('Unknown item "{0}"'.format(item), file=sys.stderr)
83 location, message = item
84 if isinstance(location, type(None)):
85 return ('', -1, message)
86 elif isinstance(location, str):
87 return (location, -1, message)
88 elif isinstance(location, tuple):
89 return (location[0], location[1], message)
91 print('Unknown item "{0}"'.format(item), file=sys.stderr)
94 def report(self, stream=sys.stdout):
95 """Report all messages in order."""
100 for m in sorted(self.messages, key=self.key):
101 print(self.pretty(m), file=stream)
104 def read_markdown(parser, path):
106 Get YAML and AST for Markdown file, returning
107 {'metadata':yaml, 'metadata_len':N, 'text':text, 'lines':[(i, line, len)], 'doc':doc}.
110 # Split and extract YAML (if present).
111 with open(path, 'r') as reader:
113 metadata_raw, metadata_yaml, body = split_metadata(path, body)
116 metadata_len = 0 if metadata_raw is None else metadata_raw.count('\n')
117 lines = [(metadata_len+i+1, line, len(line))
118 for (i, line) in enumerate(body.split('\n'))]
121 cmd = 'ruby {0}'.format(parser)
122 p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE,
123 close_fds=True, universal_newlines=True)
124 stdout_data, stderr_data = p.communicate(body)
125 doc = json.loads(stdout_data)
128 'metadata': metadata_yaml,
129 'metadata_len': metadata_len,
136 def split_metadata(path, text):
138 Get raw (text) metadata, metadata as YAML, and rest of body.
139 If no metadata, return (None, None, body).
145 pieces = text.split('---', 2)
147 metadata_raw = pieces[1]
150 metadata_yaml = yaml.load(metadata_raw)
151 except yaml.YAMLError as e:
152 print('Unable to parse YAML header in {0}:\n{1}'.format(
153 path, e), file=sys.stderr)
156 return metadata_raw, metadata_yaml, text
159 def load_yaml(filename):
161 Wrapper around YAML loading so that 'import yaml' is only needed
166 with open(filename, 'r') as reader:
167 return yaml.load(reader)
168 except (yaml.YAMLError, IOError) as e:
169 print('Unable to load YAML file {0}:\n{1}'.format(
170 filename, e), file=sys.stderr)
174 def check_unwanted_files(dir_path, reporter):
176 Check that unwanted files are not present.
179 for filename in UNWANTED_FILES:
180 path = os.path.join(dir_path, filename)
181 reporter.check(not os.path.exists(path),
183 "Unwanted file found")
186 def require(condition, message):
187 """Fail if condition not met."""
190 print(message, file=sys.stderr)