1 |
6620
|
aaronmk
|
#!/usr/bin/env python
|
2 |
|
|
# Translates a data dictionary spreadsheet to Redmine formatting
|
3 |
|
|
|
4 |
|
|
import csv
|
5 |
|
|
import re
|
6 |
|
|
import sys
|
7 |
|
|
|
8 |
|
|
# Spreadsheet format
|
9 |
|
|
source_sep = ', '
|
10 |
|
|
|
11 |
|
|
##### URLs
|
12 |
|
|
|
13 |
6636
|
aaronmk
|
url_comment_re = r'(?:\([^)]*\))'
|
14 |
|
|
|
15 |
6620
|
aaronmk
|
def simplify_url(url): return re.sub(r'\(.*?\)', r'', url)
|
16 |
|
|
|
17 |
6635
|
aaronmk
|
def url_comment_text(comment):
|
18 |
|
|
if not comment: return comment
|
19 |
|
|
|
20 |
6638
|
aaronmk
|
match = re.match(r'^\(\d*:?(.*?)\)$', comment)
|
21 |
6635
|
aaronmk
|
assert match
|
22 |
|
|
text, = match.groups()
|
23 |
|
|
return text
|
24 |
|
|
|
25 |
6620
|
aaronmk
|
def url_term(url):
|
26 |
6637
|
aaronmk
|
'''Prefixes any provider in the URL to the term name, to create a namespace.
|
27 |
|
|
Each hierarchical component of the provider is stored in a URL comment.
|
28 |
|
|
'''
|
29 |
|
|
match = re.match(r'^('+url_comment_re+r'*).*?([\w:-]+)$', url)
|
30 |
6620
|
aaronmk
|
assert match
|
31 |
6637
|
aaronmk
|
provider_str, term = match.groups()
|
32 |
6639
|
aaronmk
|
provider = filter(bool, map(url_comment_text,
|
33 |
|
|
re.findall(url_comment_re, provider_str)))
|
34 |
6637
|
aaronmk
|
|
35 |
|
|
return ':'.join(provider+[term])
|
36 |
6620
|
aaronmk
|
|
37 |
|
|
##### Redmine
|
38 |
|
|
|
39 |
6684
|
aaronmk
|
sp = ' ' # Unicode thin space
|
40 |
|
|
|
41 |
6693
|
aaronmk
|
def redmine_esc(text): return '<notextile>'+text+'</notextile>'
|
42 |
|
|
|
43 |
6632
|
aaronmk
|
def redmine_bold(text): return '*'+text+'*'
|
44 |
|
|
|
45 |
6688
|
aaronmk
|
def redmine_pad(text): return sp+text+sp
|
46 |
|
|
|
47 |
6836
|
aaronmk
|
def redmine_link(text, url):
|
48 |
6686
|
aaronmk
|
if not url: return text
|
49 |
|
|
elif url.find('://') >= 0: return '"'+text+'":'+url # external link
|
50 |
|
|
else: return '[['+url+'|'+text+']]' # internal link
|
51 |
6620
|
aaronmk
|
|
52 |
6836
|
aaronmk
|
def source2redmine_link(url):
|
53 |
|
|
if url: return redmine_link(url_term(url), simplify_url(url))
|
54 |
6623
|
aaronmk
|
else: return ''
|
55 |
6620
|
aaronmk
|
|
56 |
6684
|
aaronmk
|
def redmine_add_links(page_name, text):
|
57 |
|
|
# Link citations to entry in sources list
|
58 |
6832
|
aaronmk
|
text = re.sub(r'(?<!\[)\[([^\[\]]*)\](?!\])',
|
59 |
6838
|
aaronmk
|
'('+redmine_link(r'\1', page_name+r'#\1')+')', text)
|
60 |
6684
|
aaronmk
|
return text
|
61 |
|
|
|
62 |
6694
|
aaronmk
|
redmine_table_sep = '|'
|
63 |
|
|
redmine_table_sep_esc = redmine_esc(redmine_table_sep)
|
64 |
|
|
|
65 |
|
|
def redmine_table_esc(text):
|
66 |
|
|
text = text.replace(redmine_table_sep, redmine_table_sep_esc)
|
67 |
|
|
return text
|
68 |
|
|
|
69 |
6620
|
aaronmk
|
class RedmineTableWriter:
|
70 |
|
|
'''Formats rows as a Redmine table'''
|
71 |
|
|
|
72 |
|
|
def __init__(self, stream):
|
73 |
|
|
self.stream = stream
|
74 |
|
|
|
75 |
|
|
def writerow(self, row):
|
76 |
6695
|
aaronmk
|
row = map(redmine_table_esc, row)
|
77 |
6620
|
aaronmk
|
self.stream.write(('|'.join(['']+row+['']))+'\n')
|
78 |
|
|
|
79 |
6673
|
aaronmk
|
class RedmineDictWriter:
|
80 |
|
|
'''Formats rows as Redmine sections containing a table'''
|
81 |
|
|
|
82 |
6676
|
aaronmk
|
def __init__(self, term_col, def_col, header, stream):
|
83 |
6673
|
aaronmk
|
self.term_col = term_col
|
84 |
6676
|
aaronmk
|
self.def_col = def_col
|
85 |
6673
|
aaronmk
|
self.header = header
|
86 |
6684
|
aaronmk
|
self.page_name = header[term_col]
|
87 |
6673
|
aaronmk
|
self.stream = stream
|
88 |
|
|
|
89 |
|
|
def writerow(self, row):
|
90 |
6684
|
aaronmk
|
row = map(lambda f: redmine_add_links(self.page_name, f), row)
|
91 |
|
|
|
92 |
6676
|
aaronmk
|
term = row[self.term_col]
|
93 |
|
|
def_ = row[self.def_col]
|
94 |
6837
|
aaronmk
|
self.stream.write('h2. '+term+'\n\n')
|
95 |
6676
|
aaronmk
|
if def_: self.stream.write(def_+'\n\n')
|
96 |
6673
|
aaronmk
|
|
97 |
|
|
table = RedmineTableWriter(self.stream)
|
98 |
|
|
for i, col_name in enumerate(self.header):
|
99 |
6677
|
aaronmk
|
if not i in set([self.term_col, self.def_col]):
|
100 |
6676
|
aaronmk
|
value = row[i]
|
101 |
|
|
if value: table.writerow([redmine_bold(col_name), value])
|
102 |
|
|
self.stream.write('\n')
|
103 |
6620
|
aaronmk
|
|
104 |
6673
|
aaronmk
|
|
105 |
6620
|
aaronmk
|
def main():
|
106 |
6676
|
aaronmk
|
try: _prog_name, term_col, def_col, sources_col = sys.argv
|
107 |
6626
|
aaronmk
|
except ValueError: raise SystemExit('Usage: '+sys.argv[0]
|
108 |
6676
|
aaronmk
|
+' <spreadsheet term_col# definition_col# sources_col# >redmine')
|
109 |
|
|
term_col, def_col, sources_col = map(int, [term_col, def_col, sources_col])
|
110 |
6626
|
aaronmk
|
|
111 |
6632
|
aaronmk
|
# Translate input
|
112 |
6620
|
aaronmk
|
reader = csv.reader(sys.stdin)
|
113 |
6673
|
aaronmk
|
header = reader.next()
|
114 |
6833
|
aaronmk
|
page_name = header[term_col]
|
115 |
6676
|
aaronmk
|
writer = RedmineDictWriter(term_col, def_col, header, sys.stdout)
|
116 |
6620
|
aaronmk
|
for row in reader:
|
117 |
|
|
term = row[term_col]
|
118 |
6634
|
aaronmk
|
sources = row[sources_col].split(source_sep)
|
119 |
6620
|
aaronmk
|
|
120 |
6836
|
aaronmk
|
row[term_col] = redmine_link(term, page_name+'#'+term)
|
121 |
|
|
row[sources_col] = source_sep.join(map(source2redmine_link, sources))
|
122 |
6628
|
aaronmk
|
|
123 |
6632
|
aaronmk
|
writer.writerow(row)
|
124 |
6620
|
aaronmk
|
|
125 |
|
|
main()
|