Class: SC::Tools

Inherits:
Thor
  • Object
show all
Defined in:
lib/sproutcore/tools.rb,
lib/sproutcore/tools/gen.rb,
lib/sproutcore/tools/init.rb,
lib/sproutcore/tools/build.rb,
lib/sproutcore/tools/server.rb,
lib/sproutcore/tools/manifest.rb,
lib/sproutcore/tools/build_number.rb

Overview

Generates components for SproutCore. The generator allows the user to quickly set up a SproutCore framework using any of the built in templates such as the project itself, apps, models, views, controllers and more

The template files will be copied to their target location and also be parsed through the Erubis templating system. Template file paths can contain instance variables in the form of class_name which in turn would be the value of class_name once generated.

To develop a new generator, you can add it to the sproutcore/gen/ directory with the following file structure:

gen/
 <generator_name>/   - singular directory name of the generator
    Buildfile        - contains all config options and build tasks
    README           - prints when generator is done
    templates/       - contains all the files you want to generate
    USAGE            - prints when user gives uses --help option

Defined Under Namespace

Classes: FatalException

Constant Summary collapse

MANIFEST_OPTIONS =

Standard manifest options. Used by build tool as well.

{ :languages     => :optional,
:symlink       => false,
:buildroot     => :optional,
:stageroot     => :optional,
:format        => :optional,
:output        => :output,
:all           => false,
['--include-required', '-r'] => false }

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Thor

[], default_task, desc, group, group_name, #help, install_task, invoke, map, maxima, method_options, opts, package_task, spec_task, subclass_files, subclasses, tasks

Constructor Details

#initialize(options, *args) ⇒ Tools

Returns a new instance of Tools.



77
78
79
# File 'lib/sproutcore/tools.rb', line 77

def initialize(options, *args)
  super
end

Class Method Details

.start(args = ARGV) ⇒ Object

Fix start so that it treats command-name like command_name



303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/sproutcore/tools.rb', line 303

def self.start(args = ARGV)
  # manually check for verbose in case we don't get far enough in regular
  # processing to actually set the verbose mode.
  is_verbose = %w(-v -V --verbose --very-verbose).any? { |x| args.include?(x) }
  begin
    super(args)
  rescue Exception => e
    SC.logger.fatal(e)
    if is_verbose && !e.kind_of?(FatalException)
      SC.logger.fatal("BACKTRACE:\n#{e.backtrace.join("\n")}\n")
    end
  end
end

Instance Method Details

#build(*targets) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/sproutcore/tools/build.rb', line 17

def build(*targets)

  # Copy some key props to the env
  SC.env.build_prefix   = options.buildroot if options.buildroot
  SC.env.stating_prefix = options.stageroot if options.stageroot
  SC.env.use_symlink    = options.symlink 
  SC.env.clean          = options.clean 
  
  # Get entries option
  entry_filters = nil
  if options[:entries]
    entry_filters = options[:entries].split(',')
  end
  
  # Get the manifests to build
  manifests = build_manifests(*targets)

  # First clean all manifests
  # Do this before building so we don't accidentally erase already build
  # nested targets.
  if SC.env.clean
    manifests.each do |manifest|
      build_root = manifest.target.build_root
      info "Cleaning #{build_root}"
      FileUtils.rm_r(build_root) if File.directory?(build_root)
      
      staging_root = manifest.target.staging_root
      info "Cleaning #{staging_root}"
      FileUtils.rm_r(staging_root) if File.directory?(staging_root)
    end
  end
      
  # Now build entries for each manifest...
  manifests.each do |manifest|
    
    # get entries.  If "entries" option was specified, use to filter 
    # filename.  Must match end of filename.
    entries = manifest.entries
    if entry_filters
      entries = entries.select do |entry|
        is_allowed = false
        entry_filters.each do |filter|
          is_allowed = entry.filename =~ /#{filter}$/
          break if is_allowed
        end
        is_allowed
      end
    end
    
    # if there are entries to build, log and build
    if entries.size > 0
      info "Building entries for #{manifest.target.target_name}:#{manifest.language}..."
      
      entries.each do |entry|
        info "  #{entry.filename} -> #{entry.build_path}"
        entry.build!
      end
    end
  end
  
end

#build_manifests(*targets) ⇒ Object

Core method to process command line options and then build a manifest. Shared by sc-manifest and sc-build commands.



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/sproutcore/tools.rb', line 263

def build_manifests(*targets)
  
  requires_project! # get project
  targets = find_targets(*targets) # get targets
  languages = find_languages(*targets) # get languages

  # log output
  SC.logger.info "Building targets: #{targets.map { |t| t.target_name } * ","}"
  SC.logger.info "Building languages: #{ languages * "," }"
  
  # Now fetch the manifests to build.  One per target/language
  manifests = targets.map do |target|
    languages.map { |l| target.manifest_for :language => l }
  end
  manifests.flatten!
  
  # Build'em
  manifests.each do |manifest| 
    SC.logger.info "Building manifest for: #{manifest.target.target_name}:#{manifest.language}"
    manifest.build!
  end
  
  return manifests
end

#build_number(*targets) ⇒ Object



17
18
19
20
# File 'lib/sproutcore/tools/build_number.rb', line 17

def build_number(*targets)
  target = requires_target!(*targets)
  $stdout << target.prepare!.build_number
end

#debug(description) ⇒ Object

Helper method. Call this when you want to log a debug message.



53
54
55
# File 'lib/sproutcore/tools.rb', line 53

def debug(description)
  SC.logger.debug(description)
end

#fatal!(description) ⇒ Object

Helper method. Call this when an acception occurs that is fatal due to a problem with the user.

Raises:



42
43
44
# File 'lib/sproutcore/tools.rb', line 42

def fatal!(description)
  raise FatalException, description
end

#find_languages(*targets) ⇒ Object

Discovers the languages requested by the user for a build. Uses the --languages command line option or disovers in targets.



250
251
252
253
254
255
256
257
258
259
# File 'lib/sproutcore/tools.rb', line 250

def find_languages(*targets)
  # Use passed languages.  If none are specified, merge installed 
  # languages for all app targets.
  if (languages = options.languages).nil?
    languages = targets.map { |t| t.installed_languages }
  else
    languages = languages.split(':').map { |l| l.to_sym }
  end
  languages.flatten.uniq.compact
end

#find_targets(*targets) ⇒ Object

Find one or more targets with the passed target names in the current project. Requires a project to function.



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/sproutcore/tools.rb', line 177

def find_targets(*targets)
  
  debug "finding targets with names: '#{targets * "','"}'"
  requires_project!
  
  # Filter out any empty target names.  Sometimes this happens when 
  # processing arguments.
  targets.reject! { |x| x.nil? || x.size == 0 }
  
  # If targets are specified, find the targets project or parents...
  if targets.size > 0
    targets = targets.map do |target_name|
      begin
        ret = project.target_for(target_name)
      rescue Exception => e
        SC.logger.fatal("Exception when searching for target #{target_name}.  Perhaps your Buildfile is configured wrong?")
        raise e
      end
      
      if ret.nil?
        fatal! "No target named #{target_name} could be found in project"
      else
        debug "Found target '#{target_name}' at PROJECT:#{ret.source_root.sub(/^#{project.project_root}\//,'')}"
      end
      ret
    end
    
  # IF no targets are specified, then just get all targets in project.
  # If --all option was specified, include those that do not autobuild
  else
    targets = project.targets.values
    unless options.all?
      targets.reject! { |t| !t.config.autobuild? }
    end
  end 

  # If include required was specified, merge in all required bundles as 
  # well.
  if options['include-required']
    targets.each do |target| 
      targets += target.expand_required_targets :theme => true,
       :debug => target.config.load_debug,
       :tests => target.config.load_tests
    end
    
    targets = targets.flatten.uniq.compact
  end
  
  return targets
end

#gen(*arguments) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/sproutcore/tools/gen.rb', line 58

def gen(*arguments)
  return show_help if arguments.empty?
  
  # backwards compatibility case: client is a synonym for 'app'
  name = arguments[0]=='client' ? 'app' : arguments[0]
  
  # Load generator
  generator_project = self.project || SC.builtin_project
  generator = generator_project.generator_for name,
    :arguments   => arguments,
    :filename    => options[:filename],
    :target_name => options[:target],
    :dry_run     => options['dry-run'],
    :force       => options[:force]

  # if no generator could be found, or if we just asked to show help,
  # just return the help...
  return show_help(name, generator) if generator.nil? || options[:help] 
  
  begin
    # Prepare generator and then log some debug info
    generator.prepare!
    info "Loading generator Buildfile at: #{generator.buildfile.loaded_paths.last}"
  
    debug "\nSETTINGS"
    generator.each { |k,v| debug("#{k}: #{v}") }
    
    # Now, run the generator
    generator.build!
    
  rescue Exception => error_message
    warn "For specific help on how to use this generator, type: sc-gen #{name} --help"
    fatal! error_message.to_s
  end

  SC.logger << "\n"
  generator.log_readme
  return 0
end

#info(description) ⇒ Object

Helper method. Call this when you want to log an info message. Logs to the standard logger.



48
49
50
# File 'lib/sproutcore/tools.rb', line 48

def info(description)
  SC.logger.info(description)
end

#init(project_name, app_name = nil) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/sproutcore/tools/init.rb', line 14

def init(project_name, app_name=nil)
  
  # Generate the project
  project_gen = SC.builtin_project.generator_for 'project',
    :arguments => ['project', project_name],
    :dry_run   => options['dry-run'],
    :force     => options[:force]
  project_gen.prepare!.build!
  
  # Next, get the project root & app name
  project_root = project_gen.build_root / project_gen.filename
  app_name = project_gen.filename if app_name.nil?
  
  # And get the app generator and run it
  project = SC::Project.load project_root, :parent => SC.builtin_project
  generator = project.generator_for 'app',
    :arguments => ['app', app_name],
    :dry_run   => options['dry-run'],
    :force     => options[:force]
  generator.prepare!.build!
  
  project_gen.log_file(project_gen.source_root / 'INIT')
  return 0
end

#invoke(*args) ⇒ Object

This is the core entry method used to run every tool. Extend this method with any standard preprocessing you want all tools to do before they do their specific thing.



84
85
86
87
88
89
# File 'lib/sproutcore/tools.rb', line 84

def invoke(*args)
  prepare_logger!
  prepare_mode!
  prepare_build_numbers!
  super
end

#log_file(path) ⇒ Object

Logs the contents of the passed file path to the logger



289
290
291
292
293
294
295
296
# File 'lib/sproutcore/tools.rb', line 289

def log_file(path)
  if !File.exists?(path) 
    warn "Could not display #{File.basename(path)} at #{File.dirname(path)} because it does not exist."
  end
  file_text = File.read(path)
  SC.logger << file_text
  SC.logger << "\n"
end

#manifest(*targets) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/sproutcore/tools/manifest.rb', line 27

def manifest(*targets)

  # Copy some key props to the env
  SC.env.build_prefix   = options.buildroot if options.buildroot
  SC.env.staging_prefix = options.stageroot if options.stageroot
  SC.env.use_symlink    = options.symlink 
  
  # Verify format
  format = (options.format || 'yaml').to_s.downcase.to_sym
  if ![:yaml, :json].include?(format)
    raise "Format must be yaml or json"
  end
  
  # Get allowed keys
  only_keys = nil
  if options[:only]
    only_keys = (options[:only] || '').to_s.split(',')
    only_keys.map! { |k| k.to_sym }
    only_keys = nil if only_keys.size == 0
  end

  except_keys = nil
  if options[:except]
    except_keys = (options[:except] || '').to_s.split(',')
    except_keys.map! { |k| k.to_sym }
    except_keys = nil if except_keys.size == 0
  end
  
  # call core method to actually build the manifests...
  manifests = build_manifests(*targets)

  # now convert them to hashes...
  manifests.map! do |manifest| 
    manifest.to_hash :hidden => options.hidden, 
      :only => only_keys, :except => except_keys
  end
  
  # Serialize'em
  case format
  when :yaml
    output = ["# SproutCore Build Manifest v1.0", manifests.to_yaml].join("\n")
  when :json
    output = mainfests.to_json
  end
  
  # output ...
  if options.output
    file = File.open(options.output, 'w')
    file.write(output)
    file.close
  else
    $stdout << output
  end
  
end

#optionsObject

Make the options hash a HashStruct so that we can access each variable as a method



93
# File 'lib/sproutcore/tools.rb', line 93

def options; @tool_options ||= HashStruct.new(super); end

#prepare_build_numbers!Object

Configure the current build numbers. Handles the --build option.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/sproutcore/tools.rb', line 110

def prepare_build_numbers!
  return if (numbers = options.build).nil?
  numbers = numbers.split(',').map { |n| n.split(':') }
  if numbers.size==1 && numbers.first.size==1
    SC.env.build_number = numbers.first.first
  else
    hash = {}
    numbers.each do |pair|
      key = pair[0]
      key = "/#{key}" if !(key =~ /^\//)
      hash[key.to_sym] = pair[1]
    end
  end
end

#prepare_logger!Object

Configure the expected log level and log target. Handles the --verbose, --very-verbose and --logfile options



97
98
99
100
# File 'lib/sproutcore/tools.rb', line 97

def prepare_logger!
  SC.env.log_level = options['very-verbose'] ? :debug : (options.verbose ? :info : :warn)
  SC.env.logfile = File.expand_path(options.logfile) if options.logfile
end

#prepare_mode!(preferred_mode = 'production') ⇒ Object

Configure the current build mode. Handles the --mode and --environment options. (--environment is provided for backwards compatibility)



104
105
106
107
# File 'lib/sproutcore/tools.rb', line 104

def prepare_mode!(preferred_mode = 'production')
  build_mode = (options.mode || options.environment || preferred_mode).to_s.downcase.to_sym
  SC.build_mode = build_mode
end

#projectObject

The current project. This is discovered based on the passed --project option or based on the current working directory. If no project can be found, this method will always return null.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/sproutcore/tools.rb', line 137

def project
  return @project if @discovered_project # cache - @project may be nil
  @discovered_project = true

  ret = nil
  project_path = options.project || options.library

  # if no project_path is named explicitly, attempt to autodiscover from
  # working dir.  If none is found, just set project to nil
  if project_path.nil?
    debug "No project path specified.  Searching for projects in #{Dir.pwd}"
    ret = SC::Project.load_nearest_project Dir.pwd, :parent => SC.builtin_project
    
  # if project path is specified, look there.  If no project is found 
  # die with a fatal exception.
  else
    debug "Project path specified at #{project_path}"
    ret = SC::Project.load File.expand_path(project_path), :parent => SC.builtin_project
    if ret.nil?
      fatal! "Could not load project at #{project_path}"
    end
  end
  
  info "Loaded project at: #{ret.project_root}" unless ret.nil?
  @project = ret
end

#project=(a_project) ⇒ Object

Set the current project. This is used mostly for unit testing.



130
131
132
# File 'lib/sproutcore/tools.rb', line 130

def project=(a_project)
  @project = a_project
end

#requires_project!Object

Attempts to discover the current project. If no project can be found throws a fatal exception. Use this method at the top of your tool method if you require a project to run.



167
168
169
170
171
172
173
# File 'lib/sproutcore/tools.rb', line 167

def requires_project!
  ret = project
  if ret.nil?
    fatal!("You do not appear to be inside of a project.  Try changing to your project directory or make sure your project as a Buildfile or sc-config")
  end
  return ret 
end

#requires_target!(*targets) ⇒ Object

Requires exactly one target.



244
245
246
# File 'lib/sproutcore/tools.rb', line 244

def requires_target!(*targets)
  requires_targets!(*targets).first
end

#requires_targets!(*target_names) ⇒ Object

Wraps around find_targets but raises an exception if no target is specified.



230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/sproutcore/tools.rb', line 230

def requires_targets!(*target_names)
  if target_names.size == 0
    fatal! "You must specify a target with this command" 
  end

  targets = find_targets(*target_names)
  if targets.size == 0
    fatal! "No targets matching #{target_names * ","} were found."
  end
  
  targets
end

#serverObject



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/sproutcore/tools/server.rb', line 19

def server
  prepare_mode!('debug') # set mode again, using debug as default
  
  SC.env.build_prefix   = options.buildroot if options.buildroot
  SC.env.staging_prefix = options.stageroot if options.stageroot
  
  # get project and start service.
  project = requires_project!
  
  # start shell if passed
  if options.irb
    require 'irb'
    require 'irb/completion'
    if File.exists? ".irbrc"
      ENV['IRBRC'] = ".irbrc"
    end
    
    SC.project = project
    SC.logger << "SproutCore v#{SC::VERSION} Interactive Shell\n"
    SC.logger << "SC.project = #{project.project_root}\n"
    ARGV.clear # do not pass onto IRB
    IRB.start
  else
    SC::Rack::Service.start(options.merge(:project => project))
  end
end

#show_help(generator_name = nil, generator = nil) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/sproutcore/tools/gen.rb', line 31

def show_help(generator_name=nil, generator=nil)
  if generator_name
    if generator.nil?
      warn("There is no #{generator_name} generator") 
    else
      generator.log_usage
    end
  else
    SC.logger << "Available generators:\n"
    SC::Generator.installed_generators_for(project).each do |name|
      SC.logger << "  #{name}\n"
    end
    SC.logger << "Type sc-gen GENERATOR --help for specific usage\n\n"
  end
  return 0
end

#warn(description) ⇒ Object

Log this when you need to issue a warning.



58
59
60
# File 'lib/sproutcore/tools.rb', line 58

def warn(description)
  SC.logger.warn(description)
end