mint 0.2.9 → 0.5.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.
- data/README.md +45 -283
- data/bin/mint +10 -10
- data/bin/mint-epub +23 -0
- data/features/plugins/epub.feature +23 -0
- data/features/publish.feature +73 -0
- data/features/support/env.rb +1 -1
- data/lib/mint.rb +1 -0
- data/lib/mint/commandline.rb +3 -3
- data/lib/mint/document.rb +46 -5
- data/lib/mint/helpers.rb +65 -4
- data/lib/mint/mint.rb +6 -7
- data/lib/mint/plugin.rb +136 -0
- data/lib/mint/plugins/epub.rb +292 -0
- data/lib/mint/version.rb +1 -1
- data/plugins/templates/epub/container.haml +5 -0
- data/plugins/templates/epub/content.haml +36 -0
- data/plugins/templates/epub/layout.haml +6 -0
- data/plugins/templates/epub/title.haml +11 -0
- data/plugins/templates/epub/toc.haml +26 -0
- data/spec/commandline_spec.rb +91 -0
- data/spec/document_spec.rb +48 -9
- data/spec/helpers_spec.rb +231 -0
- data/spec/layout_spec.rb +6 -0
- data/spec/mint_spec.rb +94 -0
- data/spec/plugin_spec.rb +457 -0
- data/spec/plugins/epub_spec.rb +242 -0
- data/spec/resource_spec.rb +135 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/style_spec.rb +69 -0
- metadata +103 -34
- data/features/mint_document.feature +0 -48
- data/features/step_definitions/mint_steps.rb +0 -1
@@ -0,0 +1,73 @@
|
|
1
|
+
Feature: Publish document with varying options at the command line
|
2
|
+
As a writer
|
3
|
+
I want to create a document at the command line from plain text
|
4
|
+
So that I can view a typeset version in a web browser
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given a file named "content.md" with:
|
8
|
+
"""
|
9
|
+
Header
|
10
|
+
======
|
11
|
+
|
12
|
+
This is a test. It is theoretically formatted in
|
13
|
+
the Markdown templating language.
|
14
|
+
"""
|
15
|
+
|
16
|
+
And a file named "style.sass" with:
|
17
|
+
"""
|
18
|
+
p
|
19
|
+
margin: 0
|
20
|
+
"""
|
21
|
+
|
22
|
+
And a file named "layout.haml" with:
|
23
|
+
"""
|
24
|
+
%html
|
25
|
+
%head
|
26
|
+
%link(rel='stylesheet' href=stylesheet)
|
27
|
+
|
28
|
+
%body
|
29
|
+
#container= content
|
30
|
+
"""
|
31
|
+
|
32
|
+
Scenario: Publish document with defaults
|
33
|
+
When I run `mint publish content.md`
|
34
|
+
Then a file named "content.md" should exist
|
35
|
+
And a file named "content.html" should exist
|
36
|
+
|
37
|
+
Scenario Outline: Publish document with named template, layout & style
|
38
|
+
When I run `mint publish <template> <layout> <style> content.md`
|
39
|
+
Then a file named "content.html" should exist
|
40
|
+
And the file "content.html" should contain "This is a test"
|
41
|
+
And a file named "<style file>" should exist
|
42
|
+
And the file "content.html" should match /templates.*style.css/
|
43
|
+
And the file "content.html" should contain "<style file>"
|
44
|
+
|
45
|
+
Examples:
|
46
|
+
| template | layout | style | style file |
|
47
|
+
| | | | ../../templates/default/css/style.css |
|
48
|
+
| -t pro | | | ../../templates/pro/css/style.css |
|
49
|
+
| | -l pro | -s pro | ../../templates/pro/css/style.css |
|
50
|
+
|
51
|
+
Scenario: Publish document with non-existent template
|
52
|
+
When I run `mint publish -t nonexistent content.md`
|
53
|
+
Then the stderr should contain "Template 'nonexistent' does not exist."
|
54
|
+
|
55
|
+
Scenario: Publish document in directory
|
56
|
+
When I run `mint publish content.md -d compiled`
|
57
|
+
Then a file named "compiled/content.html" should exist
|
58
|
+
|
59
|
+
Scenario: Publish document in subdirectory
|
60
|
+
When I run `mint publish content.md -d compiled/chapter-1`
|
61
|
+
Then a file named "compiled/chapter-1/content.html" should exist
|
62
|
+
|
63
|
+
Scenario: Publish document with default style and explicit style destination
|
64
|
+
When I run `mint publish content.md -n styles`
|
65
|
+
Then a file named "styles/style.css" should exist
|
66
|
+
|
67
|
+
Scenario: Publish document with hand-crafted style and explicit style destination
|
68
|
+
When I run `mint publish content.md -n styles -s style.sass`
|
69
|
+
Then a file named "styles/style.css" should exist
|
70
|
+
|
71
|
+
Scenario: Publish document with hand-crafted layout
|
72
|
+
When I run `mint publish content.md -l layout.haml`
|
73
|
+
Then the file "content.html" should match /id=['"]container['"]/
|
data/features/support/env.rb
CHANGED
data/lib/mint.rb
CHANGED
data/lib/mint/commandline.rb
CHANGED
@@ -144,7 +144,7 @@ module Mint
|
|
144
144
|
config_directory = Mint.path_for_scope(scope, true)
|
145
145
|
config_file = config_directory + Mint.files[:config]
|
146
146
|
Helpers.ensure_directory config_directory
|
147
|
-
Helpers.update_yaml opts, config_file
|
147
|
+
Helpers.update_yaml! opts, config_file
|
148
148
|
end
|
149
149
|
|
150
150
|
# Tries to set a config option (at the specified scope) per
|
@@ -184,7 +184,7 @@ module Mint
|
|
184
184
|
# @param [Hash, #[]] commandline_options a structured set of configuration options
|
185
185
|
# that will guide Mint.publish!
|
186
186
|
# @return [void]
|
187
|
-
def self.
|
187
|
+
def self.publish!(files, commandline_options)
|
188
188
|
documents = []
|
189
189
|
options = configuration_with commandline_options
|
190
190
|
|
@@ -194,7 +194,7 @@ module Mint
|
|
194
194
|
# change detection
|
195
195
|
render_style = true
|
196
196
|
files.each do |file|
|
197
|
-
Document.new(file, options).publish!
|
197
|
+
Document.new(file, options).publish! :render_style => render_style
|
198
198
|
render_style = false
|
199
199
|
end
|
200
200
|
end
|
data/lib/mint/document.rb
CHANGED
@@ -4,9 +4,12 @@ require 'mint/style'
|
|
4
4
|
|
5
5
|
module Mint
|
6
6
|
class Document < Resource
|
7
|
+
METADATA_DELIM = "\n\n"
|
8
|
+
|
7
9
|
# Implicit readers are paired with explicit accessors. This
|
8
10
|
# allows for processing variables before storing them.
|
9
11
|
attr_reader :content, :layout, :style
|
12
|
+
attr_accessor :metadata
|
10
13
|
|
11
14
|
# Passes content through a renderer before assigning it to be
|
12
15
|
# the Document's content
|
@@ -15,7 +18,16 @@ module Mint
|
|
15
18
|
# from a templating language into HTML
|
16
19
|
# @return [void]
|
17
20
|
def content=(content)
|
18
|
-
|
21
|
+
tempfile = Helpers.generate_temp_file! content
|
22
|
+
original_content = File.read content
|
23
|
+
|
24
|
+
metadata, text = Document.parse_metadata_from original_content
|
25
|
+
self.metadata = metadata
|
26
|
+
intermediate_content = Mint.before_render text
|
27
|
+
|
28
|
+
File.open(tempfile, 'w') {|file| file << intermediate_content }
|
29
|
+
|
30
|
+
@renderer = Mint.renderer tempfile
|
19
31
|
@content = @renderer.render
|
20
32
|
end
|
21
33
|
|
@@ -71,7 +83,7 @@ module Mint
|
|
71
83
|
# The style_destination attribute is lazy. It's exposed via
|
72
84
|
# virtual attributes like #style_destination_file.
|
73
85
|
attr_reader :style_destination
|
74
|
-
|
86
|
+
|
75
87
|
# @param [String] style_destination the subdirectory into
|
76
88
|
# which styles will be rendered or copied
|
77
89
|
# @return [void]
|
@@ -153,12 +165,14 @@ module Mint
|
|
153
165
|
|
154
166
|
# Renders content in the context of layout and returns as a String.
|
155
167
|
def render(args={})
|
156
|
-
layout.render self, args
|
168
|
+
intermediate_content = layout.render self, args
|
169
|
+
Mint.after_render(intermediate_content)
|
157
170
|
end
|
158
171
|
|
159
172
|
# Writes all rendered content where a) possible, b) required,
|
160
173
|
# and c) specified. Outputs to specified file.
|
161
|
-
def publish!(
|
174
|
+
def publish!(opts={})
|
175
|
+
options = { :render_style => true }.merge(opts)
|
162
176
|
FileUtils.mkdir_p self.destination_directory
|
163
177
|
File.open(self.destination_file, 'w+') do |f|
|
164
178
|
f << self.render
|
@@ -168,12 +182,14 @@ module Mint
|
|
168
182
|
# b) it actually needs rendering (i.e., it's in template form and
|
169
183
|
# not raw, browser-parseable CSS) or it if it doesn't need
|
170
184
|
# rendering but there is an explicit style_destination.
|
171
|
-
if render_style
|
185
|
+
if options[:render_style]
|
172
186
|
FileUtils.mkdir_p style_destination_directory
|
173
187
|
File.open(self.style_destination_file, 'w+') do |f|
|
174
188
|
f << self.style.render
|
175
189
|
end
|
176
190
|
end
|
191
|
+
|
192
|
+
Mint.after_publish(self, opts)
|
177
193
|
end
|
178
194
|
|
179
195
|
# Convenience methods for views
|
@@ -184,5 +200,30 @@ module Mint
|
|
184
200
|
Helpers.normalize_path(self.style_destination_file,
|
185
201
|
self.destination_directory).to_s
|
186
202
|
end
|
203
|
+
|
204
|
+
protected
|
205
|
+
|
206
|
+
def self.metadata_chunk(text)
|
207
|
+
text.split(METADATA_DELIM).first
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.metadata_from(text)
|
211
|
+
raw_metadata = YAML.load metadata_chunk(text)
|
212
|
+
raw_metadata.is_a?(String) ? {} : raw_metadata
|
213
|
+
rescue
|
214
|
+
{}
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.parse_metadata_from(text)
|
218
|
+
metadata = metadata_from text
|
219
|
+
new_text =
|
220
|
+
if !metadata.empty?
|
221
|
+
text.sub metadata_chunk(text) + METADATA_DELIM, ''
|
222
|
+
else
|
223
|
+
text
|
224
|
+
end
|
225
|
+
|
226
|
+
[metadata, new_text]
|
227
|
+
end
|
187
228
|
end
|
188
229
|
end
|
data/lib/mint/helpers.rb
CHANGED
@@ -1,8 +1,23 @@
|
|
1
1
|
require 'pathname'
|
2
|
+
require 'tempfile'
|
2
3
|
require 'yaml'
|
4
|
+
require 'active_support/core_ext/string/inflections'
|
3
5
|
|
4
6
|
module Mint
|
5
7
|
module Helpers
|
8
|
+
def self.underscore(obj, opts={})
|
9
|
+
namespaces = obj.to_s.split('::').map do |namespace|
|
10
|
+
if opts[:ignore_prefix]
|
11
|
+
namespace[0..1].downcase + namespace[2..-1]
|
12
|
+
else
|
13
|
+
namespace
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
string = opts[:namespaces] ? namespaces.join('::') : namespaces.last
|
18
|
+
string.underscore
|
19
|
+
end
|
20
|
+
|
6
21
|
# Transforms a String into a URL-ready slug. Properly handles
|
7
22
|
# ampersands, non-alphanumeric characters, extra hyphens and spaces.
|
8
23
|
#
|
@@ -41,12 +56,14 @@ module Mint
|
|
41
56
|
#
|
42
57
|
# @param [Hash, #[]] map a potentially nested Hash containing symbolizable keys
|
43
58
|
# @return [Hash] a version of map where all keys are symbols
|
44
|
-
def self.symbolize_keys(map)
|
59
|
+
def self.symbolize_keys(map, opts={})
|
60
|
+
transform = lambda {|x| opts[:downcase] ? x.downcase : x }
|
61
|
+
|
45
62
|
map.reduce(Hash.new) do |syms,(k,v)|
|
46
|
-
syms[k.to_sym] =
|
63
|
+
syms[transform[k].to_sym] =
|
47
64
|
case v
|
48
65
|
when Hash
|
49
|
-
self.symbolize_keys(v)
|
66
|
+
self.symbolize_keys(v, opts)
|
50
67
|
else
|
51
68
|
v
|
52
69
|
end
|
@@ -54,6 +71,38 @@ module Mint
|
|
54
71
|
end
|
55
72
|
end
|
56
73
|
|
74
|
+
def self.listify(list)
|
75
|
+
if list.length > 2
|
76
|
+
list[0..-2].join(', ') + ' & ' + list.last
|
77
|
+
else
|
78
|
+
list.join(' & ')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.standardize(metadata, opts={})
|
83
|
+
table = opts[:table] || {}
|
84
|
+
metadata.reduce({}) do |hash, (key,value)|
|
85
|
+
if table[key] && table[key].length == 2
|
86
|
+
standard_key, standard_type = table[key]
|
87
|
+
standard_value =
|
88
|
+
case standard_type
|
89
|
+
when :array
|
90
|
+
[*value]
|
91
|
+
when :string
|
92
|
+
value
|
93
|
+
else
|
94
|
+
# If key/type were not in table
|
95
|
+
value
|
96
|
+
end
|
97
|
+
|
98
|
+
hash[standard_key] = standard_value
|
99
|
+
else
|
100
|
+
hash[key] = value
|
101
|
+
end
|
102
|
+
hash
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
57
106
|
# Returns the relative path to to_directory from from_directory.
|
58
107
|
# If to_directory and from_directory have no parents in common besides
|
59
108
|
# /, returns the absolute directory of to_directory. Assumes no symlinks.
|
@@ -77,12 +126,24 @@ module Mint
|
|
77
126
|
# @param [Hash, #[]] new_opts a set of options to add to the Yaml file
|
78
127
|
# @param [Pathname, #exist] file a file to read from and write to
|
79
128
|
# @return [void]
|
80
|
-
def self.update_yaml(new_opts, file)
|
129
|
+
def self.update_yaml!(new_opts, file)
|
81
130
|
curr_opts = file.exist? ? YAML.load_file(file) : {}
|
82
131
|
|
83
132
|
File.open file, 'w' do |f|
|
84
133
|
YAML.dump(curr_opts.merge(new_opts), f)
|
85
134
|
end
|
86
135
|
end
|
136
|
+
|
137
|
+
def self.generate_temp_file!(file)
|
138
|
+
basename = File.basename file
|
139
|
+
extension = File.extname file
|
140
|
+
content = File.read file
|
141
|
+
|
142
|
+
tempfile = Tempfile.new([basename, extension])
|
143
|
+
tempfile << content
|
144
|
+
tempfile.flush
|
145
|
+
tempfile.close
|
146
|
+
tempfile.path
|
147
|
+
end
|
87
148
|
end
|
88
149
|
end
|
data/lib/mint/mint.rb
CHANGED
@@ -141,11 +141,10 @@ module Mint
|
|
141
141
|
find_files = lambda {|x| Pathname.glob "#{x.to_s}.*" }
|
142
142
|
acceptable = lambda {|x| x.to_s =~ /#{Mint.formats.join '|'}/ }
|
143
143
|
|
144
|
-
|
145
|
-
select(&acceptable).select(&:exist?).first.
|
146
|
-
|
147
|
-
|
148
|
-
template
|
144
|
+
Mint.path(true).map(&file_name).map(&find_files).flatten.
|
145
|
+
select(&acceptable).select(&:exist?).first.tap do |template|
|
146
|
+
raise TemplateNotFoundException unless template
|
147
|
+
end.to_s
|
149
148
|
end
|
150
149
|
|
151
150
|
# Checks (non-rigorously) to see if the file is somewhere on the
|
@@ -185,7 +184,7 @@ module Mint
|
|
185
184
|
#
|
186
185
|
# @param [Document] document a Mint document
|
187
186
|
# @return [void]
|
188
|
-
def self.publish!(document)
|
189
|
-
document.publish!
|
187
|
+
def self.publish!(document, opts={})
|
188
|
+
document.publish! opts
|
190
189
|
end
|
191
190
|
end
|
data/lib/mint/plugin.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'mint/document'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Mint
|
5
|
+
def self.plugins
|
6
|
+
@@plugins ||= Set.new
|
7
|
+
@@plugins.to_a
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.activated_plugins
|
11
|
+
@@activated_plugins ||= Set.new
|
12
|
+
@@activated_plugins.to_a
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.register_plugin!(plugin)
|
16
|
+
@@plugins ||= Set.new
|
17
|
+
@@plugins << plugin
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.activate_plugin!(plugin)
|
21
|
+
@@activated_plugins ||= Set.new
|
22
|
+
@@activated_plugins << plugin
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.clear_plugins!
|
26
|
+
defined?(@@plugins) && @@plugins.clear
|
27
|
+
defined?(@@activated_plugins) && @@activated_plugins.clear
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.template_directory(plugin)
|
31
|
+
Mint.root + '/plugins/templates/' + plugin.underscore
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.config_directory(plugin)
|
35
|
+
Mint.root + '/plugins/config/' + plugin.underscore
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.commandline_options_file(plugin)
|
39
|
+
plugin.config_directory + '/syntax.yml'
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.commandline_name(plugin)
|
43
|
+
plugin.underscore
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.before_render(plain_text, opts={})
|
47
|
+
active_plugins = opts[:plugins] || Mint.activated_plugins
|
48
|
+
active_plugins.reduce(plain_text) do |intermediate, plugin|
|
49
|
+
plugin.before_render(intermediate)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.after_render(html_text, opts={})
|
54
|
+
active_plugins = opts[:plugins] || Mint.activated_plugins
|
55
|
+
active_plugins.reduce(html_text) do |intermediate, plugin|
|
56
|
+
plugin.after_render(intermediate)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.after_publish(document, opts={})
|
61
|
+
active_plugins = opts[:plugins] || Mint.activated_plugins
|
62
|
+
active_plugins.each do |plugin|
|
63
|
+
plugin.after_publish(document)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Plugin
|
68
|
+
def self.inherited(plugin)
|
69
|
+
Mint.register_plugin! plugin
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.underscore(opts={})
|
73
|
+
opts[:ignore_prefix] ||= true
|
74
|
+
Helpers.underscore self.name, :ignore_prefix => opts[:ignore_prefix]
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.template_directory
|
78
|
+
Mint.template_directory(self)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.config_directory
|
82
|
+
Mint.config_directory(self)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.commandline_options_file
|
86
|
+
Mint.commandline_options_file(self)
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.commandline_name
|
90
|
+
Mint.commandline_name(self)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Supports:
|
94
|
+
# - Change raw text
|
95
|
+
#
|
96
|
+
# Use cases:
|
97
|
+
# - Add footnote syntax on top of Markdown
|
98
|
+
# - Perform text analysis for use in later callbacks (?)
|
99
|
+
def self.before_render(text_document)
|
100
|
+
text_document
|
101
|
+
end
|
102
|
+
|
103
|
+
# Supports:
|
104
|
+
# - Change preview HTML
|
105
|
+
#
|
106
|
+
# Use cases:
|
107
|
+
# - Transform elements based on position or other HTML attributes
|
108
|
+
# For example: Add a class to the first paragraph of a document if it is
|
109
|
+
# italicized
|
110
|
+
#
|
111
|
+
# Questions:
|
112
|
+
# - Could I allow jQuery use here?
|
113
|
+
def self.after_render(html_document)
|
114
|
+
html_document
|
115
|
+
end
|
116
|
+
|
117
|
+
# Supports:
|
118
|
+
# - Change file, filesystem once written
|
119
|
+
# - Automatic cleanup of intermediate files, including all edge cases
|
120
|
+
# currently covered by transformation library. (For example, if I generated
|
121
|
+
# a CSS file but am ultimately generating a PDF, I would want to have
|
122
|
+
# an automatic way to delete that CSS file.)
|
123
|
+
#
|
124
|
+
# Use cases:
|
125
|
+
# - Zip set of documents into ePub and create manifest
|
126
|
+
# - Change file extension
|
127
|
+
# - Generate PDF from emitted HTML and get rid of intermediate files
|
128
|
+
# - Generate .doc (or any other OO UNO format) and get rid of intermediate files
|
129
|
+
#
|
130
|
+
# NOTE: Unlike the other two callbacks, this doesn't use the result of
|
131
|
+
# the callback expression for anything. This callback is purely for
|
132
|
+
# side effects like rearranging the file system.
|
133
|
+
def self.after_publish(document)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|