notroff 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/bin/notroff ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pp'
3
+ require "notroff"
4
+
5
+ options={}
6
+ formatter_class = HtmlFormatter
7
+
8
+ while true
9
+ if ARGV.first == '-o'
10
+ formatter_class = OdtFormatter
11
+ ARGV.shift
12
+ elsif ARGV.first == '-d'
13
+ formatter_class = DocbookFormatter
14
+ ARGV.shift
15
+ elsif ARGV.first == '-v'
16
+ Logger.verbose = true
17
+ ARGV.shift
18
+ else
19
+ break
20
+ end
21
+ end
22
+
23
+ unless ARGV.size == 2
24
+ raise "Usage: notroff [-d] <nr file> <output file>"
25
+ end
26
+
27
+
28
+ f = formatter_class.new( ARGV[0], ARGV[1] )
29
+ f.process
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
@@ -0,0 +1,9 @@
1
+ class CodeScrubber
2
+ def process( paragraphs )
3
+ paragraphs.each do |p|
4
+ next unless p[:type] == :code
5
+ p.string.gsub!(/##.*$/, '')
6
+ end
7
+ paragraphs
8
+ end
9
+ end
@@ -0,0 +1,37 @@
1
+ class CodeTypeRefiner
2
+ def process( paragraphs )
3
+ processed_paragraphs = []
4
+
5
+ previous_type = nil
6
+
7
+ paragraphs.each_with_index do |paragraph, i|
8
+ type = paragraph[:type]
9
+ previous_type = ( paragraphs.first == paragraph) ? nil : paragraphs[i-1][:type]
10
+ next_type = ( paragraphs.last == paragraph) ? nil : paragraphs[i+1][:type]
11
+ new_type = code_type_for( previous_type, type, next_type )
12
+ paragraph[:type] = new_type
13
+ processed_paragraphs << paragraph
14
+ end
15
+ processed_paragraphs
16
+ end
17
+
18
+ def code_type_for( previous_type, type, next_type )
19
+ if type != :code
20
+ new_type = type
21
+
22
+ elsif previous_type == :code and next_type == :code
23
+ new_type = :middle_code
24
+
25
+ elsif previous_type == :code
26
+ new_type = :end_code
27
+
28
+ elsif next_type == :code
29
+ new_type = :first_code
30
+
31
+ else
32
+ new_type = :single_code
33
+ end
34
+
35
+ new_type
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ class CommandProcessor
2
+ def process( lines )
3
+ paragraphs = []
4
+ lines.each do |line|
5
+ cmd, text = parse_line( line )
6
+ if cmd.empty?
7
+ cmd = :text
8
+ else
9
+ cmd = cmd.sub(/^./, '').to_sym
10
+ end
11
+ para = Text.new(text, :type => cmd, :original_text => line.to_s)
12
+ paragraphs << para
13
+ end
14
+ paragraphs
15
+ end
16
+
17
+ def parse_line( line )
18
+ match_data = /^(\.\w+ ?)(.*)/.match(line)
19
+ if match_data
20
+ cmd = match_data[1].strip
21
+ text = match_data[2]
22
+ else
23
+ cmd = ''
24
+ text = line
25
+ end
26
+ [ cmd.strip, text ]
27
+ end
28
+ end
@@ -0,0 +1,52 @@
1
+ class CompositeProcessor
2
+ def initialize(*args)
3
+ @processors = args
4
+ end
5
+
6
+ def add_processor(p)
7
+ @processors << p
8
+ end
9
+
10
+ def prepend_processor(p)
11
+ @processors = [p] + @processors
12
+ end
13
+
14
+ def process(paras=[])
15
+ @processors.each do |processor|
16
+ Logger.log "Applying processor #{processor.class} to #{paras.size} paragraphs"
17
+ dump(paras)
18
+ Logger.log
19
+ paras = processor.process( paras )
20
+ Logger.log "After processor #{processor.class}"
21
+ Logger.log
22
+ end
23
+ paras
24
+ end
25
+
26
+ def dump(data)
27
+ return unless Verbose
28
+ Logger.log "======="
29
+ if data.nil?
30
+ Logger.log "data: nil"
31
+ elsif data.kind_of?(Array)
32
+ Logger.log "data with #{data.size} items:"
33
+ data.each_with_index {|item, i| Logger.log "[#{i}] - #{item.inspect}" }
34
+ else
35
+ Logger.log data
36
+ end
37
+ Logger.log "======="
38
+ end
39
+ end
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
@@ -0,0 +1,110 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
3
+ xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
4
+ xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
5
+ xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
6
+ xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
7
+ xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
8
+ xmlns:xlink="http://www.w3.org/1999/xlink"
9
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
10
+ xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
11
+ xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"
12
+ xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
13
+ xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0"
14
+ xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"
15
+ xmlns:math="http://www.w3.org/1998/Math/MathML"
16
+ xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0"
17
+ xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0"
18
+ xmlns:ooo="http://openoffice.org/2004/office"
19
+ xmlns:ooow="http://openoffice.org/2004/writer"
20
+ xmlns:oooc="http://openoffice.org/2004/calc"
21
+ xmlns:dom="http://www.w3.org/2001/xml-events"
22
+ xmlns:xforms="http://www.w3.org/2002/xforms"
23
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
24
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
25
+ xmlns:field="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:field:1.0"
26
+ office:version="1.1">
27
+ <office:scripts />
28
+ <office:font-face-decls>
29
+ <style:font-face style:name="Wingdings"
30
+ svg:font-family="Wingdings" style:font-pitch="variable"
31
+ style:font-charset="x-symbol" />
32
+ <style:font-face style:name="Symbol" svg:font-family="Symbol"
33
+ style:font-family-generic="roman" style:font-pitch="variable"
34
+ style:font-charset="x-symbol" />
35
+ <style:font-face style:name="Courier New"
36
+ svg:font-family="'Courier New'"
37
+ style:font-family-generic="modern" />
38
+ <style:font-face style:name="Courier"
39
+ svg:font-family="Courier, 'Courier New'"
40
+ style:font-family-generic="modern" />
41
+ <style:font-face style:name="Nimbus Mono L"
42
+ svg:font-family="'Nimbus Mono L', 'Courier New'"
43
+ style:font-family-generic="modern" />
44
+ <style:font-face style:name="Nimbus Roman No9 L"
45
+ svg:font-family="'Nimbus Roman No9 L'"
46
+ style:font-family-generic="roman"
47
+ style:font-pitch="variable" />
48
+ <style:font-face style:name="Times" svg:font-family="Times"
49
+ style:font-family-generic="roman"
50
+ style:font-pitch="variable" />
51
+ <style:font-face style:name="Times New Roman"
52
+ svg:font-family="'Times New Roman'"
53
+ style:font-family-generic="roman"
54
+ style:font-pitch="variable" />
55
+ <style:font-face style:name="Arial" svg:font-family="Arial"
56
+ style:font-family-generic="swiss"
57
+ style:font-pitch="variable" />
58
+ <style:font-face style:name="Arial Unicode MS"
59
+ svg:font-family="'Arial Unicode MS'"
60
+ style:font-family-generic="swiss"
61
+ style:font-pitch="variable" />
62
+ <style:font-face style:name="Helvetica"
63
+ svg:font-family="Helvetica" style:font-family-generic="swiss"
64
+ style:font-pitch="variable" />
65
+ <style:font-face style:name="Tahoma" svg:font-family="Tahoma"
66
+ style:font-family-generic="swiss"
67
+ style:font-pitch="variable" />
68
+ <style:font-face style:name="Verdana" svg:font-family="Verdana"
69
+ style:font-family-generic="swiss"
70
+ style:font-pitch="variable" />
71
+ <style:font-face style:name="DejaVu Sans"
72
+ svg:font-family="'DejaVu Sans'"
73
+ style:font-family-generic="system"
74
+ style:font-pitch="variable" />
75
+ </office:font-face-decls>
76
+ <office:automatic-styles>
77
+ <style:style style:name="P1" style:family="paragraph"
78
+ style:parent-style-name="Standard">
79
+ <style:paragraph-properties fo:text-align="end"
80
+ style:justify-single-word="false" />
81
+ </style:style>
82
+ <style:style style:name="P2" style:family="paragraph"
83
+ style:parent-style-name="HA" style:master-page-name="Standard">
84
+ <style:paragraph-properties style:page-number="1" />
85
+ </style:style>
86
+ <style:style style:name="P3" style:family="paragraph"
87
+ style:parent-style-name="FTN">
88
+ <style:paragraph-properties fo:margin-left="0.1598in"
89
+ fo:margin-right="0in" fo:margin-top="0.0417in"
90
+ fo:margin-bottom="0.0417in"
91
+ style:line-height-at-least="0.1807in"
92
+ fo:text-indent="-0.1598in" style:auto-text-indent="false" />
93
+ </style:style>
94
+ <style:style style:name="T1" style:family="text">
95
+ <style:text-properties fo:font-style="italic"
96
+ style:font-style-asian="italic"
97
+ style:font-style-complex="italic" />
98
+ </style:style>
99
+ <style:style style:name="T2" style:family="text">
100
+ <style:text-properties fo:font-weight="bold"
101
+ style:font-weight-asian="bold"
102
+ style:font-weight-complex="bold" />
103
+ </style:style>
104
+ </office:automatic-styles>
105
+ <office:body>
106
+ <office:text>
107
+ <%=content%>
108
+ </office:text>
109
+ </office:body>
110
+ </office:document-content>
@@ -0,0 +1,155 @@
1
+ require 'rexml/document'
2
+ require 'pp'
3
+
4
+ class DocbookRenderer < Processor
5
+ include Tokenize
6
+ include REXML
7
+
8
+ def process( paragraphs )
9
+ @author = 'John Smith'
10
+ @title = 'Wealth of Nations'
11
+ @chapters = []
12
+ @chapter = nil
13
+ @section = nil
14
+
15
+ paragraphs.each do |paragraph|
16
+ format( paragraph )
17
+ end
18
+ @book = Element.new('book')
19
+ @book.add_namespace('http://docbook.org/ns/docbook')
20
+ @book.add_attribute('version', '5.0')
21
+ @book.add_element element_for('title', @title)
22
+ @book.add_element element_for('author', @author)
23
+ @chapters.each {|ch| @book << ch}
24
+ doc = Document.new
25
+ decl = XMLDecl.new
26
+ decl.version = '1.0'
27
+ doc << decl
28
+ doc << @book
29
+ doc
30
+ end
31
+
32
+ def element_for(type, text)
33
+ result = Element.new(type)
34
+ result.add_text(text)
35
+ result
36
+ end
37
+
38
+ def format( p )
39
+ type = p[:type]
40
+ text = p.string
41
+
42
+ return nil if text.empty? and :type != :code
43
+
44
+ case type
45
+ when :title
46
+ @title = text
47
+ when :author
48
+ @author = text
49
+ when :chapter
50
+ Logger.log "adding chapter #{text}"
51
+ @chapter = Element.new('chapter')
52
+ @chapters << @chapter
53
+ @section = nil
54
+ title_element = Element.new('title')
55
+ add_body_text(title_element, text)
56
+ @chapter.add_element(title_element)
57
+ when :section
58
+ Logger.log "adding section #{text}"
59
+ @section = Element.new('section')
60
+ @chapter.add(@section)
61
+ title_element = Element.new('title')
62
+ add_body_text(title_element, text)
63
+ @section.add_element(title_element)
64
+ when :body
65
+ Logger.log "adding body #{text[0..5]}"
66
+ paragraph = Element.new('para')
67
+ add_body_text(paragraph, text)
68
+ add_content_element(paragraph)
69
+ when :code
70
+ add_content_element(code_element(type, text))
71
+ else
72
+ raise "#{type}???"
73
+ end
74
+ end
75
+
76
+ def add_content_element(el)
77
+ if @section
78
+ @section.add_element(el)
79
+ elsif @chapter
80
+ @chapter.add_element(el)
81
+ else
82
+ raise "No chapter to add #{el} to"
83
+ end
84
+ end
85
+
86
+ def text_element(type, text)
87
+ element = [ tag_for(type) ]
88
+ add_body_text(element, text)
89
+ element
90
+ end
91
+
92
+ def tag_for(type)
93
+ case type
94
+ when :body
95
+ 'para'
96
+ when :text
97
+ 'p'
98
+ when :author
99
+ 'h3'
100
+ when :section
101
+ 'h3'
102
+ when :chapter
103
+ 'chapter'
104
+ when :sec
105
+ 'h3'
106
+ when :title
107
+ 'h2'
108
+ else
109
+ raise "Dont know what to do with #{type}"
110
+ end
111
+ end
112
+
113
+ def add_body_text( element, text )
114
+ tokens = tokenize_body_text( text )
115
+ tokens.each {|token| add_span( token, element ) }
116
+ end
117
+
118
+ def add_span( token, element )
119
+ Logger.log "Add span: token: #{token} element: #{element}"
120
+ case token[:type]
121
+ when :italic
122
+ element.add( span_for( token.string, "emphasis" ))
123
+ when :code
124
+ element.add( span_for( token.string, "code" ))
125
+ when :bold
126
+ element.add( span_for( token.string, "emphasis" ))
127
+ when :normal
128
+ element.add_text( token.string )
129
+ when :footnote
130
+ element.add(footnote_for(token.string))
131
+ else
132
+ raise "Dont know what to do with #{token}"
133
+ end
134
+ end
135
+
136
+ def footnote_for( text )
137
+ fn = Element.new('footnote')
138
+ fn.add_element(element_for('para', text))
139
+ fn
140
+ end
141
+
142
+ def code_element(type, text)
143
+ element = Element.new('informalexample')
144
+ prog_element = element_for('programlisting', text)
145
+ prog_element.add_attribute('xml:space', 'preserve')
146
+ element.add_element(prog_element)
147
+ element
148
+ end
149
+
150
+ def span_for( text, style )
151
+ span = Element.new(style)
152
+ span.text = remove_escapes(text)
153
+ span
154
+ end
155
+ end
@@ -0,0 +1,132 @@
1
+ class BetweenFilter
2
+ def initialize(re1, re2=re1)
3
+ @re1 = re1
4
+ @re2 = re2
5
+ end
6
+
7
+ def process(paras)
8
+ state = :before_first
9
+ paras.each do |para|
10
+ if state == :before_first and @re1 =~ para
11
+ para[:included] = true
12
+ state = :after_first
13
+ break if para =~ @re2
14
+ elsif state == :after_first
15
+ para[:included] = true
16
+ break if para =~ @re2
17
+ end
18
+ end
19
+ paras
20
+ end
21
+ end
22
+
23
+ class DefinitionFilter
24
+ def initialize(def_re, include_body=true)
25
+ @def_re = def_re
26
+ @include_body = include_body
27
+ end
28
+
29
+ def process(paras)
30
+ state = :before_def
31
+ end_re = nil
32
+ paras.each do |para|
33
+ Logger.log para, (@def_re =~ para)
34
+ if state == :before_def and @def_re =~ para
35
+ para[:included] = true
36
+ end_re = Regexp.new( "^#{' ' * para.string.indent_depth}end" )
37
+ state = :after_def
38
+ elsif state == :after_def and end_re =~ para.string
39
+ para[:included] = true
40
+ break
41
+ elsif state == :after_def and @include_body
42
+ para[:included] = true
43
+ end
44
+ end
45
+ paras
46
+ end
47
+ end
48
+
49
+ class MethodFilter < DefinitionFilter
50
+ def initialize(method_name, include_body)
51
+ super /^ *def +#{method_name}(\(|$| )/, include_body
52
+ end
53
+ end
54
+
55
+ class ClassFilter < DefinitionFilter
56
+ def initialize(class_name, include_body)
57
+ super /^ *class +#{class_name}(\(|$| )/, include_body
58
+ end
59
+ end
60
+
61
+ class ModuleFilter < DefinitionFilter
62
+ def initialize(module_name, include_body)
63
+ super /^ *module +#{module_name}(\(|$| )/, include_body
64
+ end
65
+ end
66
+
67
+ class EmbeddedRubyProcessor
68
+ def process(paragraphs)
69
+ new_paragraphs = []
70
+ paragraphs.each do |p|
71
+ if p[:type] == :x
72
+ Logger.log p
73
+ results = process_command(p.string)
74
+ new_paragraphs << results if results
75
+ else
76
+ new_paragraphs << p
77
+ end
78
+ end
79
+ new_paragraphs.flatten
80
+ end
81
+
82
+ def process_command(ruby_expression)
83
+ Logger.log "Ruby expression: #{ruby_expression}"
84
+ lines = eval(ruby_expression, binding)
85
+ end
86
+
87
+ def embed(*filters, &block)
88
+ paras = block.call.map {|line| line.rstrip}
89
+ paras.map! {|p| Text.new(p, :type => :code)}
90
+ Logger.log "EMBED: #{paras}"
91
+ unless filters.empty?
92
+ filters.each {|f| paras = f.process(paras)}
93
+ paras = paras.find_all {|p| p[:included]}
94
+ end
95
+ paras
96
+ end
97
+
98
+ def inc(path, *filters)
99
+ embed(*filters) {File.readlines(path)}
100
+ end
101
+
102
+ def run(command, *filters)
103
+ embed(*filters) {File.popen(command).readlines}
104
+ end
105
+
106
+ def matches(re1, re2=re1)
107
+ BetweenFilter.new(re1, re2)
108
+ end
109
+
110
+ def method(name, include_body=true)
111
+ MethodFilter.new(name, include_body)
112
+ end
113
+
114
+ def clazz(name, include_body=true)
115
+ ClassFilter.new(name, include_body)
116
+ end
117
+
118
+ def mod(name, include_body=true)
119
+ ModuleFilter.new(name, include_body)
120
+ end
121
+
122
+ def indent(delta_indent, paragraphs)
123
+ paragraphs.map do |p|
124
+ if delta_indent > 0
125
+ p.string = (" " * delta_indent) + p.string
126
+ elsif delta_indent < 0
127
+ p.string.sub!( ' ' * delta_indent.abs, '' )
128
+ end
129
+ end
130
+ paragraphs
131
+ end
132
+ end
@@ -0,0 +1,15 @@
1
+ class Filter
2
+ def process(paragraphs)
3
+ paragraphs.find_all {|p| included?(p)}
4
+ end
5
+
6
+ def included?(paragraph)
7
+ true
8
+ end
9
+ end
10
+
11
+ class IncludedFilter < Filter
12
+ def included?(paragraph)
13
+ paragraph[:included]
14
+ end
15
+ end
@@ -0,0 +1,54 @@
1
+ class Formatter < CompositeProcessor
2
+ def initialize()
3
+ super
4
+ add_processor CommandProcessor.new
5
+ add_processor TypeAssigner.new
6
+ add_processor EmbeddedRubyProcessor.new
7
+ add_processor CodeScrubber.new
8
+ add_processor BodyParagraphJoiner.new
9
+ end
10
+ end
11
+
12
+ class HtmlFormatter < Formatter
13
+ def initialize(input, output)
14
+ super()
15
+ prepend_processor FileReader.new(input)
16
+ add_processor CodeParagraphJoiner.new
17
+ add_processor HtmlRenderer.new
18
+ add_processor FileWriter.new(output)
19
+ end
20
+ end
21
+
22
+ class DocbookFormatter < Formatter
23
+ def initialize(input, output)
24
+ super()
25
+ prepend_processor FileReader.new(input)
26
+ add_processor CodeParagraphJoiner.new
27
+ add_processor DocbookRenderer.new
28
+ add_processor FileWriter.new(output)
29
+ end
30
+ end
31
+
32
+ class OdtFormatter < Formatter
33
+ def initialize(input, output)
34
+ super()
35
+ prepend_processor FileReader.new(input)
36
+ add_processor CodeTypeRefiner.new
37
+ add_processor OdtRenderer.new
38
+ add_processor TemplateExpander.new
39
+ add_processor OdtReplacer.new(output)
40
+ end
41
+ end
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+