mdprev 1.0.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.
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
+