mdoc 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.rubocop.yml +2 -0
  2. data/README.md +134 -47
  3. data/bin/mdoc +11 -32
  4. data/lib/mdoc/document/kramdown.rb +25 -0
  5. data/lib/mdoc/document.rb +87 -0
  6. data/lib/mdoc/meta.rb +14 -0
  7. data/lib/mdoc/options.rb +104 -0
  8. data/lib/mdoc/pipeline.rb +59 -0
  9. data/lib/mdoc/processor/add_title.rb +15 -0
  10. data/lib/mdoc/processor/add_toc.rb +11 -0
  11. data/lib/mdoc/processor/smart_code_block.rb +37 -0
  12. data/lib/mdoc/processor.rb +21 -0
  13. data/lib/mdoc/version.rb +3 -4
  14. data/lib/mdoc/writer.rb +18 -0
  15. data/lib/mdoc.rb +143 -2
  16. data/mdoc.gemspec +19 -19
  17. data/spec/add_title_spec.rb +10 -0
  18. data/spec/add_toc_spec.rb +12 -0
  19. data/spec/document_spec.rb +34 -0
  20. data/spec/execute_spec.rb +12 -0
  21. data/spec/find_doc_type_spec.rb +14 -0
  22. data/spec/fixtures/README.md +135 -0
  23. data/spec/fixtures/config/mdoc_a.cnf +1 -0
  24. data/spec/fixtures/config/mdoc_b.cnf +1 -0
  25. data/spec/fixtures/executes/a +0 -0
  26. data/spec/fixtures/executes/b +0 -0
  27. data/spec/fixtures/executes/c +0 -0
  28. data/spec/fixtures/general.txt +10 -0
  29. data/spec/fixtures/multikeys.html +18 -0
  30. data/{examples → spec/fixtures}/multikeys.md +10 -8
  31. data/{examples → spec/fixtures}/original.md +10 -10
  32. data/{examples → spec/fixtures}/pandoc.md +7 -7
  33. data/spec/fixtures/process.test.md +11 -0
  34. data/spec/fixtures/templates/default.html.erb +0 -0
  35. data/spec/fixtures/templates/pandoc.html.erb +0 -0
  36. data/spec/get_class_spec.rb +35 -0
  37. data/spec/kramdown_spec.rb +10 -0
  38. data/spec/meta_spec.rb +6 -0
  39. data/spec/options_spec.rb +66 -0
  40. data/spec/pipeline_spec.rb +95 -0
  41. data/spec/smart_code_block_spec.rb +10 -0
  42. data/spec/spec_helper.rb +40 -7
  43. data/spec/tpl_out_spec.rb +19 -0
  44. data/templates/default.html.erb +17 -0
  45. metadata +63 -25
  46. data/ROADMAP +0 -24
  47. data/config/members.yml +0 -16
  48. data/docs/css/jsgantt.css +0 -53
  49. data/docs/gantt.html +0 -237
  50. data/docs/js/jsgantt.js +0 -1681
  51. data/lib/mdoc/convert.rb +0 -92
  52. data/lib/mdoc/parser.rb +0 -80
  53. data/spec/parser_spec.rb +0 -32
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ Encoding:
2
+ Enabled: false
data/README.md CHANGED
@@ -1,47 +1,134 @@
1
-
2
- MDOC: markdown document converting/management tool
3
- =======================================================
4
-
5
- Requirement
6
- ----------------
7
-
8
- - Ruby 1.9.x
9
- - pandoc in path for docx/rtf conversion
10
- - xelatex in path for pdf conversion
11
-
12
- Command line options
13
- -------------------------
14
-
15
- Convert a markdown file (`readme.md`) to docx (`readme.docx`):
16
-
17
- mdoc -t docx readme.md
18
-
19
- Use `mdoc --help` for more options.
20
-
21
- Online Preview
22
- -----------------
23
-
24
- Put a markdown document to an temporary url for revew:
25
-
26
- mdoc --preview readme.md
27
-
28
- This will open your browser to show a converted document (use documentup).
29
-
30
- Meta information in header
31
- -----------------------------
32
-
33
- Three different formats are supported:
34
-
35
- 1. pandoc like three line header:
36
-
37
- % title
38
- % author
39
- % date
40
-
41
- 2. multi-header (separate by first blank line)
42
-
43
- Title: some key
44
- Author: some key
45
-
46
- 3. separator:
47
- if `% ---` is the first no-blank line, the contents between until the next `% ---` will be treated as header.
1
+ # Mdoc-gem
2
+
3
+ A tool for convert document between several different formats.
4
+
5
+ Mdoc has a modularized structure, easily extensible with custom processors.
6
+
7
+ ## Install
8
+
9
+ gem install mdoc
10
+
11
+ (requires ruby 1.9.x)
12
+
13
+ ## Synopsis
14
+
15
+ - Convert a markdown file (`readme.md` in this example) to html (use `default.html` template):
16
+
17
+ mdoc readme.md
18
+
19
+ This will create a `readme.html` file besides `readme.md`.
20
+
21
+ - Convert a markdown file with a custom template:
22
+
23
+ mdoc -t custom.html readme.md
24
+
25
+ Mdoc will try to find `custom.html.erb` from `./templates` folder, or you can specify it by:
26
+
27
+ mdoc -t custom.html -d ~/mdoc_templates readme.md
28
+
29
+ - Specify output filename:
30
+
31
+ mdoc -o README.html readme.md
32
+
33
+ Or print out to STDOUT:
34
+
35
+ mdoc -O readme.md
36
+
37
+ ## Markdown with Meta Information
38
+
39
+ The default source file format is markdown[^1]. Mdoc convert it into a document class
40
+ `Mdoc::Document::Kramdown`, with supports all extensions from Kramdown[^2].
41
+
42
+ [^1]: Wikipedia: http://en.wikipedia.org/wiki/Markdown
43
+ [^2]: Kramdown Syntax: http://kramdown.rubyforge.org/quickref.html
44
+
45
+ Additionally, you can put meta informations in the begin of your source file, in two
46
+ different format:
47
+
48
+ 1. pandoc like three line header (max 3 lines)
49
+
50
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
51
+
52
+ % title
53
+ % author
54
+ % date
55
+
56
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
57
+
58
+ 2. multi-header (first non-blank line with three or more dashes)
59
+
60
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
61
+
62
+ % ---
63
+ title: some key
64
+ author: some key
65
+ % ---
66
+
67
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68
+
69
+ The heading `%` is optional.
70
+
71
+ You can access those information from erb files by `<%= meta.title %>`
72
+
73
+ ## Processors
74
+
75
+ The following processors are enabled by default:
76
+
77
+ - `add_toc`: Add `table of contents` field in the contents body;
78
+ - `add_title`: Add a `meta.title` as a first level header in the contents body;
79
+ - `smart_code_block`: delete extra heading/trailing blank lines in code blocks;
80
+
81
+ You can disable some of the processors by:
82
+
83
+ mdoc -z add_toc,smart_code_block readme.md
84
+
85
+ ## Use Mdoc as a Library
86
+
87
+ ~~~~~~~~~~~~~~~~~~~~~~~~~ ruby
88
+
89
+ require 'mdoc'
90
+ module Mdoc
91
+ class Processor
92
+ class Custom < Processor
93
+ def process!(document)
94
+ document.meta.title += ' (draft)' # edit the document title
95
+ document.body.gsub!(/https/, 'http') # change the source body text
96
+ end
97
+
98
+ def repeatable?
99
+ true # default is false
100
+ end
101
+ end
102
+
103
+ class Other < Processor
104
+ def process!(document)
105
+ # do some thing ...
106
+ end
107
+ end
108
+ end
109
+
110
+ class Writer
111
+ class CustomWriter
112
+ def process!(document)
113
+ # fh.puts ...
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ Mdoc.convert!('somefile.md') do |pipeline|
120
+ pipeline.insert :custom # insert into the begin of the processor pipeline
121
+ pipeline.insert :other, :before => :custom # or :after => :some_processor
122
+ pipeline.remove :todo
123
+
124
+ pipeline.writer = CustomWriter
125
+ end
126
+
127
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
128
+
129
+ Unless you define a method `repeatable?` and returns `true`, one processor will
130
+ process a document at most once.
131
+
132
+ ## Tests
133
+
134
+ rubocop && rspec
data/bin/mdoc CHANGED
@@ -1,32 +1,11 @@
1
- #!/usr/bin/env ruby
2
- # vim: ft=ruby
3
-
4
- $: << 'lib' # debug only
5
-
6
- require "mdoc"
7
- require "optparse"
8
-
9
- options = {}
10
-
11
- optparse = OptionParser.new do |opts|
12
- opts.banner = "Usage: mdoc [options] filename.md"
13
- opts.separator "Options:"
14
-
15
- options[:type] = 'odt'
16
- opts.on('-t TYPE', '--type TYPE', 'output file type') do |type|
17
- options[:type] = type
18
- end
19
-
20
- opts.on_tail('-h', '--help', 'Display this screen') do
21
- puts opts
22
- exit
23
- end
24
- end
25
-
26
- optparse.parse!(ARGV)
27
-
28
- file = ARGV[0]
29
- raise "Only markdown file (`*.md`) can be parsed" unless file =~ /\.md$/
30
-
31
- klass = options[:type].to_s.capitalize
32
- eval("Mdoc::" + klass).new(file).convert
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # vi: ft=ruby
4
+
5
+ # $LOAD_PATH.unshift 'lib' # debug only
6
+ require 'mdoc'
7
+
8
+ Mdoc.load_conf_files %w[/etc/mdoc.conf ~/.mdoc.conf]
9
+ Mdoc.load_cli_options ARGV
10
+
11
+ Mdoc.execute!
@@ -0,0 +1,25 @@
1
+ require 'kramdown'
2
+
3
+ module Mdoc
4
+ class Document
5
+ class Kramdown < Document
6
+ def kramdown
7
+ # TODO: toc and other preprocessors
8
+ @kramdown = ::Kramdown::Document.new(@body) unless @kramdown
9
+ @kramdown
10
+ end
11
+
12
+ def body_html
13
+ kramdown.to_html
14
+ end
15
+
16
+ def body_latex
17
+ kramdown.to_latex
18
+ end
19
+
20
+ alias_method :body_tex, :body_latex
21
+ alias_method :to_latex, :body_latex
22
+ alias_method :to_html, :body_html
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,87 @@
1
+ require 'forwardable'
2
+
3
+ module Mdoc
4
+ class Document
5
+
6
+ LOOP_MAX = 99
7
+
8
+ extend Forwardable
9
+ attr_accessor :file, :meta, :body, :tpl_file, :out_file,
10
+ :smeta, # meta from source file
11
+ :performed # performed processor
12
+
13
+ # rubocop:disable MethodLength
14
+ def initialize(path)
15
+ if path.is_a?(String)
16
+ @file = path
17
+ path = File.new(@file, 'r:utf-8')
18
+ end
19
+
20
+ # initialize performed processor list
21
+ @performed = {}
22
+
23
+ position = nil # before meta, :meta, :body, :between, :pandoc_header
24
+ pandoc_meta, raw_meta = [], ''
25
+ @meta, @body = Meta.new, ''
26
+ path.each do |line|
27
+ # puts position.to_s + line if position
28
+ line.chomp!
29
+
30
+ if line.match(/^\s*$/)
31
+ next if position.nil? || position == :between
32
+ else
33
+ position = :body if position == :between
34
+ end
35
+
36
+ if line.match(/^\%?\s*\-{3,}\s*$/) # meta headers
37
+ position = :between if position == :meta
38
+ position = :meta unless position
39
+ next
40
+ elsif line.match(/^\%\s*/)
41
+ position = :pandoc_header if position.nil?
42
+ else
43
+ position = :body unless position # if position == :pandoc_header
44
+ end
45
+
46
+ if position == :pandoc_header
47
+ pandoc_meta << line.gsub(/^\%\s*/, '')
48
+ position = :between if pandoc_meta.size >= 3
49
+ end
50
+
51
+ raw_meta << line << "\n" if position == :meta
52
+ @body << line << "\n" if position == :body
53
+ end
54
+
55
+ @meta.load(raw_meta) if raw_meta.size > 0
56
+ if pandoc_meta.size > 0
57
+ @meta.title, @meta.author, @meta.date = pandoc_meta[0 .. 2]
58
+ end
59
+
60
+ # source meta holds meta information from source file only
61
+ @smeta = @meta.dup
62
+ end
63
+ # rubocop:ensable MethodLength
64
+
65
+ # apply processors by processor name (if corresponding processor)
66
+ # class defined.
67
+ # rubocop:disable MethodLength
68
+ def apply!(pn)
69
+ prc = Mdoc.get_processor(pn)
70
+ if performed[prc]
71
+ if prc.new.repeatable?
72
+ prc.new.process! self
73
+ performed[prc] += 1
74
+ # error if performed too many times (prevent dead loop)
75
+ raise "loop max reached: #{prc}" if performed[prc] > LOOP_MAX
76
+ end
77
+ else # not performed
78
+ prc.new.process! self
79
+ performed[prc] = 1
80
+ end
81
+ end
82
+ # rubocop:enable MethodLength
83
+
84
+ def_delegators :@meta, :title, :author, :date
85
+
86
+ end
87
+ end
data/lib/mdoc/meta.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'yaml'
2
+ require 'ostruct'
3
+
4
+ module Mdoc
5
+ ## parsed meta information from the source file
6
+ class Meta < OpenStruct
7
+
8
+ def load(contents)
9
+ # contents is expected as a hash in yaml format
10
+ YAML.load(contents).each { |k, v| self.send("#{k}=".to_sym, v) }
11
+ self
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,104 @@
1
+ require 'optparse'
2
+
3
+ module Mdoc
4
+
5
+ # default configurations
6
+ # rubocop:disable MethodLength
7
+ def load_defaults
8
+ hsh = {
9
+ template: 'html',
10
+ output: nil,
11
+ no_output: false,
12
+ processors: [],
13
+ no_processors: [],
14
+ tpl_directories: [],
15
+ s_files: [],
16
+ }
17
+
18
+ # create a dynamic struct with default values
19
+ @opts = Struct.new(*hsh.keys).new(*hsh.values)
20
+ end
21
+ # rubocop:enable MethodLength
22
+
23
+ # load configuration files (if exists)
24
+ # from a list of candidates
25
+ def load_conf_files(file_ary)
26
+ load_defaults unless opts
27
+
28
+ file_ary.each do |file|
29
+ yml = YAML.load(File.open(file, 'r:utf-8').read) if File.exists? file
30
+ set_option! yml if yml
31
+ end
32
+ end
33
+
34
+ # load command line options
35
+ # rubocop:disable LineLength, MethodLength
36
+ def load_cli_options(argv = ARGV)
37
+ load_defaults unless opts
38
+ argv = %w[-h] if argv.size == 0
39
+
40
+ OptionParser.new do |opts|
41
+ opts.banner = 'Usage: mdoc [options] file.md [file2.md]'
42
+ opts.separator ''
43
+ opts.separator 'Options: '
44
+
45
+ opts.on('-t TPL', '--template TPL', 'output file template') do |tpl|
46
+ set_option!({ template: tpl })
47
+ end
48
+
49
+ opts.on('-o FILE', '--output FILE', 'output file template') do |file|
50
+ set_option!({ output: file })
51
+ end
52
+
53
+ opts.on('-p P,P2', '--processors P,P2', 'enable processors') do |p|
54
+ p.split(',').each do |pr|
55
+ @opts.processors << pr unless @opts.processors.include?(pr)
56
+ end
57
+ end
58
+
59
+ opts.on('-z P,P2', '--disable P,P2', 'disable processors') do |op|
60
+ op.split(',').each do |pr|
61
+ @opts.no_processors << pr unless @opts.processors.include?(pr)
62
+ end
63
+ end
64
+
65
+ opts.on('-d D,D2', '--template_directories D,D2', 'directories for finding template') do |d|
66
+ d.split(',').each do |dir|
67
+ dir = File.expand_path(dir)
68
+ @opts.tpl_directories << dir unless @opts.tpl_directories.include?(dir)
69
+ end
70
+ end
71
+
72
+ opts.on('-O', '--no-output', 'dump result to STDOUT') do
73
+ @opts.no_output = true
74
+ end
75
+
76
+ opts.on_tail('-v', '--version', 'show mdoc version') do
77
+ puts Mdoc::VERSION
78
+ exit
79
+ end
80
+
81
+ opts.on_tail('-h', '--help', 'display this screen') do
82
+ puts opts
83
+ exit
84
+ end
85
+
86
+ end.parse!(argv)
87
+
88
+ set_option!({ s_files: argv })
89
+
90
+ # check consistency for related options
91
+ raise 'you can not specify output file when there are more than on source files.' if opts.output && opts.s_files.size > 0
92
+ raise 'you can not speficy output file with --no-output option' if opts.output && opts.no_output
93
+ end
94
+ # rubocop:enable LineLength, MethodLength
95
+
96
+ private
97
+
98
+ # set options from a hash, raise errors
99
+ # if a key not exists in default hash
100
+ def set_option!(hsh)
101
+ hsh.each { |k, v| @opts.send(:"#{k}=", v) }
102
+ end
103
+
104
+ end
@@ -0,0 +1,59 @@
1
+ module Mdoc
2
+ class Pipeline
3
+ attr_accessor :writer
4
+ attr_reader :processors
5
+
6
+ def initialize(ary = [])
7
+ @processors = []
8
+ append ary
9
+ end
10
+
11
+ # get processor class in name
12
+ def get_prc(prc)
13
+ prc = [prc] unless prc.is_a?(Array)
14
+ prc.map { |pn| Mdoc.get_processor(pn) }
15
+ end
16
+
17
+ def insert(prc, opts = {})
18
+ prc = get_prc(prc)
19
+ raise 'can not set before with after' if opts[:before] && opts[:after]
20
+ ankor = opts[:before] || opts[:after]
21
+ offset = get_offset(opts) if ankor
22
+ ankor ? @processors.insert(offset, *prc) :
23
+ @processors = prc.concat(@processors)
24
+ end
25
+
26
+ # from :before, :after, calculate insert offset
27
+ def get_offset(opts)
28
+ phash = Hash[@processors.map.with_index.to_a]
29
+ if opts[:before]
30
+ offset = phash[Mdoc.get_processor(opts[:before])]
31
+ elsif opts[:after]
32
+ offset = phash[Mdoc.get_processor(opts[:after])] + 1
33
+ end
34
+ offset
35
+ end
36
+
37
+ def append(prc)
38
+ prc = get_prc(prc)
39
+ prc.each { |p| @processors << p }
40
+ end
41
+
42
+ def remove(prc)
43
+ prc = get_prc(prc)
44
+ prc.map { |pn| @processors.delete(pn) }
45
+ end
46
+
47
+ # recursively apply processors to document
48
+ def apply!(document)
49
+ @processors.each do |pn|
50
+ prc = Mdoc.get_processor(pn)
51
+ prc.new.pre_processors.each { |p| document.apply!(p) }
52
+ document.apply!(prc)
53
+ prc.new.post_processors.each { |p| document.apply!(p) }
54
+ end
55
+
56
+ writer.new.process!(document)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,15 @@
1
+ module Mdoc
2
+ class Processor
3
+ class AddTitle < Processor
4
+ def process!(doc)
5
+ title = doc.title
6
+ if title
7
+ unless doc.body =~ /^\s*\#*\s*#{title}/
8
+ title = '# ' + title + "\n\n"
9
+ doc.body = title + doc.body
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Mdoc
2
+ class Processor
3
+ class AddToc < Processor
4
+ def process!(doc)
5
+ unless doc.body =~ /\{\:toc\}/
6
+ doc.body = "\n* Table of Contents\n{:toc}\n\n" + doc.body
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ # Delete extra blank lines inside ~~~~~ code bloks
2
+ #
3
+ # rubocop:disable MethodLength
4
+ module Mdoc
5
+ class Processor
6
+ class SmartCodeBlock < Processor
7
+ def process!(doc)
8
+ odd, last, hold = false, false, 0
9
+ new_body = ''
10
+ doc.body.split(/\n/).each do |line|
11
+ if line =~ /^\s*~{3,}\s*\w*\s*/
12
+ hold = 0 if odd
13
+ odd = odd ? false : true
14
+ last = true
15
+ else
16
+ next if last && odd && (line =~ /^\s*$/)
17
+
18
+ if line =~ /^\s*$/
19
+ hold += 1 # hold the line
20
+ next
21
+ end
22
+
23
+ last = false
24
+ end
25
+
26
+ hold.times { new_body << "\n" }
27
+ hold = 0
28
+ new_body << line << "\n"
29
+ end
30
+
31
+ doc.body = new_body.chomp
32
+ # puts doc.body
33
+ end
34
+ end
35
+ end
36
+ end
37
+ # rubocop:enable MethodLength
@@ -0,0 +1,21 @@
1
+ module Mdoc
2
+ class Processor
3
+ # add processors apply before self
4
+ def pre_processors
5
+ []
6
+ end
7
+
8
+ # apply those processors after self
9
+ def post_processors
10
+ []
11
+ end
12
+
13
+ # do the real jobs, raise for errors
14
+ def process!(document); end
15
+
16
+ # by default, can not perform more than one times for a single document
17
+ def repeatable?
18
+ false
19
+ end
20
+ end
21
+ end
data/lib/mdoc/version.rb CHANGED
@@ -1,4 +1,3 @@
1
- module Mdoc
2
- VERSION = '0.0.3'
3
- end
4
-
1
+ module Mdoc
2
+ VERSION = '0.0.5'
3
+ end
@@ -0,0 +1,18 @@
1
+ require 'tilt/erb'
2
+
3
+ module Mdoc
4
+ class Writer
5
+ attr_accessor :tilt
6
+
7
+ def out(doc)
8
+ Mdoc.opts.no_output ? $stdout : File.new(doc.out_file, 'w:utf-8')
9
+ end
10
+
11
+ def process!(doc)
12
+ @tilt = Tilt::ERBTemplate.new(doc.tpl_file)
13
+ oh = out(doc)
14
+ oh.write @tilt.render doc
15
+ oh.close unless oh == $stdout
16
+ end
17
+ end
18
+ end