markascend 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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