notroff 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,92 @@
1
+ require 'rexml/document'
2
+ require 'pp'
3
+
4
+ class HtmlRenderer < Processor
5
+ include Tokenize
6
+ include REXML
7
+
8
+ def process( paragraphs )
9
+ body = Element.new('body')
10
+ paragraphs.each do |paragraph|
11
+ new_element = format( paragraph )
12
+ body.add new_element if new_element
13
+ end
14
+ body
15
+ end
16
+
17
+ def format( p )
18
+ type = p[:type]
19
+ text = p.string
20
+
21
+ return nil if text.empty? and :type != :code
22
+
23
+ if type == :code
24
+ code_element(type, text)
25
+ else
26
+ text_element(type, text)
27
+ end
28
+ end
29
+
30
+ def text_element(type, text)
31
+ element = Element.new(tag_for(type))
32
+ add_body_text(text, element)
33
+ element
34
+ end
35
+
36
+ def tag_for(type)
37
+ case type
38
+ when :body
39
+ 'p'
40
+ when :text
41
+ 'p'
42
+ when :author
43
+ 'h3'
44
+ when :section
45
+ 'h3'
46
+ when :sec
47
+ 'h3'
48
+ when :chapter
49
+ 'h2'
50
+ when :title
51
+ 'h1'
52
+ else
53
+ raise "Dont know what to do with #{type}"
54
+ end
55
+ end
56
+
57
+ def add_body_text( text, element )
58
+ tokens = tokenize_body_text( text )
59
+ tokens.each {|token| add_span( token, element ) }
60
+ end
61
+
62
+ def add_span( token, element )
63
+ case token[:type]
64
+ when :italic
65
+ element.add(span_for(token.string, "em"))
66
+ when :code
67
+ element.add(span_for(token.string, "code"))
68
+ when :bold
69
+ element.add(span_for(token.string, "b"))
70
+ when :normal
71
+ element.add_text(token.string)
72
+ when :footnote
73
+ add_body_text(" [#{token.string}] ", element)
74
+ else
75
+ raise "Dont know what to do with type #{token[:type]} - #{token}"
76
+ end
77
+ end
78
+
79
+ def code_element(type, text)
80
+ element = Element.new('code')
81
+ pre_element = Element.new('pre')
82
+ element.add pre_element
83
+ pre_element.add_text text
84
+ element
85
+ end
86
+
87
+ def span_for( text, style )
88
+ span = Element.new( style )
89
+ span.text = remove_escapes(text)
90
+ span
91
+ end
92
+ end
data/lib/notroff/io.rb ADDED
@@ -0,0 +1,18 @@
1
+ class FileProcessor
2
+ def initialize(path)
3
+ @path = path
4
+ end
5
+ end
6
+
7
+ class FileReader < FileProcessor
8
+ def process(ignored)
9
+ paras = File.readlines(@path)
10
+ paras.map! {|p| p.rstrip}
11
+ end
12
+ end
13
+
14
+ class FileWriter < FileProcessor
15
+ def process(output)
16
+ File.open(@path, 'w') {|f| f.write(output)}
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+
2
+ module Logger
3
+ @verbose = false
4
+
5
+ def self.verbose=(value)
6
+ @verbose = value
7
+ end
8
+
9
+ def self.log(*args)
10
+ return unless @verbose
11
+ puts args.join(' ')
12
+ end
13
+ end
@@ -0,0 +1,154 @@
1
+ require 'rexml/document'
2
+ require 'pp'
3
+
4
+
5
+ class OdtRenderer < Processor
6
+ include Tokenize
7
+ include REXML
8
+
9
+ LONG_DASH_CODE = 0xe1.chr + 0x80.chr + 0x93.chr
10
+
11
+ PARAGRAPH_STYLES = {
12
+ :body => 'BodyNoIndent',
13
+ :title => 'HB',
14
+ :section => 'HC',
15
+ :sec => 'HC',
16
+ :first_code => 'CDT1',
17
+ :middle_code => 'CDT',
18
+ :end_code => 'CDTX',
19
+ :author => 'AU',
20
+ :quote => 'Quotation',
21
+ :single_code => 'C1',
22
+ :pn => 'PN',
23
+ :pt => 'PT',
24
+ :cn => 'HA',
25
+ :chapter => 'HA',
26
+ :ct => 'HB' }
27
+
28
+ @@footnote_number = 1
29
+
30
+ def process( paragraphs )
31
+ elements = []
32
+ paragraphs.each do |paragraph|
33
+ new_element = format( paragraph )
34
+ elements << new_element if new_element
35
+ end
36
+ elements
37
+ end
38
+
39
+ def format( para )
40
+ Logger.log "Format: #{para.inspect}"
41
+ type = para[:type]
42
+ text = para
43
+
44
+ return nil if text.empty? and ! code_type?( type )
45
+
46
+ result = new_text_element( type )
47
+
48
+ if [ :author, :section, :sec, :title, :pn, :pt, :chapter ].include?( type )
49
+ result.add_text( text.string )
50
+ elsif [:body, :quote].include?(type)
51
+ add_body_text( text, result )
52
+ elsif code_type?(type)
53
+ add_code_text( text, result )
54
+ else
55
+ raise "Dont know what to do with type [#{type}]"
56
+ end
57
+ result
58
+ end
59
+
60
+ def code_type?( type )
61
+ [ :first_code, :middle_code, :end_code, :single_code ].include?(type)
62
+ end
63
+
64
+ def new_text_element( type )
65
+ result = Element.new( "text:p" )
66
+ result.attributes["text:style-name"] = PARAGRAPH_STYLES[type]
67
+ result
68
+ end
69
+
70
+ def add_body_text( text, element )
71
+ tokens = tokenize_body_text( text )
72
+ tokens.each {|token| add_span( token, element ) }
73
+ end
74
+
75
+ def add_code_text( text, element )
76
+ text = text.dup
77
+ re = /\S+|\s+/
78
+ until text.empty?
79
+ chunk = text.slice!( re )
80
+ if chunk !~ /^ /
81
+ element.add_text( chunk.string )
82
+ else
83
+ space_element = Element.new( 'text:s' )
84
+ space_element.attributes['text:c'] = chunk.size.to_s
85
+ element.add( space_element )
86
+ end
87
+ end
88
+ end
89
+
90
+ def add_span( token, element )
91
+ case token[:type]
92
+ when :italic
93
+ element.add( span_for( token.string, "T1" ))
94
+ when :code
95
+ element.add( span_for( token.string, "CD1" ))
96
+ when :bold
97
+ element.add( span_for( token.string, "T2" ))
98
+ when :normal
99
+ element.add_text( token.string )
100
+ when :footnote
101
+ element.add( footnote_for( token.string ) )
102
+ else
103
+ raise "Dont know what to do with #{token}"
104
+ end
105
+ end
106
+
107
+ def span_for( text, style )
108
+ span = Element.new( "text:span" )
109
+ span.attributes['text:style-name'] = style
110
+ span.text = remove_escapes(text)
111
+ span
112
+ end
113
+
114
+ def remove_escapes( text )
115
+ text = text.clone
116
+
117
+ results = ''
118
+
119
+ until text.empty?
120
+ match = /\\(.)/.match( text )
121
+ if match.nil?
122
+ results << text
123
+ text = ''
124
+ else
125
+ unless match.pre_match.empty?
126
+ results << match.pre_match
127
+ end
128
+ results << match[1]
129
+ text = match.post_match
130
+ end
131
+ end
132
+ results
133
+ end
134
+
135
+ def footnote_for(text )
136
+ note_element = Element.new( "text:note" )
137
+ note_element.attributes["text:id"] ="ftn#{@@footnote_number}"
138
+ note_element.attributes["text:note-class"] ="footnote"
139
+
140
+ cit = Element.new( "text:note-citation" )
141
+ cit.add_text( "#{@@footnote_number}" )
142
+ note_element.add( cit )
143
+
144
+ note_body = Element.new( "text:note-body" )
145
+ note_paragraph = Element.new( "text:p" )
146
+ note_paragraph.attributes['text:style-name'] = 'FTN'
147
+ add_body_text(text, note_paragraph)
148
+
149
+ note_body.add( note_paragraph )
150
+ note_element.add( note_body )
151
+ @@footnote_number += 1
152
+ note_element
153
+ end
154
+ end
@@ -0,0 +1,22 @@
1
+ require 'pp'
2
+ require 'zip/zipfilesystem'
3
+ require 'fileutils'
4
+
5
+ SKEL = File.expand_path('../skel.odt', __FILE__)
6
+
7
+ class OdtReplacer < Processor
8
+
9
+ def initialize( output_path )
10
+ @output_path = output_path
11
+ end
12
+
13
+ def process( new_content )
14
+ FileUtils.cp( SKEL, @output_path )
15
+
16
+ Zip::ZipFile.open( @output_path ) do |zipfile|
17
+ zipfile.file.open("content.xml", "w") do |content|
18
+ content.print( new_content )
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,53 @@
1
+ class ParagraphJoiner
2
+ def process( paragraphs )
3
+ processed_paragraphs = []
4
+ new_p = nil
5
+ paragraphs.each do |paragraph|
6
+ do_join = join?(paragraph)
7
+
8
+ if join?(paragraph)
9
+ if new_p
10
+ new_p.string += " "
11
+ new_p.string += paragraph
12
+ else
13
+ new_p = paragraph
14
+ end
15
+ else
16
+ if new_p
17
+ processed_paragraphs << new_p
18
+ new_p = nil
19
+ end
20
+ processed_paragraphs << paragraph unless skip?(paragraph)
21
+ end
22
+ end
23
+ processed_paragraphs << new_p if new_p
24
+ processed_paragraphs
25
+ end
26
+
27
+ def join?(paragraph)
28
+ false
29
+ end
30
+
31
+ def skip?(paragraph)
32
+ false
33
+ end
34
+ end
35
+
36
+ class BodyParagraphJoiner < ParagraphJoiner
37
+ def join?(paragraph)
38
+ return false unless paragraph[:type] == :body
39
+ return false if paragraph.empty?
40
+ true
41
+ end
42
+
43
+ def skip?(paragraph)
44
+ paragraph[:type] == :body && paragraph.empty?
45
+ end
46
+ end
47
+
48
+ class CodeParagraphJoiner < ParagraphJoiner
49
+ def join?(paragraph)
50
+ return false unless paragraph[:type] == :code
51
+ true
52
+ end
53
+ end
@@ -0,0 +1,192 @@
1
+ class Processor
2
+ end
3
+
4
+ class TextPrinter
5
+ def process( text )
6
+ puts text
7
+ end
8
+ end
9
+
10
+ class FileWriter
11
+ def initialize(output_file)
12
+ @output_file = output_file
13
+ end
14
+
15
+ def process(content)
16
+ File.open(@output_file, 'w') do |f|
17
+ f.print(content.to_s)
18
+ end
19
+ end
20
+ end
21
+
22
+ class Printer
23
+ def process( paragraphs )
24
+ paragraphs.each {|p| puts "#{p.type}: #{p.text}"}
25
+ end
26
+ end
27
+
28
+ class TextReader < Processor
29
+ def initialize( path )
30
+ @path = path
31
+ end
32
+
33
+ def process( ignored )
34
+ lines = File.open( @path ).readlines
35
+ lines.map! { |line| line.rstrip }
36
+ lines
37
+ end
38
+ end
39
+
40
+ #class CommandProcessor
41
+ #
42
+ # def process( lines )
43
+ # paragraphs = []
44
+ # lines.each do |line|
45
+ # cmd, text = parse_line( line )
46
+ # if cmd.empty?
47
+ # cmd = :text
48
+ # else
49
+ # cmd = cmd.sub(/^./, '').to_sym
50
+ # end
51
+ #
52
+ # text.attr(:type, cmd)
53
+ # text.attr(:original_text, line)
54
+ # paragraphs << text
55
+ # end
56
+ # paragraphs
57
+ # end
58
+ #
59
+ # def parse_line( line )
60
+ # match_data = /^(\.\w+ ?)(.*)/.match(line)
61
+ # if match_data
62
+ # cmd = match_data[1].strip
63
+ # text = match_data[2]
64
+ # else
65
+ # cmd = ''
66
+ # text = line
67
+ # end
68
+ # [ cmd.strip, text ]
69
+ # end
70
+ #end
71
+ #
72
+ #class ParagraphTypeAssigner
73
+ # def process( paragraphs )
74
+ # processed_paragraphs = []
75
+ #
76
+ # current_type = :body
77
+ #
78
+ # paragraphs.each do |paragraph|
79
+ # type = paragraph | :type
80
+ # if (type == :body) or (type == :code) or (type == :quote)
81
+ # current_type = type
82
+ #
83
+ # elsif type == :text
84
+ # new_p = paragraph.clone
85
+ # new_p.attr(:type, current_type)
86
+ # processed_paragraphs << new_p
87
+ #
88
+ # else
89
+ # processed_paragraphs << paragraph
90
+ # end
91
+ #
92
+ # current_type = :body if [ :section, :title, :code1 ].include?(type)
93
+ # end
94
+ # processed_paragraphs
95
+ # end
96
+ #end
97
+ #
98
+ #class CodeTypeRefiner
99
+ # def process( paragraphs )
100
+ # processed_paragraphs = []
101
+ #
102
+ # previous_type = nil
103
+ #
104
+ # paragraphs.each_with_index do |paragraph, i|
105
+ # type = paragraph | :type
106
+ # previous_type = ( paragraphs.first == paragraph) ? nil : paragraphs[i-1] | :type
107
+ # next_type = ( paragraphs.last == paragraph) ? nil : paragraphs[i+1] | :type
108
+ # new_type = code_type_for( previous_type, type, next_type )
109
+ # new_p = paragraph.clone
110
+ # new_p.attr(:type, new_type)
111
+ # processed_paragraphs << new_p
112
+ # end
113
+ # processed_paragraphs
114
+ # end
115
+ #
116
+ # def code_type_for( previous_type, type, next_type )
117
+ # if type != :code
118
+ # new_type = type
119
+ #
120
+ # elsif previous_type == :code and next_type == :code
121
+ # new_type = :middle_code
122
+ #
123
+ # elsif previous_type == :code
124
+ # new_type = :end_code
125
+ #
126
+ # elsif next_type == :code
127
+ # new_type = :first_code
128
+ #
129
+ # else
130
+ # new_type = :single_code
131
+ # end
132
+ #
133
+ # new_type
134
+ # end
135
+ #end
136
+ #
137
+ #class SimilarParagraphJoiner
138
+ # def initialize(target_type)
139
+ # @target_type = target_type
140
+ # end
141
+ #
142
+ # def process( paragraphs )
143
+ # processed_paragraphs = []
144
+ # new_p = nil
145
+ # paragraphs.each do |paragraph|
146
+ # if (paragraph | :type) != @target_type
147
+ # processed_paragraphs << new_p if new_p
148
+ # new_p = nil
149
+ # processed_paragraphs << paragraph
150
+ #
151
+ # elsif new_p
152
+ # new_p += "\n"
153
+ # new_p += paragraph
154
+ #
155
+ # else
156
+ # new_p = paragraph
157
+ # end
158
+ # end
159
+ # processed_paragraphs << new_p if new_p
160
+ # processed_paragraphs
161
+ # end
162
+ #end
163
+ #
164
+ #class TextParagraphJoiner
165
+ # def process( paragraphs )
166
+ # processed_paragraphs = []
167
+ #
168
+ # new_p = nil
169
+ #
170
+ # paragraphs.each do |paragraph|
171
+ # if (paragraph | type != :body) and (paragraph | type != :quote)
172
+ # processed_paragraphs << new_p if new_p
173
+ # new_p = nil
174
+ # processed_paragraphs << paragraph
175
+ #
176
+ # elsif paragraph.blank?
177
+ # processed_paragraphs << new_p if new_p
178
+ # new_p = nil
179
+ #
180
+ # elsif new_p
181
+ # new_p += paragraph.text
182
+ #
183
+ # else
184
+ # new_p = paragraph
185
+ # end
186
+ # end
187
+ # processed_paragraphs << new_p if new_p
188
+ # processed_paragraphs
189
+ # end
190
+ #end
191
+ #
192
+ #
Binary file
@@ -0,0 +1,6 @@
1
+ class String
2
+ # Return the number of leading blanks
3
+ def indent_depth
4
+ /^ */.match(self).to_s.size
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ require 'erb'
2
+
3
+ TEMPLATE = File.expand_path('../content.xml.erb', __FILE__)
4
+
5
+ class TemplateExpander < Processor
6
+
7
+ def initialize
8
+ @template = ERB.new(File.read(TEMPLATE))
9
+ end
10
+
11
+ def process( elements )
12
+ content = elements.join( "\n" )
13
+ @template.result(binding)
14
+ end
15
+ end
@@ -0,0 +1,67 @@
1
+ require 'delegate'
2
+
3
+ class Text
4
+ attr_accessor :string, :attrs
5
+
6
+ def initialize(initial_string, initial_attrs={})
7
+ @string = initial_string.to_str.clone
8
+ @attrs = initial_attrs.clone
9
+ end
10
+
11
+ def self.wrap_method(method_name)
12
+ define_method method_name do |*args|
13
+ result = @string.send(method_name, *args)
14
+ return Text.new(result, attrs) if result.kind_of? String
15
+ result
16
+ end
17
+ end
18
+
19
+ def self.wrap_methods(method_names)
20
+ method_names.each {|name| wrap_method(name)}
21
+ end
22
+
23
+ wrap_methods( String.instance_methods(false) )
24
+
25
+ def [](name)
26
+ @attrs[name]
27
+ end
28
+
29
+ def []=(name, value)
30
+ @attrs[name] = value
31
+ end
32
+
33
+ def ==(other)
34
+ return false unless other.kind_of? Text
35
+ @string == other.string && @attrs == other.attrs
36
+ end
37
+
38
+ def clone
39
+ Text.new(string, attrs)
40
+ end
41
+
42
+ def to_str
43
+ #puts "To string: #{@string.class} #{@string}"
44
+ @string
45
+ end
46
+
47
+ def to_s
48
+ @string
49
+ end
50
+
51
+ def inspect
52
+ "#{@string.inspect} :: #{@attrs.inspect}"
53
+ end
54
+ end
55
+
56
+ class String
57
+ def to_text
58
+ Text.new(self)
59
+ end
60
+
61
+ alias_method :old_double_equals, :'=='
62
+
63
+ def ==(other)
64
+ return self == other.string if other.kind_of?(Text)
65
+ old_double_equals(other)
66
+ end
67
+ end
@@ -0,0 +1,65 @@
1
+ #require 'rexml/document'
2
+ require 'pp'
3
+ #require 'zip/zipfilesystem'
4
+ #require 'fileutils'
5
+ #require 'erb'
6
+
7
+ FormatOdtDir = File.dirname(__FILE__)
8
+
9
+ require "#{FormatOdtDir}/utils"
10
+ require "#{FormatOdtDir}/processor"
11
+ require "#{FormatOdtDir}/code_processors"
12
+ require "#{FormatOdtDir}/odt_renderer"
13
+ require "#{FormatOdtDir}/odt_replacer"
14
+ require "#{FormatOdtDir}/template_expander"
15
+
16
+
17
+ class Editor
18
+ def initialize( input, output )
19
+ @input = input
20
+ @output = output
21
+ @processors = []
22
+ @processors << TextReader.new( @input )
23
+ @processors << CommandProcessor.new
24
+ @processors << ParagraphTypeAssigner.new
25
+ @processors << ProgramOutputInserter.new
26
+ @processors << C1Inserter.new
27
+ @processors << IncInserter.new
28
+ @processors << CodeInserter.new
29
+ @processors << LastOutputInserter.new
30
+ @processors << CodeTagFilter.new
31
+ @processors << CodeTypeRefiner.new
32
+ @processors << OdtRenderer.new
33
+ @processors << TemplateExpander.new( File.read( ContentTemplate ) )
34
+ @processors << OdtReplacer.new( OdtSkeleton, @output )
35
+ end
36
+
37
+ def process
38
+ result = nil
39
+ @processors.each do |processor|
40
+ result = processor.process( result )
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+ f = Editor.new( ARGV[0], ARGV[1] )
47
+ f.process
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+