jim 0.2.3 → 0.3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/Gemfile +2 -1
  2. data/Gemfile.lock +2 -0
  3. data/HISTORY +13 -0
  4. data/README.md +148 -0
  5. data/Rakefile +6 -3
  6. data/bin/jim +1 -2
  7. data/default +0 -0
  8. data/jim.gemspec +142 -105
  9. data/lib/jim.rb +1 -1
  10. data/lib/jim/bundler.rb +168 -73
  11. data/lib/jim/cli.rb +200 -151
  12. data/lib/jim/index.rb +20 -9
  13. data/lib/jim/installer.rb +46 -46
  14. data/lib/jim/rack.rb +57 -20
  15. data/lib/jim/templates/jimfile +11 -5
  16. data/lib/jim/version_parser.rb +3 -3
  17. data/test/fixtures/infoincomments.js +1 -1
  18. data/test/fixtures/jimfile +14 -7
  19. data/test/fixtures/jquery-1.4.1.js +3 -6057
  20. data/test/fixtures/jquery.color.js +1 -1
  21. data/test/fixtures/localfile.js +1 -1
  22. data/test/fixtures/mustache.js/package.json +1 -1
  23. data/test/fixtures/noversion.js +1 -1
  24. data/test/fixtures/old_jimfile +7 -0
  25. data/test/fixtures/sammy-0.5.0/examples/backend/app.rb +4 -4
  26. data/test/fixtures/sammy-0.5.0/examples/backend/public/javascripts/app.js +18 -18
  27. data/test/fixtures/sammy-0.5.0/examples/backend/public/javascripts/jquery.cloudkit.js +1 -1
  28. data/test/fixtures/sammy-0.5.0/examples/backend/public/javascripts/jquery.js +1 -1
  29. data/test/fixtures/sammy-0.5.0/examples/backend/public/javascripts/sammy.js +161 -161
  30. data/test/fixtures/sammy-0.5.0/examples/backend/public/templates/task.html.erb +1 -1
  31. data/test/fixtures/sammy-0.5.0/examples/backend/public/templates/task_details.html.erb +1 -1
  32. data/test/fixtures/sammy-0.5.0/examples/backend/views/app.sass +6 -6
  33. data/test/fixtures/sammy-0.5.0/examples/backend/views/index.haml +5 -5
  34. data/test/fixtures/sammy-0.5.0/examples/form_handling/index.html +16 -16
  35. data/test/fixtures/sammy-0.5.0/examples/hello_world/index.html +13 -13
  36. data/test/fixtures/sammy-0.5.0/examples/location_override/data.html +28 -28
  37. data/test/fixtures/sammy-0.5.0/examples/location_override/index.html +18 -18
  38. data/test/fixtures/sammy-0.5.0/examples/location_override/test.html +36 -36
  39. data/test/fixtures/sammy-0.5.0/lib/min/sammy-0.5.0.min.js +1 -1
  40. data/test/fixtures/sammy-0.5.0/lib/min/sammy-lastest.min.js +1 -1
  41. data/test/fixtures/sammy-0.5.0/lib/plugins/sammy.cache.js +13 -13
  42. data/test/fixtures/sammy-0.5.0/lib/plugins/sammy.haml.js +2 -2
  43. data/test/fixtures/sammy-0.5.0/lib/plugins/sammy.json.js +15 -15
  44. data/test/fixtures/sammy-0.5.0/lib/plugins/sammy.mustache.js +46 -46
  45. data/test/fixtures/sammy-0.5.0/lib/plugins/sammy.nested_params.js +29 -29
  46. data/test/fixtures/sammy-0.5.0/lib/plugins/sammy.storage.js +54 -54
  47. data/test/fixtures/sammy-0.5.0/lib/plugins/sammy.template.js +17 -17
  48. data/test/fixtures/sammy-0.5.0/lib/sammy.js +220 -220
  49. data/test/fixtures/sammy-0.5.0/test/fixtures/partial.html +1 -1
  50. data/test/fixtures/sammy-0.5.0/test/index.html +26 -26
  51. data/test/fixtures/sammy-0.5.0/test/test_sammy_application.js +60 -60
  52. data/test/fixtures/sammy-0.5.0/test/test_sammy_event_context.js +21 -21
  53. data/test/fixtures/sammy-0.5.0/test/test_sammy_location_proxy.js +3 -3
  54. data/test/fixtures/sammy-0.5.0/test/test_sammy_plugins.js +17 -17
  55. data/test/fixtures/sammy-0.5.0/test/test_sammy_storage.js +4 -4
  56. data/test/helper.rb +15 -0
  57. data/test/test_jim_bundler.rb +114 -74
  58. data/test/test_jim_cli.rb +34 -18
  59. data/test/test_jim_index.rb +19 -19
  60. data/test/test_jim_installer.rb +13 -13
  61. data/test/test_jim_rack.rb +41 -0
  62. data/test/test_jim_version_parser.rb +4 -4
  63. metadata +204 -41
  64. data/.gitignore +0 -24
  65. data/README.rdoc +0 -105
  66. data/lib/jim/templates/commands +0 -58
data/lib/jim.rb CHANGED
@@ -5,7 +5,7 @@ require 'version_sorter'
5
5
  require 'digest/md5'
6
6
 
7
7
  module Jim
8
- VERSION = '0.2.3'
8
+ VERSION = '0.3.0.pre'
9
9
 
10
10
  class Error < RuntimeError; end
11
11
  class InstallError < Error; end
data/lib/jim/bundler.rb CHANGED
@@ -3,99 +3,170 @@ module Jim
3
3
  # versions and then can bundle, compress, or copy those files into specific dirs
4
4
  # or files.
5
5
  #
6
- # A Jimfile has a really simple format:
7
- #
8
- # // comments look like JS comments
9
- # // you can set options by adding comments that look like JSON pairs
10
- # // bundle_path: /path/to/bundle.js
11
- #
12
- # // A requirement is just a name and an optional version
13
- # // requirements are resolved and bundled in order of specification
14
- # jquery 1.4.2
15
- # jquery.color
16
- # sammy 0.5.0
6
+ # A Jimfile has a really simple format that's encoded as JSON. Options are
7
+ # specified as key value pairs and 'bundles' are specified under the "bundles"
8
+ # attribute. Each item in a bundle can be a simple string, or an array of
9
+ # [name, version].
17
10
  #
11
+ # {
12
+ # "bundle_dir": "test/tmp/",
13
+ # "vendor_dir": "test/tmp/public/javascripts/vendor",
14
+ # "bundles": {
15
+ # "default": [
16
+ # ["jquery", "1.4.1"],
17
+ # "myproject",
18
+ # "localfile"
19
+ # ],
20
+ # "base": [
21
+ # "jquery"
22
+ # ]
23
+ # }
24
+ # }
18
25
  #
26
+ # Pre jim version 0.3 had a different simpler but proprietary and (possibly confusing)
27
+ # Jimfile format. Bundler can still read that format and can actually convert
28
+ # it into the new JSON format for you. Future versions may remove this support.
19
29
  class Bundler
20
30
  class MissingFile < Jim::Error; end
31
+ class InvalidBundle < Jim::Error; end
21
32
 
22
- attr_accessor :jimfile, :index, :requirements, :paths, :options
33
+ attr_accessor :jimfile, :index, :bundles, :paths, :options
34
+ attr_reader :jimfile, :bundle_dir
23
35
 
24
36
  # create a new bundler instance passing in the Jimfile as a `Pathname` or a
25
37
  # string. `index` is a Jim::Index
26
38
  def initialize(jimfile, index = nil, extra_options = {})
27
- self.jimfile = jimfile.is_a?(Pathname) ? jimfile.read : jimfile
28
39
  self.index = index || Jim::Index.new
29
- self.options = {}
30
- self.requirements = []
31
- parse_jimfile
40
+ self.options = {
41
+ :compressed_suffix => '.min'
42
+ }
43
+ self.bundles = {}
44
+ self.jimfile = jimfile
32
45
  self.options = options.merge(extra_options)
33
- self.paths = []
46
+ self.paths = {}
34
47
  if options[:vendor_dir]
35
48
  logger.debug "adding vendor dir to index #{options[:vendor_dir]}"
36
49
  self.index.add(options[:vendor_dir])
37
50
  end
38
51
  end
39
52
 
40
- # resove the requirements specified into Jimfile or raise a MissingFile error
53
+ # Set the Jimfile and parse it. If `file` is a `Pathname` read it as a string.
54
+ # If it's a string assume that its in the Jimfile format.
55
+ def jimfile=(file)
56
+ @jimfile = file.is_a?(Pathname) ? file.read : file
57
+ # look for old jimfile
58
+ if @jimfile =~ /^\/\//
59
+ logger.warn "You're Jimfile is in a deprecated format. Run `jim update_jimfile` to convert it."
60
+ parse_old_jimfile
61
+ else
62
+ parse_jimfile
63
+ end
64
+ @jimfile
65
+ end
66
+
67
+ # Set the `bundle_dir` where bundles will be written. If `bundle_dir` is set
68
+ # to nil, all bundles will be written to STDOUT
69
+ def bundle_dir=(new_dir)
70
+ if new_dir
71
+ new_dir = Pathname.new(new_dir)
72
+ new_dir.mkpath
73
+ end
74
+ @bundle_dir = new_dir
75
+ end
76
+
77
+ # Output the parse Jimfile requirements and options as a Jimfile-ready
78
+ # JSON-encoded string
79
+ def jimfile_to_json
80
+ h = {
81
+ "bundle_dir" => bundle_dir
82
+ }.merge(options)
83
+ h['bundles'] = {}
84
+ self.bundles.each do |bundle_name, requirements|
85
+ h['bundles']['bundle_name'] = []
86
+ requirements.each do |name, version|
87
+ h['bundles']['bundle_name'] << if version.nil? || version.strip == ''
88
+ name
89
+ else
90
+ [name, version]
91
+ end
92
+ end
93
+ end
94
+ Yajl::Encoder.encode(h, :pretty => true)
95
+ end
96
+
97
+ # Resolve the requirements specified in the Jimfile for each bundle to `paths`
98
+ # Raises MissingFile error
41
99
  def resolve!
42
- self.requirements.each do |search|
43
- name, version = search.strip.split(/\s+/)
44
- path = self.index.find(name, version)
45
- if !path
46
- raise(MissingFile,
47
- "Could not find #{name} #{version} in any of these paths #{index.directories.join(':')}")
100
+ self.bundles.each do |bundle_name, requirements|
101
+ self.paths[bundle_name] = []
102
+ requirements.each do |name, version|
103
+ path = self.index.find(name, version)
104
+ if !path
105
+ raise(MissingFile,
106
+ "Could not find #{name} #{version} in any of these paths #{index.directories.join(':')}")
107
+ end
108
+ self.paths[bundle_name] << [path, name, version]
48
109
  end
49
- self.paths << [path, name, version]
50
110
  end
51
111
  paths
52
112
  end
53
113
 
54
- # concatenate all the requirements into a single file and write to `to` or to the
55
- # path specified in the :bundled_path option
56
- def bundle!(to = nil)
114
+ # Concatenate all of the bundles to the dir set in `bundle_dir`
115
+ # or a specific bundle specified by bundle name. Setting `compress` to
116
+ # true will run the output of each bundle to the Google Closure Compiler.
117
+ # You can also use the YUI compressor by setting the option :compressor to 'yui'
118
+ # Raises an error if there is no bundled dir or specific bundle set
119
+ def bundle!(bundle_name = false, compress = false)
57
120
  resolve! if paths.empty?
58
- to = options[:bundled_path] if to.nil? && options[:bundled_path]
59
- io_for_path(to) do |io|
60
- logger.info "Bundling to #{to}" if to
61
- paths.each do |path, name, version|
62
- io << path.read << "\n"
121
+ if bundle_name
122
+ files = self.paths[bundle_name]
123
+ if bundle_dir
124
+ path = path_for_bundle(bundle_name, compress)
125
+ concatenate(files, path, compress)
126
+ [path]
127
+ else
128
+ concatenate(files, "", compress)
63
129
  end
130
+ elsif bundle_dir
131
+ self.paths.collect do |bundle_name, files|
132
+ path = path_for_bundle(bundle_name, compress)
133
+ concatenate(files, path, compress)
134
+ path
135
+ end
136
+ else
137
+ raise(InvalidBundle,
138
+ "Must set either a :bundle_dir to write files to or a specific bundle to write to STDOUT")
64
139
  end
65
140
  end
66
141
 
67
- # concatenate all the requirements into a single file then run through a JS
68
- # then write to `to` or to the path specified in the :bundled_path option.
69
- # You can also use the YUI compressor by setting the option :compressor to 'yui'
70
- def compress!(to = nil)
71
- to = options[:compressed_path] if to.nil? && options[:compressed_path]
72
- io_for_path(to) do |io|
73
- logger.info "Compressing to #{to}"
74
- io << compress_js(bundle!(false))
75
- end
142
+ # Alias to running `bundle!` with `compress` = `true`
143
+ def compress!(bundle_name = false)
144
+ bundle!(bundle_name, true)
76
145
  end
77
146
 
78
- # copy each of the requirements into the dir specified with `dir` or the path
79
- # specified with the :vendor_dir option
147
+ # Copy each of the requirements into the dir specified with `dir` or the path
148
+ # specified with the :vendor_dir option. Returns the dir it was vendored to.
80
149
  def vendor!(dir = nil, force = false)
81
150
  resolve! if paths.empty?
82
151
  dir ||= options[:vendor_dir]
83
152
  dir ||= 'vendor' # default
84
- logger.info "Vendoring to #{dir}"
85
- paths.each do |path, name, version|
153
+ logger.debug "Vendoring #{paths.length} files to #{dir}"
154
+ paths.collect {|n, p| p }.flatten.each do |path, name, version|
86
155
  if index.in_jimhome?(path)
87
156
  Jim::Installer.new(path, dir, :shallow => true, :force => force).install
88
157
  end
89
158
  end
159
+ dir
160
+ end
161
+
162
+ # Returns an array of `Pathname`s where each of the bundles will be written
163
+ def bundle_paths
164
+ self.bundles.collect {|name, reqs| path_for_bundle(name) }
90
165
  end
91
166
 
92
- # Run the uncompressed test through a JS compressor (closure-compiler) by
167
+ # Run the uncompressed js through a JS compressor (closure-compiler) by
93
168
  # default. Setting options[:compressor] == 'yui' will force the YUI JS Compressor
94
169
  def compress_js(uncompressed)
95
- # if uncompressed.is_a?(File) && uncompressed.closed?
96
- # puts "uncompressed is a file"
97
- # uncompressed = File.read(uncompressed.path)
98
- # end
99
170
  if options[:compressor] == 'yui'
100
171
  begin
101
172
  require "yui/compressor"
@@ -111,40 +182,64 @@ module Jim
111
182
  end
112
183
  compressor = ::Closure::Compiler.new
113
184
  end
114
- compressor.compress(uncompressed)
185
+ begin
186
+ compressor.compress(uncompressed)
187
+ rescue Exception => e
188
+ logger.error e.message
189
+ end
115
190
  end
116
191
 
117
192
  private
118
- def io_for_path(to, &block)
119
- case to
120
- when IO
121
- yield to
122
- to.close
123
- to
124
- when Pathname
125
- to.dirname.mkpath
126
- io = to.open('w')
127
- yield io
128
- io.close
129
- io
130
- when String
131
- to = Pathname.new(to)
132
- io_for_path(to, &block)
133
- else
134
- io = ""
135
- yield io
136
- io
193
+ def concatenate(paths, io, compress)
194
+ if io.is_a?(Pathname)
195
+ io = io.open('w')
196
+ logger.debug "#{compress ? 'Compressing' : 'Bundling'} to #{io}"
137
197
  end
198
+ final_io, io = io, "" if compress
199
+ paths.each do |path, name, version|
200
+ io << path.read << "\n"
201
+ end
202
+ if compress
203
+ final_io << compress_js(io)
204
+ io = final_io
205
+ end
206
+ io.close if io.respond_to?(:close)
207
+ io
208
+ end
209
+
210
+ def path_for_bundle(bundle_name, compressed = false)
211
+ bundle_dir + "#{bundle_name}#{compressed ? options[:compressed_suffix] : ''}.js"
138
212
  end
139
213
 
140
214
  def parse_jimfile
215
+ json = Yajl::Parser.parse(jimfile)
216
+ json.each do |k, v|
217
+ set_option(k, v)
218
+ end
219
+ end
220
+
221
+ def parse_old_jimfile
222
+ bundle = []
141
223
  jimfile.each_line do |line|
142
224
  if /^\/\/\s?([^\:]+)\:\s(.*)$/.match line
143
- self.options[$1.to_sym] = $2.strip
225
+ k, v = $1, $2.strip
226
+ if k == 'bundled_path'
227
+ k, v = 'bundle_dir', File.dirname(v)
228
+ end
229
+ set_option(k, v)
144
230
  elsif line !~ /^\// && line.strip != ''
145
- self.requirements << line
231
+ bundle << line.split(/\s+/, 2).compact.collect {|s| s.strip }.reject {|s| s == '' }
146
232
  end
147
233
  end
234
+ self.bundles['default'] = bundle
235
+ end
236
+
237
+ def set_option(k, v)
238
+ if respond_to?("#{k}=")
239
+ self.send("#{k}=", v)
240
+ else
241
+ self.options[k.to_sym] = v
242
+ end
148
243
  end
149
244
 
150
245
  def logger
data/lib/jim/cli.rb CHANGED
@@ -1,212 +1,250 @@
1
- require 'optparse'
2
- require 'readline'
1
+ require 'thor'
3
2
 
4
3
  module Jim
4
+ # CLI handles the command line interface for the `jim` binary. It is a `Thor`
5
+ # application.
6
+ class CLI < ::Thor
7
+ include Thor::Actions
5
8
 
6
- # CLI handles the command line interface for the `jim` binary.
7
- # The layout is farily simple. Options are parsed using optparse.rb and
8
- # the different public methods represent 1-1 the commands provided by the bin.
9
- class CLI
9
+ attr_accessor :jimfile, :jimhome, :debug, :force
10
10
 
11
- attr_accessor :jimfile, :jimhome, :force, :stdout
11
+ source_root File.dirname(__FILE__) + '/templates'
12
+
13
+ class_option "jimhome",
14
+ :type => :string,
15
+ :banner => "set the install path/JIMHOME dir (default ~/.jim)"
16
+
17
+ class_option "jimfile",
18
+ :type => :string,
19
+ :aliases => '-j',
20
+ :banner => "load specific Jimfile at path (default ./Jimfile)"
21
+
22
+ class_option "force",
23
+ :default => false,
24
+ :aliases => '-f',
25
+ :banner => "force file creation/overwrite"
26
+
27
+ class_option "debug",
28
+ :default => false,
29
+ :aliases => '-d',
30
+ :banner => "set log level to debug"
31
+
32
+ class_option "version",
33
+ :type => :boolean,
34
+ :aliases => '-v',
35
+ :banner => "print version and exit"
12
36
 
13
37
  # create a new instance with the args passed from the command line i.e. ARGV
14
- def initialize(args)
15
- @output = ""
38
+ def initialize(*)
39
+ super
40
+ if options[:version]
41
+ say "jim #{Jim::VERSION}", :red
42
+ exit
43
+ end
16
44
  # set the default jimhome
17
- self.jimhome = Pathname.new(ENV['JIMHOME'] || '~/.jim').expand_path
45
+ self.jimhome = Pathname.new(options[:jimhome] || ENV['JIMHOME'] || '~/.jim').expand_path
18
46
  # parse the options
19
- self.jimfile = Pathname.new('Jimfile')
20
- @args = parse_options(args)
21
- ## try to run based on args
22
- end
23
-
24
- # method called by the bin directly after initialization.
25
- def run(reraise = false)
26
- command = @args.shift
27
- if command && respond_to?(command)
28
- self.send(command, *@args)
29
- elsif command.nil? || command.strip == ''
30
- cheat
31
- else
32
- @output << "No action found for #{command}. Run -h for help."
33
- end
34
- @output
35
- rescue ArgumentError => e
36
- @output << "#{e.message} for #{command}"
37
- raise e if reraise
38
- rescue Jim::FileExists => e
39
- @output << "#{e.message} already exists, bailing. Use --force if you're sure"
40
- raise e if reraise
41
- rescue => e
42
- @output << e.message + " (#{e.class})"
43
- raise e if reraise
44
- end
45
-
46
- # list the possible commands to the logger
47
- def commands
48
- logger.info "Usage: jim [options] [command] [args]\n"
49
- logger.info "Commands:"
50
- logger.info template('commands')
51
- end
52
-
53
- # list the possible commands without detailed descriptions
54
- def cheat
55
- logger.info "Usage: jim [options] [command] [args]\n"
56
- logger.info "Commands:"
57
- logger.info [*template('commands')].grep(/^\w/).join
58
- logger.info "run commands for details"
59
- end
60
- alias :help :cheat
61
-
62
- # initialize the current dir with a new Jimfile
47
+ self.jimfile = Pathname.new(options[:jimfile] || 'Jimfile').expand_path
48
+ self.force = options[:force]
49
+ self.debug = options[:debug]
50
+ logger.level = Logger::DEBUG if debug
51
+ end
52
+
53
+ desc 'init [APPDIR]',
54
+ 'Create an example Jimfile at path or the current directory if path is omitted'
63
55
  def init(dir = nil)
64
56
  dir = Pathname.new(dir || '')
65
57
  jimfile_path = dir + 'Jimfile'
66
- if jimfile_path.readable? && !force
67
- raise Jim::FileExists.new(jimfile_path)
68
- else
69
- File.open(jimfile_path, 'w') do |f|
70
- f << template('jimfile')
71
- end
72
- logger.info "wrote Jimfile to #{jimfile_path}"
73
- end
58
+ template('jimfile', jimfile_path)
74
59
  end
75
60
 
76
- # install the file/project `url` into `jimhome`
61
+ desc 'install <URL> [NAME] [VERSION]',
62
+ "Install the file(s) at url into the JIMHOME directory."
63
+
64
+ long_desc <<-EOT
65
+ Install the file(s) at url into the JIMHOME directory. URL can be any path
66
+ or url that Downlow understands. This means:
67
+
68
+ jim install http://code.jquery.com/jquery-1.4.1.js\n
69
+ jim install ../sammy/\n
70
+ jim install gh://quirkey/sammy\n
71
+ jim install git://github.com/jquery/jquery.git\n
72
+ EOT
77
73
  def install(url, name = false, version = false)
78
74
  Jim::Installer.new(url, jimhome, :force => force, :name => name, :version => version).install
79
75
  end
80
76
 
81
- # bundle the files specified in Jimfile into `to`
82
- def bundle(to = nil)
83
- to = STDOUT if stdout
84
- io = bundler.bundle!(to)
85
- logger.info "Wrote #{File.size(io.path) / 1024}kb" if io.respond_to? :path
77
+ desc 'bundle [BUNDLE_NAME]',
78
+ "Bundle the files specified in Jimfile"
79
+ long_desc <<-EOT
80
+ Concatenate all the bundles listed in a Jimfile and save them to the bundle dir
81
+ specified in the options or in the Jimfile.
82
+
83
+ If [BUNDLE_NAME] is specified, only bundles that specific bundle.
84
+
85
+ If no Jimfile is set in the options, assumes ./Jimfile.
86
+ EOT
87
+ method_option "bundle_dir",
88
+ :type => :string,
89
+ :banner => "override the bundle_dir set in the Jimfile"
90
+ method_option "stdout",
91
+ :default => false,
92
+ :aliases => '-o',
93
+ :type => :boolean,
94
+ :banner => "write the bundle to STDOUT"
95
+ def bundle(bundle_name = nil)
96
+ make_bundle(bundle_name, false)
86
97
  end
87
98
 
88
- # compress the files specified in Jimfile into `to`
89
- def compress(to = nil)
90
- to = STDOUT if stdout
91
- io = bundler.compress!(to)
92
- logger.info "Wrote #{File.size(io.path) / 1024}kb" if io.respond_to? :path
99
+ desc "compress [BUNDLE_NAME]",
100
+ "Bundle all the files listed in a Jimfile, run through the google closure " +
101
+ "compiler and save them to [COMPRESSED_PATH]."
102
+ long_desc <<-EOT
103
+ Concatenate all the bundles listed in a Jimfile, run them through the google
104
+ closure compiler and save them to the bundle dirspecified in the options or
105
+ in the Jimfile.
106
+
107
+ If a [BUNDLE_NAME] is specified, only bundle and compress that bundle.
108
+
109
+ If no Jimfile is set in the options, assumes ./Jimfile.
110
+ EOT
111
+ method_option "bundle_dir",
112
+ :type => :string,
113
+ :banner => "override the bundle_dir set in the Jimfile"
114
+ method_option "stdout",
115
+ :default => false,
116
+ :aliases => '-o',
117
+ :type => :boolean,
118
+ :banner => "write the bundle to STDOUT"
119
+ def compress(bundle_name = nil)
120
+ make_bundle(bundle_name, true)
93
121
  end
94
122
 
95
- # copy/vendor all the files specified in Jimfile to `dir`
123
+ desc "vendor [VENDOR_DIR]",
124
+ "Copy all the files listed in Jimfile to the vendor_dir"
96
125
  def vendor(dir = nil)
97
- bundler.vendor!(dir, force)
126
+ dir = bundler.vendor!(dir, force)
127
+ say("Vendored files to #{dir}", :green)
128
+ rescue Jim::Error => e
129
+ say e.message, :red
98
130
  end
99
131
 
100
- # list the only the _installed_ projects and versions.
101
- # Match names against `search` if supplied.
132
+ desc "list [SEARCH]",
133
+ "List all the installed packages and their versions, optionally limiting by [SEARCH]"
102
134
  def list(search = nil)
103
- logger.info "Getting list of installed files in\n#{installed_index.directories.join(':')}"
104
- logger.info "Searching for '#{search}'" if search
135
+ say "Getting list of installed files in"
136
+ say("#{installed_index.directories.join(':')}", :yellow)
137
+ say("Searching for '#{search}'", :yellow) if search
105
138
  list = installed_index.list(search)
106
- logger.info "Installed:"
139
+ say "Installed:"
107
140
  print_version_list(list)
108
141
  end
109
- alias :installed :list
142
+ map "installed" => "list"
110
143
 
111
- # list all available projects and versions including those in the local path, or
112
- # paths specified in a Jimfile.
113
- # Match names against `search` if supplied.
144
+ desc "available [SEARCH]",
145
+ "List all available projects and versions " +
146
+ "including those in the local path, or paths specified in a Jimfile"
114
147
  def available(search = nil)
115
- logger.info "Getting list of all available files in\n#{index.directories.join("\n")}"
116
- logger.info "Searching for '#{search}'" if search
148
+ say "Getting list of all available files in\n#{index.directories.join("\n")}"
149
+ say "Searching for '#{search}'" if search
117
150
  list = index.list(search)
118
- logger.info "Available:"
151
+ say "Available:"
119
152
  print_version_list(list)
120
153
  end
121
154
 
122
- # Iterates over matching files and prompts for removal
155
+ desc "remove <NAME> [VERSION]",
156
+ "Iterate through the install files and prompt for the removal of those " +
157
+ "matching the supplied NAME and VERSION"
123
158
  def remove(name, version = nil)
124
- logger.info "Looking for files matching #{name} #{version}"
159
+ say "Looking for files matching #{name} #{version}"
125
160
  files = installed_index.find_all(name, version)
126
161
  if files.length > 0
127
- logger.info "Found #{files.length} matching files"
162
+ say "Found #{files.length} matching files"
128
163
  removed = 0
129
164
  files.each do |filename|
130
- response = Readline.readline("Remove #{filename}? (y/n)\n")
131
- if response.strip =~ /y/i
132
- logger.info "Removing #{filename}"
165
+ do_remove = yes?("Remove #{filename}?", :red)
166
+ if do_remove
167
+ say "Removing #{filename}"
133
168
  filename.delete
134
169
  removed += 1
135
170
  else
136
- logger.info "Skipping #{filename}"
171
+ say "Skipping #{filename}", :yellow
137
172
  end
138
173
  end
139
- logger.info "Removed #{removed} files."
174
+ say "Removed #{removed} files."
140
175
  else
141
- logger.info "No installed files matched."
176
+ say "No installed files matched."
142
177
  end
143
178
  end
144
- alias :uninstall :remove
179
+ map "uninstall" => "remove"
145
180
 
146
- # list the files and their resolved paths specified in the Jimfile
181
+ desc "resolve",
182
+ "Resolve all the paths listed in a Jimfile and print them to STDOUT. " +
183
+ "If no Jimfile is set in the options, assumes ./Jimfile."
147
184
  def resolve
148
185
  resolved = bundler.resolve!
149
- logger.info "Files:"
150
- resolved.each do |r|
151
- logger.info r.join(" | ")
186
+ say "Files:"
187
+ resolved.each do |bundle_name, requirements|
188
+ say bundle_name, :green
189
+ say "-----------------------", :green
190
+ requirements.each do |path, name, version|
191
+ say [name, version, path].join(" | ") + "\n"
192
+ end
152
193
  end
153
194
  resolved
195
+ rescue Jim::Error => e
196
+ say e.message, :red
154
197
  end
155
198
 
156
- # vendor to dir, then bundle and compress the Jimfile contents
199
+ desc "pack [DIR]",
200
+ "Runs in order, vendor, bundle, compress. This command " +
201
+ "simplifies the common workflow of vendoring and re-bundling " +
202
+ "before committing or deploying changes to a project"
157
203
  def pack(dir = nil)
158
- logger.info "packing the Jimfile for this project"
159
- vendor(dir)
160
- bundle
161
- compress
204
+ say "Packing the Jimfile for this project"
205
+ invoke :vendor, [dir]
206
+ invoke :bundle
207
+ invoke :compress
162
208
  end
163
209
 
164
- private
165
- def parse_options(runtime_args)
166
- OptionParser.new("", 24, ' ') do |opts|
167
- opts.banner = "Usage: jim [options] [command] [args]"
168
-
169
- opts.separator ""
170
- opts.separator "jim options:"
171
-
172
- opts.on("--jimhome path/to/home", "set the install path/JIMHOME dir (default ~/.jim)") {|h|
173
- self.jimhome = Pathname.new(h)
174
- }
175
-
176
- opts.on("-j", "--jimfile path/to/jimfile", "load specific Jimfile at path (default ./Jimfile)") { |j|
177
- self.jimfile = Pathname.new(j)
178
- }
179
-
180
- opts.on("-f", "--force", "force file creation/overwrite") {|f|
181
- self.force = true
182
- }
183
-
184
- opts.on("-d", "--debug", "set log level to debug") {|d|
185
- logger.level = Logger::DEBUG
186
- }
187
-
188
- opts.on("-o", "--stdout", "write output of commands (like bundle and compress to STDOUT)") {|o|
189
- logger.level = Logger::ERROR
190
- self.stdout = true
191
- }
192
-
193
- opts.on("-v", "--version", "print version") {|d|
194
- puts "jim #{Jim::VERSION}"
195
- exit
196
- }
210
+ desc "watch [DIR]",
211
+ "Watches your Jimfile and JS files and triggers `bundle` if something " +
212
+ "changes. Handy for development."
213
+ def watch(dir = nil)
214
+ require 'fssm'
215
+ run_update = lambda {|type, path|
216
+ unless bundler.bundle_paths.any? {|p| path.include?(p) }
217
+ say("--> #{path} #{type}")
218
+ system "jim bundle"
219
+ end
220
+ }
221
+ say "Now watching JS files..."
222
+ run_update["started", 'Jimfile']
223
+ FSSM.monitor(Dir.pwd, [File.join('**','*.js'), 'Jimfile']) do
224
+ update do |base, relative|
225
+ run_update["changed", relative]
226
+ end
197
227
 
228
+ create do |base, relative|
229
+ run_update["created", relative]
230
+ end
198
231
 
199
- opts.on_tail("-h", "--help", "Show this message. Run jim commands for list of commands.") do
200
- puts opts.help
201
- exit
232
+ delete do |base, relative|
233
+ run_update["deleted", relative]
202
234
  end
235
+ end
236
+ end
203
237
 
204
- end.parse! runtime_args
205
- rescue OptionParser::MissingArgument => e
206
- logger.warn "#{e}, run -h for options"
207
- exit
238
+ desc "update_jimfile [APP_DIR]",
239
+ "Converts a Jimfile from the old pre 0.3 format to the JSON format."
240
+ def update_jimfile(dir = nil)
241
+ dir = Pathname.new(dir || '')
242
+ bundler
243
+ copy_file(dir + 'Jimfile', dir + 'Jimfile.old')
244
+ create_file(dir + 'Jimfile', bundler.jimfile_to_json)
208
245
  end
209
246
 
247
+ private
210
248
  def index
211
249
  @index ||= Jim::Index.new(install_dir, Dir.pwd)
212
250
  end
@@ -223,18 +261,29 @@ module Jim
223
261
  jimhome + 'lib'
224
262
  end
225
263
 
226
- def template(path)
227
- (Pathname.new(__FILE__).dirname + 'templates' + path).read
228
- end
229
-
230
264
  def logger
231
265
  Jim.logger
232
266
  end
233
267
 
234
268
  def print_version_list(list)
235
269
  list.each do |file, versions|
236
- logger.info "#{file} (#{VersionSorter.rsort(versions.collect {|v| v[0] }).join(', ')})"
270
+ say "#{file} (#{VersionSorter.rsort(versions.collect {|v| v[0] }).join(', ')}\n"
271
+ end
272
+ end
273
+
274
+ def make_bundle(bundle_name, compress = false)
275
+ bundler.bundle_dir = options[:bundle_dir] if options[:bundle_dir]
276
+ bundler.bundle_dir = nil if options[:stdout]
277
+ result = bundler.bundle!(bundle_name, compress)
278
+ if options[:stdout]
279
+ puts result
280
+ else
281
+ result.each do |path|
282
+ say("Wrote #{path} #{File.size(path.to_s) / 1024}kb", :green)
283
+ end
237
284
  end
285
+ rescue Jim::Error => e
286
+ say e.message, :red
238
287
  end
239
288
 
240
289
  end