markascend 0.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,220 @@
1
+ module Markascend
2
+ class Parser
3
+ REC_START = /\A[\+\-\>]\ /
4
+ NON_PARA_START = /
5
+ ^[\+\-\>]\ # rec block
6
+ |
7
+ ^\|\ *(?!\d)\w*\ *$ # block code
8
+ |
9
+ ^h[1-6](?:\#\w+(?:-\w+)*)?\ # header
10
+ /x
11
+ REC_BLOCK_STARTS = {
12
+ '+ ' => /\+\ /,
13
+ '- ' => /\-\ /,
14
+ '> ' => /\>\ /
15
+ }
16
+
17
+ def initialize env, src
18
+ @src = StringScanner.new src
19
+ @env = env
20
+ @top_level = @env.srcs.empty?
21
+ @env.srcs.push @src
22
+ end
23
+
24
+ def parse
25
+ @out = []
26
+ while parse_new_line or parse_rec_block or parse_hx or parse_block_code or parse_paragraph
27
+ end
28
+ unless @src.eos?
29
+ @env.warn 'reached end of input'
30
+ end
31
+ @env.srcs.pop
32
+
33
+ @out.map! do |(node, content)|
34
+ case node
35
+ when :footnode_id_ref
36
+ if content < 1 or content > @env.footnotes.size
37
+ raise "footnote not defined: #{content}"
38
+ end
39
+ %Q|<a href="#footnote-#{content}">#{content}</a>|
40
+ when :footnode_acronym_ref
41
+ unless index = @env.footnotes.find_index{|k, _| k == content }
42
+ raise "footnote note defined: #{content}"
43
+ end
44
+ %Q|<a href="#footnote-#{index + 1}">#{content}</a>|
45
+ else
46
+ node
47
+ end
48
+ end
49
+ @out.join
50
+ end
51
+
52
+ def warnings
53
+ @env.warnings
54
+ end
55
+
56
+ def parse_new_line
57
+ if @src.scan(/\ *\n/)
58
+ @out << '<br>'
59
+ true
60
+ end
61
+ end
62
+
63
+ def parse_rec_block
64
+ return unless @src.match? REC_START
65
+
66
+ # first elem, scans the whole of following string:
67
+ # |
68
+ # + line 1 of the li. an embed \macro
69
+ # macro content
70
+ # line 2 of the li.
71
+ # line 3 of the li.
72
+ #
73
+ # NOTE that first line is always not indented
74
+ line, block = scan_line_and_block 2
75
+ return unless line
76
+ rec_start = line[REC_START]
77
+ wrapper_begin, elem_begin, elem_end, wrapper_end =
78
+ case rec_start
79
+ when '+ '; ['<ol>', '<li>', '</li>', '</ol>']
80
+ when '- '; ['<ul>', '<li>', '</li>', '</ul>']
81
+ when '> '; ['', '<quote>', '</quote>', '']
82
+ end
83
+ elems = ["#{line[2..-1]}#{block}"]
84
+
85
+ # followed up elems
86
+ block_start_re = REC_BLOCK_STARTS[rec_start]
87
+ while @src.match?(block_start_re)
88
+ line, block = scan_line_and_block 2
89
+ break unless line
90
+ elems << "#{line[2..-1]}#{block}"
91
+ end
92
+
93
+ # generate
94
+ @out << wrapper_begin
95
+ elems.each do |elem|
96
+ @out << elem_begin
97
+ elem.rstrip!
98
+ @out << Parser.new(@env, elem).parse
99
+ @out << elem_end
100
+ end
101
+ @out << wrapper_end
102
+ true
103
+ end
104
+
105
+ def parse_hx
106
+ hx = @src.scan /h[1-6](\#\w+(-\w+)*)?\ /
107
+ return unless hx
108
+ hx.strip!
109
+
110
+ # fiddle id
111
+ if @env.sandbox
112
+ if @env.toc
113
+ id = "-#{@env.toc.size}"
114
+ end
115
+ else
116
+ if hx.size > 2
117
+ id = hx[3..-1]
118
+ elsif @env.toc
119
+ id = "-#{@env.toc.size}"
120
+ end
121
+ end
122
+ if id
123
+ id_attr = %Q{ id="#{id}"}
124
+ end
125
+ hx = hx[0...2]
126
+
127
+ @out << "<#{hx}#{id_attr}>"
128
+ line, block = scan_line_and_block
129
+ if line
130
+ out = []
131
+ LineUnit.new(@env, line, block).parse(out)
132
+ out.pop if out.last == :"<br>"
133
+ out.each do |token|
134
+ @out << token
135
+ end
136
+ if id and @env.toc
137
+ @env.toc[id] = [hx[1].to_i, out.join]
138
+ end
139
+ end
140
+ @out << "</#{hx}>"
141
+
142
+ # consume one more empty line if possible
143
+ @src.scan /\ *\n/ if (!block or block.empty?)
144
+ true
145
+ end
146
+
147
+ def parse_block_code
148
+ if lang = @src.scan(/\|\ *(?!\d)\w*\ *\n/)
149
+ lang = lang[1..-1].strip
150
+ if lang.empty? and @env.hi
151
+ lang = @env.hi
152
+ end
153
+ block = @src.scan(/
154
+ (
155
+ \ *\n # empty line
156
+ |
157
+ \ {2,}.*\n # line indented equal to 2 or more than 2
158
+ )*
159
+ /x)
160
+ block.gsub!(/^ /, '')
161
+ block.rstrip!
162
+ @out << (::Markascend.hilite block, lang)
163
+ true
164
+ end
165
+ end
166
+
167
+ def parse_paragraph
168
+ @src.match?(/\ */)
169
+ indent = @src.matched_size
170
+ line, block = scan_line_and_block
171
+ if line
172
+ @out << :"<p>" if @top_level
173
+ LineUnit.new(@env, line, block).parse(@out)
174
+
175
+ # same indent and not matching rec/code blocks
176
+ while (@src.match?(/\ */); @src.matched_size) == indent and !@src.match?(NON_PARA_START)
177
+ line, block = scan_line_and_block
178
+ break unless line
179
+ LineUnit.new(@env, line, block).parse(@out)
180
+ end
181
+ # consume one more empty line if possible
182
+ @src.scan /\ *\n/
183
+ # delete back last <br>
184
+ @out.pop if @out.last == :"<br>"
185
+ @out << :"</p>" if @top_level
186
+ true
187
+ end
188
+ end
189
+
190
+ def scan_line_and_block undent=:all
191
+ if line = @src.scan(/.+?(?:\n|\z)/)
192
+ block = @src.scan(/
193
+ (?:\ *\n)* # leading empty lines
194
+ (?<indent>\ {2,})
195
+ .*?(?:\n|\z) # first line content
196
+ (
197
+ \g<indent>\ *
198
+ .*?(?:\n|\z) # rest line content
199
+ |
200
+ (?:\ *\n)* # rest empty line
201
+ )*
202
+ /x)
203
+ block = nil if block =~ /\A\s*\z/
204
+ # undent block
205
+ if block
206
+ if undent == :all
207
+ /
208
+ (?:\ *\n)*
209
+ (?<indent>\ {2,})
210
+ /x =~ block
211
+ block.gsub! /^#{indent}/, ''
212
+ else
213
+ block.gsub! /^\ \ /, ''
214
+ end
215
+ end
216
+ end
217
+ [line, block]
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,87 @@
1
+ module Markascend
2
+ # video
3
+
4
+ class Macro
5
+ # accepts:
6
+ # |
7
+ # @somebody
8
+ def parse_twitter
9
+ if content.start_with?('@')
10
+ text = ::Markascend.escape_html content
11
+ link = "https://twitter.com/#{::Markascend.escape_attr content[1..-1]}"
12
+ else
13
+ # TODO embed tweet
14
+ raise 'not implemented yet'
15
+ end
16
+ %Q{<a href="#{link}">#{text}</a>}
17
+ end
18
+
19
+ # accepts:
20
+ # |
21
+ # @somebody
22
+ def parse_weibo
23
+ if content.start_with?('@')
24
+ text = ::Markascend.escape_html content
25
+ link = "https://weibo.com/#{::Markascend.escape_attr content[1..-1]}"
26
+ else
27
+ # TODO embed tweet
28
+ raise 'not implemented yet'
29
+ end
30
+ %Q{<a href="#{link}">#{text}</a>}
31
+ end
32
+
33
+ def parse_wiki
34
+ %Q|<a href="http://en.wikipedia.org/wiki/#{content}">#{content}</a>|
35
+ end
36
+
37
+ # embed gist, accepts:
38
+ # |
39
+ # luikore/737238
40
+ # gist.github.com/luikore/737238
41
+ # https://gist.github.com/luikore/737238
42
+ def parse_gist
43
+ src = content.strip
44
+ if src =~ /\A\w+(\-\w+)*\/\d+/
45
+ src = "https://gist.github.com/#{src}"
46
+ else
47
+ src.sub! /\A(?=gist\.github\.com)/, 'https://'
48
+ end
49
+ src.sub!(/((?<!\.js)|\.git)$/, '.js')
50
+ %Q|<script src="#{src}"></script>|
51
+ end
52
+
53
+ # embed video, calculates embed iframe by urls from various simple formats, but not accept iframe code
54
+ def parse_video
55
+ # standard
56
+ unless /\A\s*(?<width>\d+)x(?<height>\d+)\s+(?<url>.+)\z/ =~ content
57
+ env.warn 'can not parse \video content, should be "#{WIDTH}x#{HEIGHT} #{URL}"'
58
+ return
59
+ end
60
+
61
+ case url
62
+ when /youtu\.?be/
63
+ # NOTE merging them into one regexp fails (because longest match?)
64
+ unless id = url[/(?<=watch\?v=)\w+/] || url[/(?<=embed\/)\w+/] || url[/(?<=youtu\.be\/)\w+/]
65
+ env.warn 'can not parse youtube id'
66
+ return
67
+ end
68
+ %Q|<iframe width="#{width}" height="#{height}" src="https://www.youtube-nocookie.com/embed/#{id}?rel=0" frameborder="0" allowfullscreen></iframe>|
69
+ when /vimeo/
70
+ unless id = url[/(?<=vimeo\.com\/)\w+/]
71
+ env.warn 'can not parse vimeo id, should use link like this "http://vimeo.com/#{DIGITS}"'
72
+ return
73
+ end
74
+ %Q|<iframe width="#{width}" height="#{height}" src="https://player.vimeo.com/video/#{id}" frameborder="0" allowFullScreen></iframe>|
75
+ when /sm/
76
+ unless id = url[/\bsm\d+/]
77
+ env.warn 'can not find "sm#{DIGITS}" from link'
78
+ return
79
+ end
80
+ %Q|<script src="https://ext.nicovideo.jp/thumb_watch/#{id}?w=#{width}&h=#{height}"></script>"|
81
+ else
82
+ env.warn 'failed to parse video link, currently only youtube, vimeo and niconico are supported'
83
+ return
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,176 @@
1
+ def osascript src
2
+ IO.popen 'osascript', 'r+' do |f|
3
+ f.puts src
4
+ f.close_write
5
+ f.read
6
+ end
7
+ end
8
+
9
+ def get_default_browser
10
+ # search handler for http
11
+ res = osascript <<-APPLESCRIPT
12
+ tell (system attribute "sysv") to set MacOS_version to it mod 4096 div 16
13
+ if MacOS_version is 5 then
14
+ set {a1, a2} to {1, 2}
15
+ else
16
+ set {a1, a2} to {2, 1}
17
+ end if
18
+ set pListpath to (path to preferences as Unicode text) & "com.apple.LaunchServices.plist"
19
+ tell application "System Events"
20
+ repeat with i in property list items of property list item 1 of contents of property list file pListpath
21
+ if value of property list item a2 of i is "http" then
22
+ return value of property list item a1 of i
23
+ end if
24
+ end repeat
25
+ return "com.apple.Safari"
26
+ end tell
27
+ APPLESCRIPT
28
+ case res
29
+ when /canary/
30
+ "Google Chrome Canary"
31
+ when /chrome/
32
+ "Google Chrome"
33
+ when /safari/
34
+ "Safari"
35
+ else
36
+ raise "browser not supported yet"
37
+ end
38
+ end
39
+
40
+ # [window, tab]
41
+ def search_tab_with_url browser, url
42
+ case browser
43
+ when /Chrome/
44
+ res = osascript <<-APPLESCRIPT
45
+ set urls to ""
46
+ tell application "#{browser}"
47
+ set i to 1
48
+ repeat with w in windows
49
+ set j to 1
50
+ repeat with t in (tabs of w)
51
+ set u to the URL of t
52
+ set u to (i as text) & "," & (j as text) & u
53
+ set urls to urls & u & return
54
+ set j to j + 1
55
+ end repeat
56
+ set i to i + 1
57
+ end repeat
58
+ end tell
59
+ return urls
60
+ APPLESCRIPT
61
+ when /Safari/
62
+ # URL is not set when loading, so need a stupid null testing
63
+ res = osascript <<-APPLESCRIPT
64
+ set urls to ""
65
+ tell application "Safari"
66
+ set i to 1
67
+ repeat with w in windows
68
+ set j to 1
69
+ repeat with t in (tabs of w)
70
+ set u to the URL of t
71
+ set isNull to false
72
+ try
73
+ get u
74
+ on error
75
+ set isNull to true
76
+ end try
77
+ if isNull then
78
+ else
79
+ set u to (i as text) & "," & (j as text) & u
80
+ set urls to urls & u & return
81
+ end if
82
+ set j to j + 1
83
+ end repeat
84
+ set i to i + 1
85
+ end repeat
86
+ end tell
87
+ return urls
88
+ APPLESCRIPT
89
+ end
90
+ res.strip.split(/[\r\n]+/).each do |u|
91
+ if u.index url
92
+ /^(?<window>\d+),(?<tab>\d+)/ =~ u
93
+ return [window, tab]
94
+ end
95
+ end
96
+ nil
97
+ end
98
+
99
+ def activate_url url
100
+ browser = get_default_browser
101
+ window, tab = search_tab_with_url browser, url
102
+ url.gsub! '"', '\"'
103
+ if tab # activate existing doc
104
+ case browser
105
+ when /Chrome/
106
+ osascript <<-APPLESCRIPT
107
+ tell application "#{browser}"
108
+ activate
109
+ set active tab index of window #{window} to #{tab}
110
+ reload tab #{tab} of window #{window}
111
+ end tell
112
+ APPLESCRIPT
113
+ when /Safari/
114
+ osascript <<-APPLESCRIPT
115
+ tell application "#{browser}"
116
+ activate
117
+ tell window #{window} to set current tab to tab #{tab}
118
+ set the url of the front document to "#{url}"
119
+ end tell
120
+ APPLESCRIPT
121
+ end
122
+ else # open new doc
123
+ case browser
124
+ when /Chrome/
125
+ # NOTE some user sets handler for .html to editor than browser
126
+ # so we don't use `open` command here
127
+ osascript <<-APPLESCRIPT
128
+ tell application "#{browser}"
129
+ activate
130
+ make new tab at the end of tabs of window 1
131
+ set the URL of active tab of window 1 to "#{url}"
132
+ end tell
133
+ APPLESCRIPT
134
+ when /Safari/
135
+ osascript <<-APPLESCRIPT
136
+ tell application "#{browser}"
137
+ activate
138
+ make new document at the end of documents
139
+ set the url of the front document to "#{url}"
140
+ end tell
141
+ APPLESCRIPT
142
+ end
143
+ end
144
+ end
145
+
146
+ desc "run tests"
147
+ task :default do
148
+ dir = File.dirname __FILE__
149
+ Dir.glob "#{dir}/test/*_test.rb" do |f|
150
+ require f
151
+ end
152
+ end
153
+
154
+ desc "generate gh-pages"
155
+ task :site do
156
+ # layout
157
+ require "slim"
158
+ template = Slim::Template.new "doc/layout.slim"
159
+
160
+ # html
161
+ require_relative "lib/markascend"
162
+ %w[syntax api].each do |doc|
163
+ File.open "gh-pages/#{doc}.html", 'w' do |f|
164
+ f.puts template.render{ Markascend.compile File.read "doc/#{doc}.ma" }
165
+ end
166
+ end
167
+
168
+ # css
169
+ system 'sass --compass -C doc/style.sass gh-pages/style.css'
170
+ end
171
+
172
+ desc "preview gh-pages in default browser"
173
+ task :preview => :site do
174
+ url = File.expand_path "gh-pages/syntax.html"
175
+ activate_url "file://#{url}"
176
+ end