mdprev 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/mdprev +15 -0
  2. data/mdprev.rb +185 -0
  3. data/mdprev_test.rb +58 -0
  4. metadata +98 -0
data/mdprev ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require File.expand_path('mdprev', File.dirname(__FILE__))
4
+
5
+ ARGV.options do |o|
6
+ flags = {}
7
+ o.set_summary_indent(' ')
8
+ o.banner = "Usage: #{File.basename($0)} [markdown-files,] [OPTIONS]"
9
+ o.define_head 'Styled preview of markdown files in browser'
10
+ o.on('-p', '--pdf', 'view as PDF') { flags[:pdf] = true }
11
+ o.on('-n', '--nav', 'include navigation') { flags[:nav] = true }
12
+ o.on('-h', '--help', 'show this help message') { puts o; exit }
13
+ o.parse!
14
+ ARGV.empty? ? puts(o) : MarkdownPreview.run(ARGV, flags)
15
+ end
data/mdprev.rb ADDED
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'kramdown'
5
+
6
+ module MarkdownPreview
7
+ VERSION = '1.0.0'
8
+ OPEN_HTML = ENV['MDPREV_OPEN_HTML'] || 'open'
9
+ OPEN_PDF = ENV['MDPREV_OPEN_PDF'] || 'open'
10
+ PREVIEW_HTML = "#{ENV['HOME']}/.preview.html"
11
+ PREVIEW_PDF = "#{ENV['HOME']}/.preview.pdf"
12
+
13
+ def self.run(fnames, flags = {})
14
+ html = File.open(PREVIEW_HTML, 'w')
15
+ html.write(to_html(
16
+ fnames.map {|f| File.read(f)}.join("\n"),
17
+ title_from_files(fnames),
18
+ flags))
19
+ html.close
20
+
21
+ if flags[:pdf]
22
+ `wkhtmltopdf #{PREVIEW_HTML} #{PREVIEW_PDF} && #{OPEN_PDF} #{PREVIEW_PDF}`
23
+ else
24
+ `#{OPEN_HTML} #{PREVIEW_HTML}`
25
+ end
26
+ rescue StandardError => e
27
+ puts e.message
28
+ ensure
29
+ html.close if html && !html.closed?
30
+ end
31
+
32
+ def self.to_html(str, title = 'Preview', flags = {})
33
+ body, anchors = build_body_and_anchors(str)
34
+ TEMPLATE.
35
+ sub('$nav$', flags[:nav] ? build_nav(anchors) : '').
36
+ sub('$body$', body).
37
+ sub('$class$', body_class(flags)).
38
+ sub('$title$', title).
39
+ gsub('<div class="section"></div>', '')
40
+ end
41
+
42
+ def self.build_body_and_anchors(str)
43
+ html = Kramdown::Document.new(str, :auto_ids => false).to_html
44
+ anchors = []
45
+ html.gsub!('<h1>', '</div><div class="section"><h1>')
46
+ html.scan(/<h1>[^<]+<\/h1>/).each do |match|
47
+ text = match.gsub(/<\/?h1>/, '')
48
+ anchor = text.
49
+ gsub(/[^a-zA-Z0-9\- ]/, '').
50
+ gsub(/\s+/, '-').
51
+ downcase
52
+ anchors.push([anchor, text])
53
+ html.sub!(match,
54
+ "<a class=\"fragment-anchor\" name=\"#{anchor}\"></a>#{match}")
55
+ end
56
+ [html, anchors]
57
+ end
58
+
59
+ def self.title_from_files(fnames)
60
+ File.basename(fnames.first).
61
+ gsub(/#{File.extname(fnames.first)}$/, '').
62
+ gsub(/[^a-zA-Z0-9\-_ ]/, '').
63
+ gsub(/[\-_]/, ' ').
64
+ split(/\s+/).
65
+ map {|s| s.capitalize}.
66
+ join(' ')
67
+ end
68
+
69
+ def self.body_class(flags)
70
+ if flags[:pdf]
71
+ 'pdf-layout'
72
+ elsif flags[:nav]
73
+ 'nav-layout'
74
+ else
75
+ ''
76
+ end
77
+ end
78
+
79
+ def self.build_nav(anchors)
80
+ html = ['<ul id="main-nav">']
81
+ anchors.each do |anchor, text|
82
+ html << "<li><a href=\"##{anchor}\">#{text}</a></li>"
83
+ end
84
+ html << '</ul>'
85
+ html.join('')
86
+ end
87
+ end
88
+
89
+ MarkdownPreview::TEMPLATE = <<END
90
+ <!doctype html>
91
+ <head>
92
+ <title>$title$</title>
93
+ <style>
94
+ html, body, div, span, object, iframe,h1, h2, h3, h4, h5, h6, p, blockquote,
95
+ pre,a, abbr, acronym, address, big, cite, code,del, dfn, em, img, ins, kbd, q,
96
+ s, samp,small, strike, strong, sub, sup, tt, var,dl, dt, dd, ol, ul, li,
97
+ fieldset, form, label, legend,table, caption, tbody, tfoot, thead, tr, th, td {
98
+ margin: 0; padding: 0; border: 0; outline: 0; font-weight: inherit;
99
+ font-style: inherit;
100
+ vertical-align: baseline;
101
+ }
102
+ body { text-align: center; font-size: 11.5px; background: #e0e0e0;
103
+ line-height: 1.7em; color: #333; width: 100%; height: 100%;
104
+ font-family: lucida grande, helvetica, sans-serif; }
105
+ h1,h2,h3,h4,h5,h6 { font-weight: bold; font-family: Copperplate / Copperplate Gothic Light, sans-serif; }
106
+ h1 { margin: 36px 0 24px; color: #111; border-bottom: 1px dashed #aaa;
107
+ padding-bottom: 6px; font-size: 2.2em; }
108
+ h1 + p, h1 + ol, h1 + ul { margin-top: -12px; }
109
+ h2, h3, h4, h5, h6 { margin: 24px 0; color: #111; }
110
+ h2 + p, h2 + ol, h2 + ul,
111
+ h3 + p, h3 + ol, h3 + ul,
112
+ h4 + p, h4 + ol, h4 + ul,
113
+ h5 + p, h5 + ol, h5 + ul,
114
+ h6 + p, h6 + ol, h6 + ul { margin-top: -12px; }
115
+ h2 { font-size: 1.8em; }
116
+ h3 { font-size: 1.6em; }
117
+ h3 { font-size: 1.4em; }
118
+ h4 { font-size: 1.2em; }
119
+ h5 { font-size: 1.1em; }
120
+ h6 { font-size: 1em; }
121
+ a { color: #a37142; text-decoration: none; }
122
+ a:hover { color: #234f32; }
123
+ .fragment-anchor + h1, h1:first-child { margin-top: 0; }
124
+ select, input, textarea { font: 99% lucida grande, helvetica, sans-serif; }
125
+ pre, code { font-family: Lucida Console, Monaco, monospace; }
126
+ ol { list-style: decimal; }
127
+ ul { list-style: disc; }
128
+ ol, ul { margin: 24px 0 24px 1.7em; }
129
+ p + ol, p + ul { margin-top: -16px; }
130
+ ol:last-child, ul:last-child { margin-bottom: 0; }
131
+ table { border-collapse: collapse; border-spacing: 0; }
132
+ caption, th, td { text-align: left; font-weight: normal; }
133
+ blockquote:before, blockquote:after,q:before, q:after { content: ""; }
134
+ blockquote, q { quotes: "" ""; }
135
+ em { font-style: italic; }
136
+ strong { font-weight: bold; }
137
+ p { margin: 24px 0; }
138
+ p:first-child { margin-top: 0; }
139
+ p:last-child { margin-bottom: 0; }
140
+ #main { width: 500px; margin: 60px auto; text-align: left; position: relative;
141
+ left: 0; }
142
+ .section { padding: 36px; background: #fff; border: 1px solid #bcbcbc;
143
+ -webkit-box-shadow: 2px 2px 4px #ccc;
144
+ -moz-box-shadow: 2px 2px 4px #ccc; margin-bottom: 36px; }
145
+ .section pre { border-top: 1px solid #000; border-bottom: 1px solid #000;
146
+ color: #fff; background: #555; width: 100%; padding: 12px 36px;
147
+ position: relative; right: 36px; font-family: Monaco, monospace; }
148
+ .section pre code { font-weight: normal; }
149
+ .section code { font-family: Monaco, monospace; font-weight: bold; }
150
+ .section strong { border-bottom: 1px dashed #aaa; }
151
+
152
+ #main-nav { position: fixed; top: 40px; text-align: left; list-style: none;
153
+ text-align: right; margin-left: -170px; width: 240px; }
154
+ #main-nav li { margin-bottom: 4px; }
155
+ .nav-layout #main { left: 100px; }
156
+
157
+ body.pdf-layout { background: #fff; line-height: 1.4em; font-size: 10px; }
158
+ .pdf-layout #main { width: 420px; }
159
+ .pdf-layout h1, .pdf-layout h2, .pdf-layout h3, .pdf-layout h4,
160
+ .pdf-layout h5, .pdf-layout h6 { margin: 12px 0 -6px; color: #000; }
161
+ .pdf-layout h1 { font-size: 1.4em; border-bottom: none; }
162
+ .pdf-layout h2 { font-size: 1.3em; }
163
+ .pdf-layout h3 { font-size: 1.2em; }
164
+ .pdf-layout h4 { font-size: 1.1em; }
165
+ .pdf-layout h5 { font-size: 1em; }
166
+ .pdf-layout .section { border: none; margin-bottom: 0; padding: 12px 0 0 0;
167
+ -webkit-box-shadow: none; -moz-box-shadow: none; }
168
+ .pdf-layout .section:first-child { padding-top: 0; }
169
+ .pdf-layout .section pre { position: static; padding: 6px 12px; width: 396px;
170
+ background: #e0e0e0; color: #000; font-size: 0.95em; border: none; }
171
+ .pdf-layout .section pre:last-child { margin-bottom: 24px; }
172
+ .pdf-layout .section code { font-weight: normal; }
173
+ .pdf-layout p { text-align: justify; }
174
+ .pdf-layout p, .pdf-layout ol, .pdf-layout ul { margin-top: 16px;
175
+ margin-bottom: 16px; }
176
+ </style>
177
+ </head>
178
+ <body class="$class$">
179
+ <div id="main">
180
+ <div class="section">$body$</div>
181
+ $nav$
182
+ </div>
183
+ </body>
184
+ </html>
185
+ END
data/mdprev_test.rb ADDED
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'mdprev'
3
+ require 'minitest/autorun'
4
+
5
+ class MarkdownPreviewTest < MiniTest::Unit::TestCase
6
+ def test_build_anchors
7
+ body, anchors = MarkdownPreview.build_body_and_anchors("
8
+ # Header !@%^_ 1\n
9
+ Paragraph\n
10
+ # Header 2\n
11
+ Paragraph 2\n
12
+ ".gsub(/^ +/, ''))
13
+ assert_equal(
14
+ [['header-1', 'Header !@%^_ 1'], ['header-2', 'Header 2']],
15
+ anchors)
16
+ end
17
+
18
+ def test_build_body
19
+ body, anchors = MarkdownPreview.build_body_and_anchors("
20
+ # Header
21
+ Sample Text
22
+ ".gsub(/^\s+/, ''))
23
+ assert_match(/<h1>Header<\/h1>/, body)
24
+ assert_match(/<p>Sample Text<\/p>/, body)
25
+ end
26
+
27
+ def test_title
28
+ assert_equal('Sample', MarkdownPreview.title_from_files(['sample.md']))
29
+ assert_equal(
30
+ 'League Of Extraordinary Gentlemen',
31
+ MarkdownPreview.title_from_files([
32
+ 'league-of-extraordinary-_^Gentlemen',
33
+ 'ignoreothernames'
34
+ ]))
35
+ end
36
+
37
+ def test_build_nav
38
+ nav = MarkdownPreview.build_nav([
39
+ ['first-id', 'First ID'],
40
+ ['second-item', 'Second Item'],
41
+ ['third', 'Third'],
42
+ ['fourth', 'Fourth'],
43
+ ['fifth', 'Fifth']
44
+ ])
45
+ assert_match(/^<ul id="main-nav">/, nav)
46
+ assert_match(/<li><a href="#first-id">First ID<\/a><\/li>/, nav)
47
+ assert_match(/<li><a href="#second-item">Second Item<\/a><\/li>/, nav)
48
+ assert_match(/<li><a href="#third">Third<\/a><\/li>/, nav)
49
+ assert_match(/<li><a href="#fourth">Fourth<\/a><\/li>/, nav)
50
+ assert_match(/<\/ul>$/, nav)
51
+ end
52
+
53
+ def test_body_class
54
+ assert_equal('', MarkdownPreview.body_class(:pdf => false, :nav => false))
55
+ assert_equal('pdf-layout', MarkdownPreview.body_class(:pdf => true))
56
+ assert_equal('nav-layout', MarkdownPreview.body_class(:nav => true))
57
+ end
58
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mdprev
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Hugh Bien
14
+ autorequire:
15
+ bindir: .
16
+ cert_chain: []
17
+
18
+ date: 2011-10-06 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: kramdown
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: minitest
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ description: Run files through markdown processor and open in a browser. Adds a default theme. Works with wkhtmltopdf.
49
+ email:
50
+ - hugh@hughbien.com
51
+ executables:
52
+ - mdprev
53
+ extensions: []
54
+
55
+ extra_rdoc_files: []
56
+
57
+ files:
58
+ - mdprev.rb
59
+ - mdprev_test.rb
60
+ - mdprev
61
+ - ./mdprev
62
+ homepage: https://github.com/hughbien/mdprev
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options: []
67
+
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 23
85
+ segments:
86
+ - 1
87
+ - 3
88
+ - 6
89
+ version: 1.3.6
90
+ requirements: []
91
+
92
+ rubyforge_project:
93
+ rubygems_version: 1.8.11
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: Styled preview of markdown files in browser
97
+ test_files: []
98
+