Project

General

Profile

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 6620 aaronmk
def redmine_url(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
def source2redmine_url(url):
53 6623 aaronmk
    if url: return redmine_url(url_term(url), simplify_url(url))
54
    else: return ''
55 6620 aaronmk
56 6684 aaronmk
def redmine_add_links(page_name, text):
57
    # Link citations to entry in sources list
58 6691 aaronmk
    text = re.sub(r'\[(.*?)\]',
59
        redmine_bold(redmine_url('\g<0>', 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
        self.stream.write('h3. '+term+'\n\n')
95
        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 6676 aaronmk
    writer = RedmineDictWriter(term_col, def_col, header, sys.stdout)
115 6620 aaronmk
    for row in reader:
116
        term = row[term_col]
117 6634 aaronmk
        sources = row[sources_col].split(source_sep)
118 6620 aaronmk
119 6634 aaronmk
        row[term_col] = redmine_url(term, simplify_url(sources[0]))
120 6632 aaronmk
        row[sources_col] = source_sep.join(map(source2redmine_url, sources))
121 6628 aaronmk
122 6632 aaronmk
        writer.writerow(row)
123 6620 aaronmk
124
main()