4 from subprocess import Popen, PIPE
6 # Import this way to produce a more useful error message.
10 print('Unable to import YAML module: please install PyYAML', file=sys.stderr)
14 # Things an image file's name can end with.
22 # Files that shouldn't be present.
27 # Marker to show that an expected value hasn't been provided.
28 # (Can't use 'None' because that might be a legitimate value.)
31 class Reporter(object):
32 """Collect and report errors."""
37 super(Reporter, self).__init__()
41 def check_field(self, filename, name, values, key, expected=REPORTER_NOT_SET):
42 """Check that a dictionary has an expected value."""
45 self.add(filename, '{0} does not contain {1}', name, key)
46 elif expected is REPORTER_NOT_SET:
48 elif type(expected) in (tuple, set, list):
49 if values[key] not in expected:
50 self.add(filename, '{0} {1} value {2} is not in {3}', name, key, values[key], expected)
51 elif values[key] != expected:
52 self.add(filename, '{0} {1} is {2} not {3}', name, key, values[key], expected)
55 def check(self, condition, location, fmt, *args):
56 """Append error if condition not met."""
59 self.add(location, fmt, *args)
62 def add(self, location, fmt, *args):
63 """Append error unilaterally."""
65 if isinstance(location, type(None)):
67 elif isinstance(location, str):
68 coords = '{0}: '.format(location)
69 elif isinstance(location, tuple):
70 filename, line_number = location
71 coords = '{0}:{1}: '.format(*location)
73 assert False, 'Unknown location "{0}"/{1}'.format(location, type(location))
75 self.messages.append(coords + fmt.format(*args))
78 def report(self, stream=sys.stdout):
79 """Report all messages."""
83 for m in sorted(self.messages):
87 def read_markdown(parser, path):
89 Get YAML and AST for Markdown file, returning
90 {'metadata':yaml, 'metadata_len':N, 'text':text, 'lines':[(i, line, len)], 'doc':doc}.
93 # Split and extract YAML (if present).
94 with open(path, 'r') as reader:
96 metadata_raw, metadata_yaml, body = split_metadata(path, body)
99 metadata_len = 0 if metadata_raw is None else metadata_raw.count('\n')
100 lines = [(metadata_len+i+1, line, len(line)) for (i, line) in enumerate(body.split('\n'))]
103 cmd = 'ruby {0}'.format(parser)
104 p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, close_fds=True, universal_newlines=True)
105 stdout_data, stderr_data = p.communicate(body)
106 doc = json.loads(stdout_data)
109 'metadata': metadata_yaml,
110 'metadata_len': metadata_len,
117 def split_metadata(path, text):
119 Get raw (text) metadata, metadata as YAML, and rest of body.
120 If no metadata, return (None, None, body).
127 pieces = text.split('---', 2)
129 metadata_raw = pieces[1]
132 metadata_yaml = yaml.load(metadata_raw)
133 except yaml.YAMLError as e:
134 print('Unable to parse YAML header in {0}:\n{1}'.format(path, e), file=sys.stderr)
137 return metadata_raw, metadata_yaml, text
140 def load_yaml(filename):
142 Wrapper around YAML loading so that 'import yaml' is only needed
147 with open(filename, 'r') as reader:
148 return yaml.load(reader)
149 except (yaml.YAMLError, FileNotFoundError) as e:
150 print('Unable to load YAML file {0}:\n{1}'.format(filename, e), file=sys.stderr)
154 def check_unwanted_files(dir_path, reporter):
156 Check that unwanted files are not present.
159 for filename in UNWANTED_FILES:
160 path = os.path.join(dir_path, filename)
161 reporter.check(not os.path.exists(path),
163 "Unwanted file found")
166 def require(condition, message):
167 """Fail if condition not met."""
170 print(message, file=sys.stderr)