1#!/usr/bin/env python
2
3# A tiny Python script to perform substitutions in the NDK documentation
4# .text input files before processing them with Markdown.
5#
6
7import re
8import argparse
9import sys
10
11class Filter:
12  def __init__(self,pattern,replacement):
13    self.pattern = re.compile(pattern)
14    self.replacement = replacement
15
16  def process(self, line):
17    return self.pattern.sub(self.replacement, line)
18
19all_filters = []
20all_filter_tests = []
21
22def add_filter(pattern, replacement):
23  global all_filters
24  filter = Filter(pattern, replacement)
25  all_filters.append(filter)
26
27def add_filter_test(input, expected):
28  global all_filter_tests
29  all_filter_tests.append((input, expected))
30
31def run_all_tests():
32  global all_filter_tests
33  count = 0
34  failed_tests = []
35  for input_string, expected in all_filter_tests:
36    string = input_string
37    print "Testing: '%s'" % input_string,
38    for f in all_filters:
39      string = f.process(string)
40    if string != expected:
41      failed_tests.append((input_string, expected, string))
42      print "  KO!"
43      print "  Got     : '%s'" % string
44      print "  Expected: '%s'" % expected
45    else:
46      print "ok."
47    count += 1
48
49  return count, failed_tests
50
51# Auto-linkify documentation
52#
53#     d/NDK-BUILD
54# -> [NDK-BUILD](NDK-BUILD.html)
55#
56add_filter(r"(^|\s+)d/([^\s.]+)", r"\1[\2](\2.html)")
57
58add_filter_test("d/NDK-BUILD", "[NDK-BUILD](NDK-BUILD.html)")
59add_filter_test("aa d/NDK-BUILD", "aa [NDK-BUILD](NDK-BUILD.html)")
60add_filter_test("ad/NDK-BUILD", "ad/NDK-BUILD")
61add_filter_test("d/NDK-BUILD.", "[NDK-BUILD](NDK-BUILD.html).")
62
63# Auto-linkify documentation
64#    NDK-BUILD.html
65# -> [NDK-BUILD](NDK-BUILD.html)
66#
67add_filter(r"(^|\s+)([A-Z0-9-]+)\.html", r"\1[\2](\2.html)")
68add_filter_test("NDK-BUILD.html", "[NDK-BUILD](NDK-BUILD.html)")
69add_filter_test("NDK-BUILD.html.", "[NDK-BUILD](NDK-BUILD.html).")
70add_filter_test("aa NDK-BUILD.html", "aa [NDK-BUILD](NDK-BUILD.html)")
71
72add_filter(r"(^|\s+)(\$NDK/docs/|docs/)([A-Z0-9_-]+)\.html", r"\1[\3](\3.html)")
73add_filter_test("$NDK/docs/ANDROID-MK.html", "[ANDROID-MK](ANDROID-MK.html)")
74add_filter_test("See docs/ANDROID-MK.html.", "See [ANDROID-MK](ANDROID-MK.html).")
75
76# Auto quote script file.
77#    make-standalone-toolchain.sh
78# -> `make-standalone-toolchain.sh`
79add_filter(r"(^|\s+)([^\s]+\.sh)", r"\1`\2`")
80add_filter_test("make-standalone-toolchain.sh", "`make-standalone-toolchain.sh`")
81
82# Auto-linkify bug entries:
83#
84#    http://b.android.com/<number>
85# or http://code.google.com/p/android/issues/detail?id=<number>
86# -> [b/<number>](http://b.android.com/<number>)
87#
88add_filter(
89  r"http://(code\.google\.com/p/android/issues/detail\?id=|b\.android\.com/)([0-9]+)",
90  r"[b/\2](http://b.android.com/\2)")
91add_filter_test(r"See http://b.android.com/12345", r"See [b/12345](http://b.android.com/12345)")
92add_filter_test(r"See http://code.google.com/p/android/issues/detail?id=12345", r"See [b/12345](http://b.android.com/12345)")
93
94# Auto-linkify bug shortcuts like b/1000
95#
96#    b/<number> after space or start of line
97# -> [b/<number>](http://b.android.com/<number>)
98add_filter(
99  r"(^|\s+)(b/([0-9]+))",
100  r"\1[\2](http://b.android.com/\3)")
101add_filter_test(r"b/12345", r"[b/12345](http://b.android.com/12345)")
102add_filter_test(r"See b/12345.", r"See [b/12345](http://b.android.com/12345).")
103add_filter_test(r"[b/12345](....)", r"[b/12345](....)")
104
105# Auto-linkify patch entries.
106#    https://android-review.googlesource.com/#/c/<number>
107# -> [r/<number>](https://android-review.googlesource.com/#/c/<number>)
108add_filter(
109  r"(^|\s+)(https://android-review\.googlesource\.com/\#/c/([0-9]+))",
110  r"\1[r/\3](\2)")
111add_filter_test(r"https://android-review.googlesource.com/#/c/12345", r"[r/12345](https://android-review.googlesource.com/#/c/12345)")
112
113# Auto-linkify anything
114#    http://www.example.com
115# -> <http://www.example.com>
116add_filter(r"(^|\s+)((ssh|http|https|ftp)://[^\s]+)", r"\1<\2>")
117add_filter_test("http://example.com", "<http://example.com>")
118
119
120#    r/<number> not followed by (...)
121# -> [r/<number>](https://android-review.googlesource.com/#/c/<number>)
122add_filter(
123  r"(^|\s+)(r/([0-9]+))",
124  r"\1[\2](https://android-review.googlesource.com/#/c/\3)")
125add_filter_test(
126  r"r/12345",
127  r"[r/12345](https://android-review.googlesource.com/#/c/12345)")
128
129# Auto format __ANDROID__, __ARM_ARCH*__, etc..
130#    __XXX__
131# -> `__XXX__`
132add_filter(r"(__[A-Z][^\s]*)", r"`\1`")
133add_filter_test(r"__ANDROID__", r"`__ANDROID__`")
134add_filter_test(r"__ARM_ARCH_5*__", r"`__ARM_ARCH_5*__`")
135
136# Auto-format compiler/linker flags:
137#    -O2
138# -> `-O2`
139add_filter(r"(^|\s+)(\-[\w][^\s]+)", r"\1`\2`")
140add_filter_test(r"-O2", r"`-O2`")
141add_filter_test(r" -fPIC", r" `-fPIC`")
142add_filter_test(r" -mfpu=neon xxx", r" `-mfpu=neon` xxx")
143add_filter_test(r" -mfpu=vfpd3-d16", r" `-mfpu=vfpd3-d16`")
144
145# Auto-format LOCAL_XXX, APP_XXX and NDK_XXX variables
146# as well as assignments.
147add_filter(r"(^|\s+)([A-Z_0-9]+=[^\s]+)", r"\1`\2`")
148add_filter_test("Use NDK_DEBUG=release", "Use `NDK_DEBUG=release`")
149add_filter_test("NDK_HOST_32BIT=1", "`NDK_HOST_32BIT=1`")
150
151add_filter(r"(^|\s+)((APP_|NDK_|LOCAL_)[A-Z0-9_]*)", r"\1`\2`")
152add_filter_test("LOCAL_MODULE", "`LOCAL_MODULE`")
153
154# Auto-format __cxa_xxxxx and other prefixes.
155#
156add_filter(r"(^|\s+)((__cxa_|__dso_|__aeabi_|__atomic_|__sync_)[A-Za-z0-9_]+)", r"\1`\2`")
157add_filter_test("__cxa_begin_cleanup", "`__cxa_begin_cleanup`")
158add_filter_test("__dso_handle", "`__dso_handle`")
159add_filter_test("__aeabi_idiv2", "`__aeabi_idiv2`")
160add_filter_test("See __cxa_cleanup.", "See `__cxa_cleanup`.")
161
162re_blockquote = re.compile(r"^        ")
163
164def process(input_file, output_file):
165  # Process lines, we need to take care or _not_ processing
166  # block-quoted lines. For our needs, these begin with 8 spaces.
167  #
168  in_list = False
169  margins = [ 0 ]
170  margin_index = 0
171  for line in input_file:
172    do_process = True
173    if len(line.strip()):
174      if not re_blockquote.match(line):
175        for f in all_filters:
176          line = f.process(line)
177    output_file.write(line)
178
179def main():
180    parser = argparse.ArgumentParser(description='''
181    Perform .text substitution before Markdown processing.''')
182
183    parser.add_argument( '-o', '--output',
184                         help="Specify output file, stdout otherwise.",
185                         dest='output',
186                         default=None )
187
188    parser.add_argument( '--run-checks',
189                         help="Run internal unit tests.",
190                         action='store_true',
191                         dest='run_checks',
192                         default=False )
193
194    parser.add_argument( 'input_file',
195                         help="Input file, stdin if not specified.",
196                         nargs="?",
197                         default=None )
198
199    args = parser.parse_args()
200
201    if args.run_checks:
202      count, failed_tests = run_all_tests()
203      if failed_tests:
204        sys.stderr.write("ERROR: %d tests out of %d failed:\n" % (len(failed_tests), count))
205        for failed in failed_tests:
206          sys.stderr.write("  '%s' -> '%s' (expected '%s')\n" % (failed[0], failed[2], failed[1]))
207        sys.exit(1)
208      else:
209        print "%d tests passed. Congratulations!" % count
210        sys.exit(0)
211
212    if args.input_file:
213      try:
214        in_file = open(args.input_file, "rt")
215      except:
216        sys.stderr.write("Error: Can't read input file: %s: %s\n" % args.input_file, repr(e))
217        sys.exit(1)
218    else:
219      in_file = sys.stdin
220
221    if args.output:
222      try:
223        out_file = open(args.output, "wt")
224      except:
225        sys.stderr.write("Error: Can't open output file: %s: %s\n" % args.output, repr(e))
226        sys.exit(1)
227    else:
228      out_file = sys.stdout
229
230    process(in_file, out_file)
231
232    out_file.close()
233    in_file.close()
234    sys.exit(0)
235
236if __name__ == '__main__':
237    main()
238
239