mint 0.1.1 → 0.1.3

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/bin/mint CHANGED
@@ -1,80 +1,164 @@
1
1
  #!/usr/bin/env ruby
2
2
  # A script for harnessing Mint at the commandline
3
3
 
4
- $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require 'pathname'
5
+
6
+ $:.unshift Pathname.new(__FILE__).realpath.dirname + '..' + 'lib'
5
7
 
6
- require 'rubygems'
7
- require 'mint'
8
8
  require 'optparse'
9
+ require 'yaml'
10
+ require 'mint'
9
11
 
10
- def usage
11
- puts "You're doing it wrong."
12
- end
12
+ module Mint
13
+ # A map of all options that OptParse will accept from the commandline.
14
+ # All other arguments are taken to be filenames.
15
+ def self.options
16
+ {
17
+ # These options take parameters
18
+ template: ['t', 'template', true, 'Specify the template (layout + style).'],
19
+ layout: ['l', 'layout', true, 'Specify only the layout.'],
20
+ style: ['s', 'style', true, 'Specify only the style.'],
21
+ root: ['w', 'root', true, 'Specify a root outside the current directory.'],
22
+ destination: ['d', 'destination', true, 'Specify a destination directory, relative to the root.'],
23
+ style_destination: ['n', 'style-destination', true, 'Specify a destination directory for stylesheets, relative to the document destination directory. If not explicit, will be linked from original location.'],
13
24
 
14
- unless ARGV[0]
15
- usage
16
- return
17
- end
25
+ # These options do not take parameters
26
+ global: ['G', 'global', false, 'Specify config changes on a global level.'],
27
+ user: ['U', 'user', false, 'Specify config changes on a user-wide level.'],
28
+ local: ['L', 'local', false, 'Specify config changes on a project-specific level.'],
29
+ force: ['f', 'force', false, 'Force file overwrite without prompt.'],
30
+ verbose: ['v', 'verbose', false, 'Verbose output.'],
31
+ simulation: ['s', 'simulation', false, 'Simulate transformation without actually creating files.']
32
+ }
33
+ end
18
34
 
19
- include Mint
35
+ module CommandLine
36
+ def self.config_options
37
+ config_filename = Pathname.new(Mint.files[:config])
20
38
 
21
- module CommandLine
22
- module DocumentContext
23
- def mint_document(doc)
24
- document = Document.new doc, :template => 'normal',
25
- :style_destination => 'styles'
26
- document.mint
39
+ # Merge config options from all config files on the Mint path,
40
+ # where more local options take precedence over more global
41
+ # options
42
+ Mint.path.map {|p| p + config_filename }.
43
+ select(&:exist?).
44
+ map {|p| YAML.load_file p }.
45
+ reverse.
46
+ reduce({}) {|r,p| r.merge p }
47
+ end
48
+
49
+ def self.full_options_with(commandline_options)
50
+ config_options.merge commandline_options
51
+ end
52
+
53
+ def self.write_config(opts, scope=:local)
54
+ Helpers.ensure_directory Mint.path_for_scope(scope)
55
+ Helpers.update_yaml(opts, Mint.path_for_scope(scope) + Mint.files[:config])
56
+ end
57
+
58
+ def self.edit(name, layout_or_style=:layout)
59
+ file = Mint.lookup_template name, layout_or_style
60
+
61
+ editor = ENV['EDITOR'] || 'vim'
62
+ system "#{editor} #{file}"
63
+ end
64
+
65
+ def self.set(key, value, scope=:local)
66
+ write_config({ key => value }, scope)
67
+ end
68
+
69
+ def self.config
70
+ puts YAML.dump(config_options)
27
71
  end
28
- end
29
72
 
30
- module ProjectContext
31
- def init(dir, opts={})
32
- dir = Pathname.new dir
33
- if config = (dir + 'config.yaml').exists?
34
- proj = Project.new(dir, YAML.load_file(config))
35
- else
36
- proj = Project.new(dir)
73
+ # Renders and writes to file all resources described by a document.
74
+ # Specifically: it renders itself (inside of its own layout) and then
75
+ # renders its style. This method will overwrite any existing content
76
+ # in a document's destination files. The `render_style` option
77
+ # provides an easy way to stop Mint from rendering a style, even
78
+ # if the document's style is not nil.
79
+ def self.mint(files, commandline_options)
80
+ puts "minting #{files}"
81
+ documents = []
82
+ options = Mint::CommandLine.full_options_with commandline_options
83
+
84
+ root = options[:root] || Dir.getwd
85
+
86
+ # Eventually render_style should be replaced with file change detection
87
+ render_style = true
88
+ files.each do |file|
89
+ Document.new(file, options).mint(root, render_style)
90
+ render_style = false
37
91
  end
38
92
  end
39
93
  end
40
94
  end
41
95
 
42
- # --layout, -L
43
- # --style, -S
44
- # --root, -R
45
- # --destination, -D
46
- # --name, -N
47
- # --style-destination
48
- # --style-name
49
-
50
- options_with_parameters = {
51
- :template => ['T', 'template'],
52
- :layout => ['L', 'layout'],
53
- :style => ['S', 'style'],
54
- :root => ['R', 'root'],
55
- :destination => ['D', 'destination'],
56
- :name => ['N', 'name'],
57
- :style_destination => ['E', 'style-destination'],
58
- :style_name => ['A', 'style-name']
59
- }
60
-
61
- options_without_parameters = [ :verbose, :simulation ]
62
-
63
- config = {}
64
-
96
+ # Parse options from the commandline
97
+ commandline_options = {}
65
98
  optparse = OptionParser.new do |opts|
66
- opts.banner = 'Usage: mint source [destination] [options]'
67
-
68
- options_with_parameters.each do |k,v|
99
+ opts.banner = 'Usage: mint [command] files [options]'
100
+
101
+ Mint.options.each do |k,v|
102
+ has_param = v[2]
103
+
69
104
  v[0] = "-#{v[0]}"
70
- v[1] = "--#{v[1]} PARAM"
71
-
72
- opts.on v[0], v[1], 'Description goes here' do |p|
73
- config[k] = p
105
+ v[1] = "--#{v[1]}"
106
+
107
+ if has_param
108
+ v[1] << " PARAM"
109
+ opts.on v[0], v[1], v[3] do |p|
110
+ commandline_options[k] = p
111
+ end
112
+ else
113
+ opts.on v[0], v[1], v[3] do
114
+ commandline_options[k] = true
115
+ end
74
116
  end
75
117
  end
76
118
  end
119
+ optparse.parse!
77
120
 
78
- puts optparse.parse!
121
+ case ARGV.first.downcase.to_sym
79
122
 
80
- CommandLine::DocumentContext.mint_document ARGV[0], config
123
+ # If we get the edit command, will retrieve appropriate file
124
+ # (probably a Mint template) and shell out that file to
125
+ # the user's favorite editor. Otherwise ...
126
+ when :edit
127
+ layout = commandline_options[:layout]
128
+ style = commandline_options[:style]
129
+
130
+ if layout and not style
131
+ Mint::CommandLine.edit layout, :layout
132
+ elsif style
133
+ Mint::CommandLine.edit style, :style
134
+ else
135
+ puts optparse.help
136
+ end
137
+
138
+ # ... if we get the set command, we will try to set
139
+ # a config option (at the specified scope) per the user's command. Else ...
140
+ when :set
141
+ # Determine scope, using local as the default
142
+ commandline_options[:local] = true
143
+ scope = [:global, :user, :local].
144
+ select {|e| commandline_options[e] }.
145
+ first
146
+
147
+ Mint::CommandLine.set ARGV.shift, ARGV.shift, scope
148
+
149
+ # ... if we get the config command, display all active configurations,
150
+ # where local configurations override global ones
151
+ when :config
152
+ Mint::CommandLine.config
153
+
154
+ # ... we know we've been passed a set of documents, so parse the
155
+ # commandline options, merge them with the config options, and
156
+ # start minting the document list.
157
+ else
158
+ # The Mint library can't parse these options
159
+ commandline_options.delete :verbose
160
+ commandline_options.delete :simulation
161
+
162
+ puts "ARGV = #{ARGV}"
163
+ Mint::CommandLine.mint ARGV, commandline_options
164
+ end
data/lib/mint.rb CHANGED
@@ -1,433 +1,2 @@
1
- require 'pathname'
2
- require 'fileutils'
3
- require 'tilt'
4
- require 'haml'
5
- require 'rdiscount'
6
-
7
- require 'helpers'
8
-
9
- module Mint
10
- VERSION = '0.1'
11
-
12
- # Default paths where Mint looks for styles and layouts, in this order
13
- $path = [
14
- Pathname.new('.mint'), # 1. Project-defined
15
- Pathname.new(ENV['HOME']) + '.mint', # 2. User-defined
16
- Pathname.new('/usr') + 'share' + 'mint', # 3. System-defined
17
- Pathname.new(__FILE__).dirname + '..' # 4. Gemfile-defined
18
- ].collect! {|p| p.expand_path }
19
-
20
- # Directories within the Mint path
21
- $default_directories = {
22
- :templates => 'templates'
23
- }
24
-
25
- # Files within a Mint Project
26
- $default_files = {
27
- :config => 'config.yaml'
28
- }
29
-
30
- # Registered HTML and CSS formats, for source -> destination
31
- # name guessing/conversion
32
- $html_formats = ['.haml', '.erb', '.md']
33
- $css_formats = ['.sass', '.scss', '.less']
34
- $source_formats = $html_formats + $css_formats
35
-
36
- # Default options, where nil acts as described in the specification
37
- # Namely, a nil root or destination or style/layout template will
38
- # not be used, and a nil name will prompt Mint to guess an appropriate
39
- # name based on your source files
40
- $default_opts = {
41
- :template => 'default', # this is a new feature I need to test
42
- # :layout => 'default',
43
- # :style => 'default',
44
- :root => nil,
45
- :destination => nil,
46
- :style_destination => nil,
47
- :name => nil,
48
- :style_name => nil
49
- }
50
-
51
- class Resource
52
- attr_accessor :type, :source, :destination, :name
53
-
54
- def initialize(source, opts={}, &block)
55
- # A nil stylesheet or layout should not be rendered, so
56
- # we'll return nil here
57
- return nil unless source
58
-
59
- # If there is a source, we initialize it right away as a
60
- # Pathname, so we can use methods like @source.basename
61
- @source = Pathname.new source
62
-
63
- # Load in opts and change nil values to the assumptions
64
- # described in the Mint README.
65
- @opts = opts
66
-
67
- # If there is no destination value, ignore it
68
- @opts[:destination] ||= String.new
69
-
70
- # For a nil name, guess based on source
71
- @opts[:name] ||= Resource.guess_from(@source.basename.to_s)
72
-
73
- # Initialize resource values based on normalized, robust
74
- # opts. For raw resources (i.e., not for Documents), the destination
75
- # and source are both resolved relative to the current working
76
- # directory. This only really matters if you are initializing Layouts
77
- # or Styles on your own instead of via a Document. If you're using
78
- # a Document, all destination paths are resolved relative to the
79
- # Document root, which defaults to the content's source directory.
80
- @destination = Pathname.new @opts[:destination]
81
- @name = @opts[:name]
82
- @template = Resource.template @source
83
-
84
- # You can use a block to instantiate a Resource. Any values you set
85
- # in this block will override all other values.
86
- if block_given?
87
- yield self
88
- end
89
- end
90
-
91
- def equals?(other)
92
- destination + name == other.destination + other.name
93
- end
94
-
95
- alias_method :==, :equals?
96
-
97
- # A convenience method for supplying and selecting configuration
98
- # options. If supplied with an array of possible values, will return
99
- # the first non-nil value. If the first non-nil value responds to
100
- # `call`, will call that function and return its value.
101
- def config_with(config)
102
- (c = config.compact[0]).respond_to?(:call) ? c.call : c
103
- end
104
-
105
- # Renders the current resource to a string, returning that string
106
- # and not outputting to any file. Can operate within the context of
107
- # an Object, whose methods can be called from within the template.
108
- # Other arguments can be passed along to the template compiler. (For
109
- # example, to output HAML as HTML5, pass the option `:format =>
110
- # :html5`. See the Tilt TEMPLATES.md file for more information.)
111
- def render(context=Object.new, args={})
112
- @template.render context, args
113
- end
114
-
115
- # Guesses an appropriate name for the Resource output file based on
116
- # its source file's base name. Will convert registered HTML template
117
- # extensions to '.html' and CSS template extensions to '.css'.
118
- def self.guess_from(name)
119
- css, html = $css_formats.join('|'), $html_formats.join('|')
120
- name.gsub(/#{css}/, '.css').gsub(/#{html}/, '.html')
121
- end
122
-
123
- # Transforms a path into a template that will render the file specified
124
- # at that path. Currently uses Tilt, so any valid Tilt source file
125
- # is valid here.
126
- def self.template(path)
127
- Tilt.new path
128
- end
129
- end
130
-
131
- # Layout describes a resource whose type is `:layout`. Beyond its type,
132
- # it is a simple resource. However, its type helps decide which template
133
- # file to use when a template name is specified. A Layout is best managed
134
- # by a Document.
135
- class Layout < Resource
136
- def initialize(source, args={})
137
- @type = :layout
138
- super
139
- end
140
- end
141
-
142
- # Style describes a resource whose type is `:style`. Beyond its type,
143
- # it is a simple resource. However, its type helps decide which template
144
- # file to use when a template name is specified. A Style is best managed
145
- # by a Document.
146
- class Style < Resource
147
- def initialize(source, args={})
148
- @type = :style
149
- super
150
- end
151
- end
152
-
153
- # Document is a workhorse of the Mint library. It extends Resource
154
- # to include helpful variables like `root`, which will be the context
155
- # for all the Resources it contains. A Document's source is its content
156
- # file, from which it can derive a root directory, an output name, and
157
- # destination directories. A Document has one Style and one Layout, which
158
- # it initializes on creation. Default and optional Styles and Layouts are
159
- # included with Mint. You can add more templates to the Mint path
160
- # for even more choices.
161
- class Document < Resource
162
- attr_accessor :root, :layout, :style, :content
163
-
164
- def initialize(source, opts={}, &block)
165
- # Initialize opts to the default opts, replacing them with user-
166
- # supplied opts where possible.
167
- @opts = $default_opts.merge opts
168
-
169
- # Invoke the parent class initializer, which sets the source,
170
- # destination, name, template (content), and basic options.
171
- super(source, @opts)
172
-
173
- # Add in Doc-specific opts and change nil values to the
174
- # assumptions described in the Mint README.
175
- @opts[:root] ||= @source.dirname
176
-
177
- # If a template name is given, it overrides style and layout
178
- # names or files, per the Mint README.
179
- if templ = @opts[:template]
180
- @opts[:layout], @opts[:style] = templ, templ
181
- end
182
-
183
- @opts[:style_destination] ||= String.new
184
-
185
- style_opts = {
186
- :destination => @destination + @opts[:style_destination],
187
- :name => @opts[:style_name]
188
- }
189
-
190
- # Initialize resource values based on normalized, robust opts. For
191
- # Documents, the destination is resolved relative to the document
192
- # root. The root and source paths are both resolved relative to the
193
- # current working directory whenever this instantiation method
194
- # is called.
195
- @type = :document
196
- @root = Pathname.new(@opts[:root]).expand_path
197
-
198
- @content = Resource.template(@source).render
199
- @layout = choose_template(@opts[:layout], :layout)
200
- @style = choose_template(@opts[:style], :style, style_opts)
201
-
202
- # Be careful not to modify this code such that you can change the
203
- # current working directory before the source directory is normalized.
204
- # expand_path is always called relative to the *current* working
205
- # directory, not the initial one.
206
- @source = normalize_path(@source) # becomes relative here
207
-
208
- # You can use a block to instantiate a Document. Any values you set
209
- # in this block will override all other values.
210
- if block_given?
211
- yield self
212
- mint
213
- end
214
- end
215
-
216
- # Returns the relative path to dir1 from dir2, which defaults to the
217
- # Document root.
218
- def normalize_path(dir1, dir2=root)
219
- Document.normalize_path(dir1, dir2)
220
- end
221
-
222
- # Returns the relative path to dir1 from dir2.
223
- def self.normalize_path(dir1, dir2)
224
- path = case dir1
225
- when String
226
- Pathname.new dir1
227
- when Pathname
228
- dir1
229
- end
230
-
231
- path.expand_path.relative_path_from dir2
232
- end
233
-
234
- # Decides whether the template specified by `name_or_file` is a real
235
- # file or the name of a template. If it is a real file, Mint will
236
- # return a template using that file. Otherwise, Mint will look for a
237
- # template with that name in the Mint path. The `layout_or_style`
238
- # argument indicates whether the template we are looking for is
239
- # a layout or a style and will affect which type of template is returned
240
- # for a given template name. For example, `choose_template('normal',
241
- # :layout)` might return a Layout template referring to the file
242
- # ~/.mint/templates/normal/layout.haml. Changing the second argument
243
- # to :style would return a Style template whose source file is
244
- # ~/.mint/templates/normal/style.sass.
245
- def choose_template(name_or_file, layout_or_style=:layout, opts={})
246
- template = File.file?(name_or_file) ? Pathname.new(name_or_file) :
247
- find_template(name_or_file, layout_or_style)
248
-
249
- Mint.const_get(layout_or_style.to_s.capitalize).new(template, opts)
250
- end
251
-
252
- # Finds a template named `name` in the Mint path. If `layout_or_style`
253
- # is :layout, will look for `MINT_PATH/templates/layout.*`. If it is
254
- # :style, will look for `MINT_PATH/templates/TEMPLATE_NAME/style.*`.
255
- # Will reject raw HTML and CSS, which are currently not compatible
256
- # with Tilt, the rendering interface that Mint uses. (There are plans
257
- # to support these files in the future.) Mint assumes that a named
258
- # template will hold only one layout and one style template. It does
259
- # not know how to decide between style.sass and style.less, for
260
- # example. For predictable results, only include one template file
261
- # called `layout.*` in the TEMPLATE_NAME directory. If you do want
262
- # several style files in the same template directory, you will want
263
- # to refer to the style file by its path and not by its template name.
264
- # Will return nil if the named template is not in the Mint path.
265
- def find_template(name, layout_or_style)
266
- file = nil
267
-
268
- $path.each do |directory|
269
- templates_dir = directory + $default_directories[:templates]
270
- query = templates_dir + name + layout_or_style.to_s
271
-
272
- if templates_dir.exist?
273
- # Mint looks for any
274
- results = Pathname.glob "#{query}.*"
275
-
276
- # Will only allow files ending in extensions that Tilt can
277
- # handle. They are working to add raw HTML and CSS support, but
278
- # it is not yet implemented.
279
- results.reject {|r| r.to_s !~ /#{$source_formats.join('|')}/}
280
-
281
- if results.length > 0
282
- # Will return the first match found. See the `find_template`
283
- # description for a discussion of when you should and should
284
- # not rely on this method.
285
- file = results[0]
286
- break
287
- end
288
- end
289
- end
290
-
291
- file
292
- end
293
-
294
- # A Document renders itself (its content) in the context of its layout.
295
- # This differs from raw Resources, which render themselves in the
296
- # context of `Object.new`
297
- def render(args={})
298
- layout.render self, args
299
- end
300
-
301
- # Renders and writes to file all Resources described by a Document.
302
- # Specifically: it renders itself (inside of its own Layout) and then
303
- # renders its Style. This method will overwrite any existing content
304
- # in a Document's destination files. The `render_style?` option
305
- # provides an easy way to stop Mint from rendering a style, even
306
- # if the Document's style is not nil. This makes it possible to
307
- # associate a style with a Document (a style that can be called
308
- # from layouts via `stylesheet`) without regenerating that
309
- # stylesheet every time Mint runs.
310
- def mint(render_style=true)
311
-
312
- resources = [self]
313
- resources << style if render_style
314
-
315
- resources.compact.each do |r|
316
- dest = @root + r.destination + r.name
317
-
318
- # Kernel.puts <<-HERE
319
- # Source: #{@root + r.source}
320
- # Destination: #{dest}
321
- # HERE
322
-
323
- # Create any directories in the destination path that don't exist.
324
- FileUtils.mkdir_p dest.dirname
325
-
326
- # Render the resource to its destination file, overwriting any
327
- # existing content.
328
- dest.open 'w+' do |file|
329
- file << r.render
330
- end
331
- end
332
- end
333
-
334
- # Convenience methods for views
335
-
336
- # Returns a relative path from the Document to its stylesheet. Can
337
- # be called directly from inside a Layout template.
338
- def stylesheet
339
- normalize_path(@root + style.destination,
340
- @root + @destination) +
341
- style.name.to_s
342
- end
343
- end
344
-
345
- $project_default_opts = {}
346
-
347
- class ProjectDocument < Document
348
- attr_accessor :changed
349
- end
350
-
351
- class Project
352
- attr_accessor :root, :documents, :layout, :style, :config
353
-
354
- def initialize(root, opts={})
355
- @opts = opts
356
- @opts = $project_default_opts.merge opts
357
-
358
- if templ = @opts[:template]
359
- @opts[:layout], @opts[:style] = templ, templ
360
- end
361
-
362
- @root = root
363
- @documents = Array.new
364
- @style = @opts[:style]
365
- @layout = @opts[:layout]
366
- end
367
-
368
- def mint
369
- @documents.each_index do |i|
370
- render_style = (i == 0)
371
- @documents[i].mint(render_style)
372
- end
373
- end
374
-
375
- def change_status(document, new_status)
376
- end
377
-
378
- def status
379
- end
380
-
381
- def add(document)
382
- doc = Document.new
383
- doc.layout = @layout
384
- doc.style = @style
385
- doc.root = @root
386
-
387
- @documents << doc
388
- end
389
-
390
- alias_method :<<, :add
391
-
392
- def remove(document)
393
- @documents.delete document
394
- end
395
-
396
- def list
397
- end
398
-
399
- def edit(file_or_name, type=:layout)
400
-
401
-
402
- if editor = ENV['EDITOR']
403
- `#{editor} `
404
- end
405
- end
406
-
407
- def watch
408
- require 'fssm'
409
-
410
- FSSM.monitor do
411
- path root do
412
- glob '*'
413
-
414
- update {|base, relative|}
415
- delete {|base, relative|}
416
- create {|base, relative|}
417
- end
418
- end
419
-
420
- path 'templates/**/*' do
421
- update { |base, relative| update(relative) }
422
- delete { |base, relative| update(relative) }
423
- create { |base, relative| update(relative) }
424
- end
425
- end
426
-
427
- def update(relative)
428
- puts ">>> Change Detected to: #{relative} <<<"
429
-
430
- puts '>>> Update Complete <<<'
431
- end
432
- end
433
- end
1
+ require 'mint/mint'
2
+ require 'mint/version'