notroff 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,64 @@
1
+ require 'rexml/document'
2
+
3
+ module Tokenize
4
+ def tokenize_body_text( text )
5
+ text = text.dup
6
+ re = /\~\~.*?\~\~|\@\@.*?\@\@+|\{\{.*?\}\}|!!.*?!!/
7
+ results = []
8
+ until text.empty?
9
+ match = re.match( text )
10
+ if match.nil?
11
+ results << Text.new(text, :type=>:normal)
12
+ text = ''
13
+ else
14
+ unless match.pre_match.empty?
15
+ results << Text.new(match.pre_match, :type=>:normal)
16
+ end
17
+ token = match.to_s
18
+ results << Text.new(token_text(token), :type=>token_type(token))
19
+ text = match.post_match
20
+ end
21
+ end
22
+ results
23
+ end
24
+
25
+ def token_type( token )
26
+ case token
27
+ when /^\~/
28
+ :italic
29
+ when /^\@/
30
+ :code
31
+ when /^\{/
32
+ :footnote
33
+ when /^!/
34
+ :bold
35
+ end
36
+ end
37
+
38
+ def token_text( token )
39
+ result = token.sub( /^../, '' ).sub( /..$/, '')
40
+ #print "token text for #{token} [[#{result}]]"
41
+ result
42
+ end
43
+
44
+ def remove_escapes( text )
45
+ text = text.clone
46
+
47
+ results = ''
48
+
49
+ until text.empty?
50
+ match = /\\(.)/.match( text )
51
+ if match.nil?
52
+ results << text
53
+ text = ''
54
+ else
55
+ unless match.pre_match.empty?
56
+ results << match.pre_match
57
+ end
58
+ results << match[1]
59
+ text = match.post_match
60
+ end
61
+ end
62
+ results
63
+ end
64
+ end
@@ -0,0 +1,25 @@
1
+ class TypeAssigner
2
+ def process( paragraphs )
3
+ processed_paragraphs = []
4
+
5
+ current_type = :body
6
+
7
+ paragraphs.each do |paragraph|
8
+ type = paragraph[:type]
9
+ if (type == :body) or (type == :code) or (type == :quote)
10
+ current_type = type
11
+ elsif type == :code1
12
+ paragraph[:type] = :code
13
+ processed_paragraphs << paragraph
14
+ elsif type == :text
15
+ paragraph[:type] = current_type
16
+ processed_paragraphs << paragraph
17
+ else
18
+ processed_paragraphs << paragraph
19
+ end
20
+
21
+ current_type = :body if [ :section, :title, :code1 ].include?(type)
22
+ end
23
+ processed_paragraphs
24
+ end
25
+ end
data/lib/notroff.rb ADDED
@@ -0,0 +1,23 @@
1
+ Verbose = false unless defined?(Verbose)
2
+
3
+ require "notroff/logger"
4
+ require "notroff/string_extensions"
5
+ require "notroff/text"
6
+ require "notroff/io"
7
+ require "notroff/filter"
8
+ require "notroff/code_scrubber"
9
+ require "notroff/embedded"
10
+ require "notroff/paragraph_joiner"
11
+ require "notroff/type_assigner"
12
+ require "notroff/processor"
13
+ require "notroff/command_processor"
14
+ require "notroff/composite_processor"
15
+ require "notroff/tokenize"
16
+ require "notroff/html_renderer"
17
+ require "notroff/docbook_renderer"
18
+ require "notroff/odt_renderer"
19
+ require "notroff/odt_replacer"
20
+ require "notroff/template_expander"
21
+ require "notroff/code_typer"
22
+ require "notroff/formatter"
23
+ require "notroff/filter"
data/readme.nr ADDED
@@ -0,0 +1,58 @@
1
+ NotRoff Text Formatter
2
+ ========
3
+
4
+ NotRoff is a simple plain text file format that allows you to embed all sort of styling, including fixed width, italics and bold into your work using plain text markers. The included notroff program will turn the plain text into an Open Office .odt document. I used NotRoff to format Eloquent Ruby.
5
+
6
+ Creating plain text in notroff is very easy -- you just write each paragraph in its own line, separated by blank lines. You can add styling to your text with simple tags: words surrounded by !! become bold, while ~~ tags italics and @@ tags code, for example like this:
7
+
8
+ !!This!! is bold, but ~~this~~ is italics and @@all of this@@ looks like code.
9
+
10
+ To do fancier stuff there are also dot commands. Each dot command starts with a period, followed by the name of the command, and then -- for most dot commands -- an argument or two. For example, there is a dot command for creating section headers:
11
+
12
+ .section NotRoff Includes Section Headers
13
+
14
+ There is also a dot command to include some program code from a source code into your final document:
15
+
16
+ .inc gutted_doc.rb
17
+
18
+ Frequently you don't want the whole file, just a chunk of it. To do that, you can add a second argument to your .inc command, a !!tag!! which will pick out a tagged section of the file. Here's what a .inc command with a tag looks like:
19
+
20
+ .inc ex_lazy_document.rb base_doc
21
+
22
+ And here is what the corresponding source file looks like:
23
+
24
+ .code
25
+ # This comment is not included in the .odt file
26
+
27
+ puts "first included line" ##(base_doc
28
+ puts "also included"
29
+ puts "last included line" ##base_doc)
30
+ .body
31
+
32
+ There is a short cut if you want to include a single line from your source file -- Just tag it with ##+tag, like this:
33
+
34
+ open_file = File.open( '/etc/passwd' ) ##+open
35
+
36
+ You can also explicitly exclude a line with ##--tag.
37
+
38
+ One of the problems with including code into text is that the indentation is frequently wrong: The code wants to have indentation that makes sense within the larger program which sometimes looks bad in the text. To deal with this, the .inc command lets you specify a 3rd parameter, the number of spaces that you want to set the indentation to:
39
+
40
+ .c1 .inc type_check_spec.rb bad_type 2
41
+
42
+ Along with pulling code from a file, you can also include single lines of code directly into the NotRoff file:
43
+
44
+ .c1 doc = get_some_kind_of_document
45
+
46
+ Or multiple lines of code:
47
+
48
+ .code
49
+ puts "Title: #{doc.title}"
50
+ puts "Author: #{doc.author}"
51
+ puts "Content: #{doc.content}"
52
+ .body
53
+
54
+ Using notroff is simple: Just run the notroff script, supplying the path to the plain text file as the first argument and the path to your new OpenOffice .odt file as the second:
55
+
56
+ .c1 notroff example.nr example.odt
57
+
58
+
@@ -0,0 +1,15 @@
1
+ describe CommandProcessor do
2
+ let(:cp) { CommandProcessor.new }
3
+
4
+ it 'should turn a plain line of text into a .text command' do
5
+ lines = [ 'line 1', 'line 2' ]
6
+ new_lines = cp.process(lines)
7
+ new_lines.size.should == 2
8
+ new_lines[0].string.should == 'line 1'
9
+ new_lines[1].string.should == 'line 2'
10
+ new_lines[0][:type].should == :text
11
+ new_lines[1][:type].should == :text
12
+ new_lines[0][:original_text].should == 'line 1'
13
+ new_lines[1][:original_text].should == 'line 2'
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ class PUp
2
+ def process(paras)
3
+ paras.map {|p| p.upcase}
4
+ end
5
+ end
6
+
7
+ class PDown
8
+ def process(paras)
9
+ paras.map {|p| p.downcase}
10
+ end
11
+ end
12
+
13
+ describe CompositeProcessor do
14
+ let(:cp) { CompositeProcessor.new() }
15
+
16
+ let(:paras) { %W{ para1 para2 para3 } }
17
+
18
+ it 'should do nothing with no processors' do
19
+ new_paras = CompositeProcessor.new.process(paras)
20
+ new_paras.should == %W{ para1 para2 para3 }
21
+ end
22
+
23
+ it 'should work like a single processor with one subprocessor' do
24
+ cp.add_processor(PUp.new)
25
+ new_paras = cp.process(paras)
26
+ new_paras.should == %W{ PARA1 PARA2 PARA3 }
27
+ end
28
+
29
+ it 'should apply several processors in turn' do
30
+ cp.add_processor(PUp.new)
31
+ cp.add_processor(PDown.new)
32
+ new_paras = cp.process(paras)
33
+ new_paras.should == %W{ para1 para2 para3 }
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ describe IncludedFilter do
2
+ let(:filter) { IncludedFilter.new }
3
+
4
+ let(:paras) do
5
+ paras = %W{ para1 para2 para3 para4 para5}
6
+ paras.map {|p| Text.new(p) }
7
+ end
8
+
9
+ it 'should filter out paras that dont have the included tag' do
10
+ paras[1][:included] = paras[3][:included] = true
11
+ new_paras = filter.process(paras)
12
+ new_paras.size.should == 2
13
+ new_paras[0].string.should == 'para2'
14
+ new_paras[1].string.should == 'para4'
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ describe Formatter do
2
+ let(:formatter) { Formatter.new }
3
+
4
+ let(:text_paras) { %W{ para1 para2 para3 para4 para5} }
5
+
6
+ it 'should turn join a series of text paragraphs into a single body' do
7
+ new_paras = formatter.process(text_paras)
8
+ new_paras.size.should == 1
9
+ new_paras[0][:type].should == :body
10
+ new_paras[0].string.should == "para1 para2 para3 para4 para5"
11
+ end
12
+
13
+ it 'should deal with sticky commands' do
14
+ new_paras = formatter.process( %W{body1 .code code1 code2 .body body2} )
15
+ pp new_paras
16
+ new_paras.size.should == 4
17
+ new_paras[0][:type].should == :body
18
+ new_paras[1][:type].should == :code
19
+ new_paras[2][:type].should == :code
20
+ new_paras[3][:type].should == :body
21
+ end
22
+ end
data/spec/hello.rb ADDED
@@ -0,0 +1 @@
1
+ puts "hello ruby"
data/spec/hello.rb.out ADDED
@@ -0,0 +1 @@
1
+ hello ruby
@@ -0,0 +1,26 @@
1
+ describe BodyParagraphJoiner do
2
+ let(:pj) { BodyParagraphJoiner.new(:body) }
3
+
4
+ let(:paras) do
5
+ paras = %W{ para1 para2 para3 para4 para5}
6
+ paras.map {|p| Text.new(p, :type => :body) }
7
+ end
8
+
9
+ it 'should turn join a series of similar paragraphs together' do
10
+ new_paras = pj.process(paras)
11
+ new_paras.size.should == 1
12
+ new_paras[0][:type].should == :body
13
+ new_paras[0].string.should == "para1 para2 para3 para4 para5"
14
+ end
15
+
16
+ it 'should turn stop joining on an empty paragraph' do
17
+ paras[2].string = ''
18
+ new_paras = pj.process(paras)
19
+ pp new_paras
20
+ new_paras.size.should == 2
21
+ new_paras[0][:type].should == :body
22
+ new_paras[0].string.should == "para1 para2"
23
+ new_paras[1][:type].should == :body
24
+ new_paras[1].string.should == "para4 para5"
25
+ end
26
+ end
data/spec/simple.nr ADDED
@@ -0,0 +1,3 @@
1
+ first line
2
+ second line
3
+ third line
@@ -0,0 +1,22 @@
1
+ describe String do
2
+ it 'should let you turn a string into a Text' do
3
+ t = 'abc'.to_text
4
+ t.class.should == Text
5
+ t.string.should == 'abc'
6
+ t.attrs.should == {}
7
+ end
8
+
9
+ it 'should still have a working ==' do
10
+ s = 'abc'
11
+ s.should == 'abc'
12
+ s.should_not == 'xxx'
13
+ s.should_not == 123
14
+ s.should_not == nil
15
+ end
16
+
17
+ it 'should be == to Text instances with the same string' do
18
+ s = 'abc'
19
+ s.should == Text.new('abc')
20
+ s.should_not == Text.new('xxx')
21
+ end
22
+ end
data/spec/text_spec.rb ADDED
@@ -0,0 +1,55 @@
1
+ describe Text do
2
+ let(:t) { Text.new('abc') }
3
+
4
+ it 'should be creatable with a string and an attr hash' do
5
+ text = Text.new('abc', :name => 'russ')
6
+ text.string.should == 'abc'
7
+ text.attrs.should == {:name => 'russ'}
8
+ end
9
+
10
+ it 'should provide access to the original string' do
11
+ t.string.should == 'abc'
12
+ t.string.class.should == String
13
+ end
14
+
15
+ it 'should have a size method like a string' do
16
+ t.size.should == 3
17
+ end
18
+
19
+ it 'should support a sub method which should return a Text' do
20
+ new_t = t.sub('b', 'B')
21
+ new_t.class.should == Text
22
+ new_t.string.should == 'aBc'
23
+ end
24
+
25
+ it 'should return attributes when you do arithmetic' do
26
+ t[:name] = 'russ'
27
+ new_t = t + "xxx"
28
+ new_t[:name].should == 'russ'
29
+ new_t.string.should == 'abcxxx'
30
+ end
31
+
32
+ it 'should support setting and getting attrs via the attrs method' do
33
+ t.attrs[:name] = 'russ'
34
+ t.attrs[:name].should == 'russ'
35
+ t.attrs.should == { :name => 'russ' }
36
+ end
37
+
38
+ it 'should support setting and getting attrs via subscripting' do
39
+ t[:name] = 'russ'
40
+ t[:name].should == 'russ'
41
+ end
42
+
43
+ it 'should take attrs into account in ==' do
44
+ t_new = Text.new('abc')
45
+ t_new[:name] = 'russ'
46
+ t[:name] = 'russ'
47
+
48
+ t.should == t_new
49
+
50
+ t_new[:name] = 'bob'
51
+ puts "T class: #{t.class}"
52
+ puts "Tnew class: #{t_new.class}"
53
+ t.should_not == t_new
54
+ end
55
+ end
@@ -0,0 +1,39 @@
1
+ describe TypeAssigner do
2
+ let(:ta) { TypeAssigner.new }
3
+ let(:paras) do
4
+ paras = %W{ para1 para2 para3 para4 para5}
5
+ paras.map {|p| Text.new(p, :type => :text) }
6
+ end
7
+
8
+ it 'should turn plain text paras into body paras' do
9
+ new_paras = ta.process(paras)
10
+ new_paras.each {|p| p[:type].should == :body}
11
+ end
12
+
13
+ it 'should make .code commands sticky while droping the code command' do
14
+ paras[0] = Text.new( '', :type => :code)
15
+ new_paras = ta.process(paras)
16
+ new_paras.size.should == 4
17
+ new_paras.each {|p| p[:type].should == :code}
18
+ end
19
+
20
+ it 'should make switch between .code and .body commands' do
21
+ paras[0] = Text.new( '', :type => :code)
22
+ paras[3] = Text.new( '', :type => :body)
23
+ new_paras = ta.process(paras)
24
+ new_paras.size.should == 3
25
+ new_paras[0][:type].should == :code
26
+ new_paras[1][:type].should == :code
27
+ new_paras[2][:type].should == :body
28
+ end
29
+
30
+ it 'should know that section and code1 are automatically followed by body' do
31
+ paras[0] = Text.new( 'section!', :type => :sec)
32
+ paras[2] = Text.new( 'code1!', :type => :code1)
33
+ new_paras = ta.process(paras)
34
+ new_paras[0][:type].should == :sec
35
+ new_paras[1][:type].should == :body
36
+ new_paras[2][:type].should == :code
37
+ new_paras[3][:type].should == :body
38
+ end
39
+ end
@@ -0,0 +1,6 @@
1
+ para1
2
+ .command1
3
+ para2
4
+ .command2 arg1 arg2
5
+ para3
6
+
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: notroff
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Russ Olsen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-15 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: NotRoff A simple text to openoffice filter
15
+ email: russ@russolsen.com
16
+ executables:
17
+ - notroff
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - readme.nr
22
+ - spec/command_processor_spec.rb
23
+ - spec/composite_processor_spec.rb
24
+ - spec/filter_spec.rb
25
+ - spec/formatter_spec.rb
26
+ - spec/hello.rb
27
+ - spec/hello.rb.out
28
+ - spec/paragraph_joiner_spec.rb
29
+ - spec/simple.nr
30
+ - spec/string_extensions_spec.rb
31
+ - spec/text_spec.rb
32
+ - spec/type_assigner_spec.rb
33
+ - spec/with_commands.nr
34
+ - lib/notroff/code_scrubber.rb
35
+ - lib/notroff/code_typer.rb
36
+ - lib/notroff/command_processor.rb
37
+ - lib/notroff/composite_processor.rb
38
+ - lib/notroff/content.xml.erb
39
+ - lib/notroff/docbook_renderer.rb
40
+ - lib/notroff/embedded.rb
41
+ - lib/notroff/filter.rb
42
+ - lib/notroff/formatter.rb
43
+ - lib/notroff/html_renderer.rb
44
+ - lib/notroff/io.rb
45
+ - lib/notroff/logger.rb
46
+ - lib/notroff/odt_renderer.rb
47
+ - lib/notroff/odt_replacer.rb
48
+ - lib/notroff/paragraph_joiner.rb
49
+ - lib/notroff/processor.rb
50
+ - lib/notroff/skel.odt
51
+ - lib/notroff/string_extensions.rb
52
+ - lib/notroff/template_expander.rb
53
+ - lib/notroff/text.rb
54
+ - lib/notroff/text_replacer.rb
55
+ - lib/notroff/tokenize.rb
56
+ - lib/notroff/type_assigner.rb
57
+ - lib/notroff.rb
58
+ - bin/notroff
59
+ homepage: http://www.russolsen.com
60
+ licenses: []
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 1.8.10
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: NotRoff A simple text to openoffice filter
83
+ test_files: []