mdoc 0.0.3 → 0.0.5

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.
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