murdoc 0.1.13 → 0.2.0
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.
- checksums.yaml +4 -4
- data/.gitignore +16 -0
- data/.rspec +1 -0
- data/.travis.yml +1 -3
- data/Gemfile +1 -10
- data/README.md +4 -4
- data/Rakefile +1 -23
- data/bin/murdoc +1 -1
- data/bin/murdoc-strip-comments +16 -3
- data/lib/murdoc.rb +36 -16
- data/lib/murdoc/annotator.rb +15 -137
- data/lib/murdoc/formatted_paragraph.rb +41 -0
- data/lib/murdoc/languages/base.rb +56 -0
- data/lib/murdoc/languages/coffeescript.rb +23 -0
- data/lib/murdoc/languages/html.rb +12 -33
- data/lib/murdoc/languages/javascript.rb +14 -35
- data/lib/murdoc/languages/markdown.rb +24 -0
- data/lib/murdoc/languages/ruby.rb +13 -34
- data/lib/murdoc/paragraph.rb +1 -37
- data/lib/murdoc/{formatter.rb → renderer.rb} +2 -2
- data/lib/murdoc/scanner.rb +138 -0
- data/lib/murdoc/version.rb +3 -0
- data/markup/stylesheet.css +136 -173
- data/markup/template.haml +10 -9
- data/markup/template_multifile.haml +13 -11
- data/murdoc.gemspec +27 -75
- data/spec/murdoc/annotator_spec.rb +42 -60
- data/spec/murdoc/languages/base_spec.rb +23 -0
- data/spec/murdoc/paragraph_spec.rb +2 -6
- data/spec/murdoc/{formatter_spec.rb → renderer_spec.rb} +3 -3
- data/spec/murdoc/scanner_spec.rb +101 -0
- data/spec/spec_helper.rb +10 -1
- metadata +29 -18
- data/Gemfile.lock +0 -71
- data/VERSION +0 -1
- data/autotest/discover.rb +0 -1
@@ -0,0 +1,56 @@
|
|
1
|
+
module Murdoc
|
2
|
+
module Languages
|
3
|
+
# Base language module
|
4
|
+
#
|
5
|
+
# Any new language module should inherit from Base, redefine .extensions
|
6
|
+
# and .comment_symbols methods, if needed, and add itself to Languages.map
|
7
|
+
# map.
|
8
|
+
class Base
|
9
|
+
def self.applies_for?(filename)
|
10
|
+
if extensions.include?(File.extname(filename).sub(/^\./, ''))
|
11
|
+
true
|
12
|
+
else
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.annotation_only?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.extensions
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.comment_symbols
|
26
|
+
{
|
27
|
+
single_line: nil,
|
28
|
+
multiline: nil
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.name
|
33
|
+
super.sub(/^(.*::)?([^:]+)$/, '\\2').
|
34
|
+
gsub(/([a-z])([A-Z])/, '\\1_\\2').
|
35
|
+
downcase.to_sym
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.map
|
40
|
+
@map ||= {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.list
|
44
|
+
map.values
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.get(name)
|
48
|
+
map.fetch(name, Base)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.detect(filename)
|
52
|
+
name, lang = map.detect {|name, lang| lang.applies_for?(filename) }
|
53
|
+
name
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Html language module
|
2
|
+
module Murdoc
|
3
|
+
module Languages
|
4
|
+
class Coffeescript < Base
|
5
|
+
def self.comment_symbols
|
6
|
+
{
|
7
|
+
single_line: '#',
|
8
|
+
multiline: {
|
9
|
+
:begin => "###",
|
10
|
+
:end => "###"
|
11
|
+
}
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.extensions
|
16
|
+
['coffee']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
self.map[:coffee] = Coffeescript
|
21
|
+
self.map[:coffeescript] = Coffeescript
|
22
|
+
end
|
23
|
+
end
|
@@ -1,43 +1,22 @@
|
|
1
1
|
# Html language module
|
2
2
|
module Murdoc
|
3
3
|
module Languages
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
if File.extname(filename) == ".html"
|
14
|
-
:html
|
15
|
-
else
|
16
|
-
super if defined?(super)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
4
|
+
class Html < Base
|
5
|
+
def self.comment_symbols
|
6
|
+
{
|
7
|
+
single_line: nil,
|
8
|
+
multiline: {
|
9
|
+
:begin => "<!--",
|
10
|
+
:end => "-->"
|
11
|
+
}
|
12
|
+
}
|
20
13
|
end
|
21
14
|
|
22
|
-
|
23
|
-
|
24
|
-
def comment_symbols
|
25
|
-
if source_type == "html"
|
26
|
-
{:single_line => nil, :multiline => {:begin => "<!--", :end => "-->"}}
|
27
|
-
else
|
28
|
-
super if defined?(super)
|
29
|
-
end
|
30
|
-
end
|
15
|
+
def self.extensions
|
16
|
+
['html']
|
31
17
|
end
|
32
18
|
end
|
33
|
-
end
|
34
|
-
|
35
|
-
class Annotator
|
36
|
-
include Languages::Html::Annotator
|
37
|
-
include Languages::Html::CommentSymbols
|
38
|
-
end
|
39
19
|
|
40
|
-
|
41
|
-
include Languages::Html::CommentSymbols
|
20
|
+
self.map[:html] = Html
|
42
21
|
end
|
43
22
|
end
|
@@ -1,43 +1,22 @@
|
|
1
|
-
# Javascript language module
|
2
1
|
module Murdoc
|
3
2
|
module Languages
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
if File.extname(filename) == ".js"
|
14
|
-
:javascript
|
15
|
-
else
|
16
|
-
super if defined?(super)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
3
|
+
class Javascript < Base
|
4
|
+
def self.comment_symbols
|
5
|
+
{
|
6
|
+
single_line: '//',
|
7
|
+
multiline: {
|
8
|
+
:begin => "/*",
|
9
|
+
:end => "*/"
|
10
|
+
}
|
11
|
+
}
|
20
12
|
end
|
21
13
|
|
22
|
-
|
23
|
-
|
24
|
-
def comment_symbols
|
25
|
-
if source_type == "javascript"
|
26
|
-
{:single_line => "//", :multiline => {:begin => "/*", :end => "*/"}}
|
27
|
-
else
|
28
|
-
super if defined?(super)
|
29
|
-
end
|
30
|
-
end
|
14
|
+
def self.extensions
|
15
|
+
['js']
|
31
16
|
end
|
32
17
|
end
|
33
|
-
end
|
34
|
-
|
35
|
-
class Annotator
|
36
|
-
include Languages::Javascript::Annotator
|
37
|
-
include Languages::Javascript::CommentSymbols
|
38
|
-
end
|
39
18
|
|
40
|
-
|
41
|
-
|
19
|
+
self.map[:js] = Javascript
|
20
|
+
self.map[:javascript] = Javascript
|
42
21
|
end
|
43
|
-
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Html language module
|
2
|
+
module Murdoc
|
3
|
+
module Languages
|
4
|
+
class Markdown < Base
|
5
|
+
def self.comment_symbols
|
6
|
+
{
|
7
|
+
single_line: nil,
|
8
|
+
multiline: nil
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.annotation_only?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.extensions
|
17
|
+
['markdown', 'md']
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
self.map[:md] = Markdown
|
22
|
+
self.map[:markdown] = Markdown
|
23
|
+
end
|
24
|
+
end
|
@@ -1,43 +1,22 @@
|
|
1
1
|
# Ruby language module
|
2
2
|
module Murdoc
|
3
3
|
module Languages
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
if File.extname(filename) == ".rb"
|
14
|
-
:ruby
|
15
|
-
else
|
16
|
-
super if defined?(super)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
4
|
+
class Ruby < Base
|
5
|
+
def self.comment_symbols
|
6
|
+
{
|
7
|
+
single_line: '#',
|
8
|
+
multiline: {
|
9
|
+
:begin => "=begin",
|
10
|
+
:end => "=end"
|
11
|
+
}
|
12
|
+
}
|
20
13
|
end
|
21
14
|
|
22
|
-
|
23
|
-
|
24
|
-
def comment_symbols
|
25
|
-
if source_type == "ruby"
|
26
|
-
{:single_line => "#", :multiline => {:begin => "=begin", :end => "=end"}}
|
27
|
-
else
|
28
|
-
super if defined?(super)
|
29
|
-
end
|
30
|
-
end
|
15
|
+
def self.extensions
|
16
|
+
['rb']
|
31
17
|
end
|
32
18
|
end
|
33
|
-
end
|
34
|
-
|
35
|
-
class Annotator
|
36
|
-
include Languages::Ruby::Annotator
|
37
|
-
include Languages::Ruby::CommentSymbols
|
38
|
-
end
|
39
19
|
|
40
|
-
|
41
|
-
include Languages::Ruby::CommentSymbols
|
20
|
+
self.map[:ruby] = Ruby
|
42
21
|
end
|
43
|
-
end
|
22
|
+
end
|
data/lib/murdoc/paragraph.rb
CHANGED
@@ -13,48 +13,12 @@ module Murdoc
|
|
13
13
|
attr_accessor :annotation
|
14
14
|
attr_accessor :source_type
|
15
15
|
attr_accessor :starting_line
|
16
|
-
attr_accessor :options
|
17
16
|
|
18
|
-
def initialize(source, annotation, starting_line = 0, source_type = nil
|
17
|
+
def initialize(source, annotation, starting_line = 0, source_type = nil)
|
19
18
|
self.source = source
|
20
19
|
self.annotation = annotation
|
21
20
|
self.starting_line = starting_line
|
22
21
|
self.source_type = source_type
|
23
|
-
self.options = options
|
24
|
-
end
|
25
|
-
|
26
|
-
def source_type
|
27
|
-
@source_type
|
28
|
-
end
|
29
|
-
|
30
|
-
def source_type=(source_type)
|
31
|
-
@source_type = source_type.to_s
|
32
|
-
end
|
33
|
-
|
34
|
-
|
35
|
-
def formatted_annotation
|
36
|
-
if defined?(Markdown)
|
37
|
-
Markdown.new(annotation, :smart).to_html
|
38
|
-
else
|
39
|
-
Kramdown::Document.new(annotation, :input => :markdown).to_html
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def formatted_source
|
44
|
-
@formatted_source ||= if pygments_installed? && options[:highlight_source]
|
45
|
-
IO.popen("pygmentize -l #{source_type} -O encoding=UTF8 -f html -O nowrap", "w+") do |pipe|
|
46
|
-
pipe.puts source
|
47
|
-
pipe.close_write
|
48
|
-
pipe.read
|
49
|
-
end
|
50
|
-
else
|
51
|
-
CGI.escapeHTML(source)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
protected
|
56
|
-
def pygments_installed?
|
57
|
-
@@pygments_installed ||= ENV['PATH'].split(':').any? { |dir| File.exist?("#{dir}/pygmentize") }
|
58
22
|
end
|
59
23
|
end
|
60
24
|
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module Murdoc
|
4
|
+
class Scanner
|
5
|
+
attr_reader :language
|
6
|
+
|
7
|
+
def initialize(language)
|
8
|
+
@language = language
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(source, do_not_count_comment_lines = false)
|
12
|
+
paragraphs = []
|
13
|
+
ss = StringScanner.new(source)
|
14
|
+
line = i = src_line = 0
|
15
|
+
|
16
|
+
loop do
|
17
|
+
comment_lines = []
|
18
|
+
code_lines = []
|
19
|
+
|
20
|
+
# Multi line comments
|
21
|
+
if has_mlc?
|
22
|
+
while (ss.scan(mlcb_regex))
|
23
|
+
comment = ''
|
24
|
+
|
25
|
+
while (!ss.eos? && !ss.match?(/.*#{mlce_regex}/))
|
26
|
+
i += 1
|
27
|
+
comment << ss.scan(/.*?$/)
|
28
|
+
comment << ss.getch.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
if (fragment = ss.scan(/.*#{mlce_regex}/))
|
32
|
+
comment << fragment.sub(mlce_regex, '')
|
33
|
+
end
|
34
|
+
|
35
|
+
ss.scan(/[ \t]*\n/) # skip trailing whitespace and a newline
|
36
|
+
comment_lines << remove_common_space_prefix(comment)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Single line comments
|
41
|
+
if has_slc?
|
42
|
+
while (ss.scan(slc_regex))
|
43
|
+
comment = ''
|
44
|
+
comment << ss.scan(/.*?$/)
|
45
|
+
comment << ss.getch.to_s
|
46
|
+
comment_lines << comment
|
47
|
+
i += 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# Code
|
53
|
+
empty_leading_lines_count = skip_empty_lines(ss)
|
54
|
+
i += empty_leading_lines_count
|
55
|
+
src_line += empty_leading_lines_count
|
56
|
+
|
57
|
+
line = do_not_count_comment_lines ? src_line : i
|
58
|
+
while (!comment_start?(ss) && !ss.eos?)
|
59
|
+
code = ss.scan(/^.*?$/)
|
60
|
+
code << ss.getch.to_s
|
61
|
+
code_lines << code
|
62
|
+
i += 1
|
63
|
+
src_line += 1
|
64
|
+
end
|
65
|
+
|
66
|
+
code = post_process_code(code_lines.join(''))
|
67
|
+
comments = post_process_comments(comment_lines.join(''))
|
68
|
+
|
69
|
+
paragraphs << Paragraph.new(code,
|
70
|
+
comments,
|
71
|
+
line,
|
72
|
+
language.name)
|
73
|
+
|
74
|
+
break if ss.eos?
|
75
|
+
end
|
76
|
+
|
77
|
+
paragraphs
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
def post_process_code(code)
|
83
|
+
code.rstrip
|
84
|
+
end
|
85
|
+
|
86
|
+
def post_process_comments(comments)
|
87
|
+
comments.strip.gsub(/^\s(\S)/, '\\1')
|
88
|
+
end
|
89
|
+
|
90
|
+
def comment_start?(ss)
|
91
|
+
(has_slc? && ss.match?(slc_regex)) ||
|
92
|
+
(has_mlc? && ss.match?(mlcb_regex))
|
93
|
+
end
|
94
|
+
|
95
|
+
def skip_empty_lines(ss)
|
96
|
+
i = 0
|
97
|
+
while (ss.scan(/\s*?$/) && !ss.eos?)
|
98
|
+
i += 1
|
99
|
+
ss.getch
|
100
|
+
end
|
101
|
+
i
|
102
|
+
end
|
103
|
+
|
104
|
+
def remove_common_space_prefix(str)
|
105
|
+
lines = str.split("\n")
|
106
|
+
# delete empty leading and trailing lines
|
107
|
+
lines.delete_at(0) while lines[0] && lines[0].empty?
|
108
|
+
lines.delete_at(-1) while lines[-1] && lines[-1].empty?
|
109
|
+
|
110
|
+
prefix_lengths = lines.map {|l| l.match(/^( *)/)[1].length }.reject(&:zero?)
|
111
|
+
prefix = ' ' * (prefix_lengths.min || 0)
|
112
|
+
lines.map {|line| line.sub(/^#{prefix}/, '') }.join("\n")
|
113
|
+
end
|
114
|
+
|
115
|
+
def has_slc?
|
116
|
+
!!language.comment_symbols[:single_line]
|
117
|
+
end
|
118
|
+
|
119
|
+
def slc_regex
|
120
|
+
return @slc_regex unless @slc_regex.nil?
|
121
|
+
@slc_regex = has_slc? && /^[ \t]*#{Regexp.escape(language.comment_symbols[:single_line])}/
|
122
|
+
end
|
123
|
+
|
124
|
+
def has_mlc?
|
125
|
+
language.comment_symbols[:multiline] &&
|
126
|
+
language.comment_symbols[:multiline][:begin] &&
|
127
|
+
language.comment_symbols[:multiline][:end]
|
128
|
+
end
|
129
|
+
|
130
|
+
def mlcb_regex
|
131
|
+
has_mlc? && /^[ \t]*#{Regexp.escape(language.comment_symbols[:multiline][:begin])}/
|
132
|
+
end
|
133
|
+
|
134
|
+
def mlce_regex
|
135
|
+
has_mlc? && /#{Regexp.escape(language.comment_symbols[:multiline][:end])}/
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|