class SC::Tools
The tools module contain the classes that make up the command line tools available from SproutCore
. In general, each command line tool has a peer class hosted in this module that implements the primary user interface.
Internally SproutCore
tools that chain together subtools (such as sc-build) will actually call these classes directly instead of taking the time to instantiate a whole new process.
Each Tool class is implemented as a Thor subclass. You can override methods in these classes in your own ruby code if you want to make a change to how these tools execute. Any ruby you place in your Buildfile
to modify one of these classes will actually be picked up by the tool itself when it runs.
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
Public Class Methods
# File lib/sproutcore/tools.rb, line 29 def self.invoke(task_name) start([task_name.to_s] + ARGV) end
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.
# File lib/sproutcore/tools.rb, line 69 def initialize(*) super prepare_logger! prepare_mode! dont_minify! prepare_app! prepare_build_numbers! end
Fix start so that it treats command-name like command_name
# File lib/sproutcore/tools.rb, line 440 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 exit(1) end end
Private Class Methods
# File lib/sproutcore/tools/manifest.rb, line 80 def self.get_allowed_keys(keys) if keys result = (keys || '').to_s.split(',') result.map!(&:strip).map!(&:to_sym) result = nil if result.size == 0 result end end
Public Instance Methods
# File lib/sproutcore/tools/build.rb, line 48 def build(*targets) if options.help help('build') return end t1 = Time.now SC.logger.info 'Starting build process...' # Copy some key props to the env SC.env.whitelist_name = options.whitelist SC.env.blacklist_name = options.blacklist SC.env.accept_name = options.accept SC.env.build_prefix = options.buildroot if options.buildroot SC.env.staging_prefix = options.stageroot if options.stageroot SC.env.use_symlink = options.symlink # Get entries option entry_filters = nil if options[:entries] entry_filters = options[:entries].split(',') end # We want Chance to clear files like sprites immediately after they're asked for, # because we'll only need them once during a build. Chance.clear_files_immediately # Get the manifests to build manifests = build_manifests(*targets) do |manifest| # This is our own logic to prevent processing a manifest twice next if manifest[:built_by_builder] manifest[:built_by_builder] = true # 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 build_entries_for_manifest manifest, options.allow_commented_js # Build dependencies target = manifest.target required = target.expand_required_targets :theme => true, :debug => target.config.load_debug, :tests => target.config.load_tests, # Modules are not 'required' technically, as they may be loaded # lazily. However, we want to know all targets that should be built, # so we'll include modules as well. :modules => true required.each {|t| m = t.manifest_for(manifest.variation) # And, yes, the same as above. We're just building entries for all required targets. # We're also going to mark them as fully-built so they don't get built again. next if m[:built_by_builder] m[:built_by_builder] = true m.build! build_entries_for_manifest m, options.allow_commented_js } # Clean up manifest.reset! Chance::ChanceFactory.clear_instances end # The HTML5 manifest generator does not work properly; it is unstable (can cause crashes) # and won't pick up modules. We need an alternative implemenation, preferably one that uses # Abbot's own manifests to figure out what files are required. This is non-trivial, however. # # if $to_html5_manifest.length > 0 # $to_html5_manifest.each do |entry| # SC::Helpers::HTML5Manifest.new.build(entry) # end # end SC::Helpers::Minifier.wait t2 = Time.now seconds = t2-t1 minutes = seconds/60 seconds = seconds%60 puts 'Build time: '+minutes.floor.to_s+ ' minutes '+seconds.floor.to_s+' secs' end
Loops over all of the manifest's entries and builds all essential entries. Entries such as javascript.js are considered non-essential because they will not actually be used in a fully built app (except for modules).
It also does a check to ensure that all JS being written is minified.
# File lib/sproutcore/tools.rb, line 390 def build_entries_for_manifest(manifest, allow_comments) if manifest.entries.size > 0 target = manifest.target info "Building entries for #{target.target_name}:#{manifest.language}..." # we ONLY generate non-packed javascript.js files for modules, which may need # to lazily load them. Otherwise: NOPE! generate_javascript = false if target[:target_type] === :module generate_javascript = true end target_build_root = Pathname.new(manifest.target.project.project_root) manifest.entries.each do |entry| # Skip combined JS when it is a target that doesn't need it. # We can't just skip all non-packed JS, because you can use sc_resource to split # the JS out, and that won't work properly. :combine, likewise, is used for sc_resource. # So, we skip the entry IF it is javascript.js--because that is how the pack task itself # knows the difference. next if not generate_javascript and entry[:filename] == 'javascript.js' # For security, skip AND WARN about files which are not minified if not SC.env[:dont_minify] and not allow_comments and entry[:entry_type] == :javascript and not entry[:minified] SC.logger.fatal "SECURITY: Entry not minified: #{entry[:filename]}; target: #{target[:target_name]}" SC.logger.fatal "All entries must be minified in a final build UNLESS --allow-commented-js argument is supplied." exit(1) end dst = Pathname.new(entry.build_path).relative_path_from(target_build_root) debug " #{entry.filename} -> #{dst}" entry.build! end end end
Core method to process command line options and then build a manifest. Shared by sc-manifest, sc-build and sc-docs commands.
# File lib/sproutcore/tools.rb, line 376 def build_manifests(*targets) # Build'em each_manifest_for_targets(*targets) do |manifest| manifest.build! yield manifest end end
# File lib/sproutcore/tools/build_number.rb, line 17 def build_number(*targets) target = requires_target!(*targets) $stdout << target.prepare![:build_number] end
Helper method. Call this when you want to log a debug message.
# File lib/sproutcore/tools.rb, line 105 def debug(description) SC.logger.debug(description) end
# File lib/sproutcore/tools/docs.rb, line 13 def docs puts "Doc generation is no longer bundled with SproutCore. Please install the sc-docs tool instead." end
# File lib/sproutcore/tools.rb, line 145 def dont_minify! SC.env[:dont_minify] = options[:'dont-minify'] end
# File lib/sproutcore/tools.rb, line 343 def each_manifest_for_targets(*targets) # setup build numbers find_build_numbers(*targets) requires_project! # get project targets = find_targets(*targets) # get targets # log output SC.logger << "Building targets: #{targets.map { |t| t.target_name } * ","}\n" languages = find_languages(*targets) # get languages SC.logger << "Building languages: #{ languages * "," }\n" index = 1 count = targets.length * languages.length SC.logger.info "Total target/language combinations to build: #{count}" targets.each {|target| languages.each {|l| manifest = target.manifest_for :language => l SC.logger.info "Creating manifest #{index} of #{count} for: #{manifest.target.target_name}:#{manifest.language}" yield manifest index += 1 } } end
Helper method. Call this when an acception occurs that is fatal due to a problem with the user.
# File lib/sproutcore/tools.rb, line 94 def fatal!(description) raise FatalException, description end
Discovers build numbers requested for the build and sets them in the in the env if needed.
# File lib/sproutcore/tools.rb, line 328 def find_build_numbers(*targets) if options['build-numbers'] numbers = {} options['build-numbers'].split(',').each do |pair| pair = pair.split(':') if pair.length < 2 fatal! "Could not parse build numbers! #{options['build-numbers']}" end numbers["/#{pair[0]}"] = pair[1] end SC.env.build_numbers = numbers SC.logger.info "Using build numbers: #{numbers.map { |k,v| "#{k}: #{v}" }.join(',')}" end end
Discovers the languages requested by the user for a build. Uses the –languages command line option or disovers in targets.
# File lib/sproutcore/tools.rb, line 315 def find_languages(*targets) # Use passed languages. If none are specified, merge installed # languages for all app targets. unless (languages = options.languages) languages = targets.map { |t| t.installed_languages } else languages = languages.split(',').map { |l| l.to_sym } end languages.flatten.uniq.compact end
Find one or more targets with the passed target names in the current project. Requires a project to function.
# File lib/sproutcore/tools.rb, line 236 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:#{project.project_root}" 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 appnames = SC.env[:build_targets] # if it has the appname argument only build the target with the appname if appnames.size > 0 tar = [] targets.each do |target| appnames.each do |appname| if target.target_name.to_s.eql? '/'+appname tar << target end end end targets = tar end targets end
# File lib/sproutcore/tools/gen.rb, line 60 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] # The --statechart switch uses a different app or project generator if name == 'app' && options[:statechart] name = 'statechart_app' end # 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], :statechart => options[:statechart] # 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
Helper method. Call this when you want to log an info message. Logs to the standard logger.
# File lib/sproutcore/tools.rb, line 100 def info(description) SC.logger.info(description) end
# 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 # Use the statechart_app generator if --statechart is provided app_type = options[:statechart] ? 'statechart_app' : 'app' generator = project.generator_for app_type, :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
Logs the contents of the passed file path to the logger
# File lib/sproutcore/tools.rb, line 426 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
# File lib/sproutcore/tools/manifest.rb, line 34 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 = Tools.get_allowed_keys(options[:only]) except_keys = Tools.get_allowed_keys(options[:except]) # 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
Make the options hash a HashStruct
so that we can access each variable as a method
# File lib/sproutcore/tools.rb, line 120 def options; @tool_options ||= HashStruct.new(super); end
# File lib/sproutcore/tools/phantom.rb, line 16 def phantom(*args) result = false target = find_targets('/sproutcore').first if target test_runner_path = File.expand_path(File.join('phantomjs', 'test_runner.js'), target.source_root) if File.file? test_runner_path result = system "phantomjs #{test_runner_path} #{args.join(' ')}" else SC.logger.fatal "Could not find PhantomJS test runner" end else SC.logger.fatal "Could not find /sproutcore target" end exit(1) unless result end
# File lib/sproutcore/tools.rb, line 137 def prepare_app! if options[:'build-targets'] SC.env[:build_targets] = options[:'build-targets'].split(',') else SC.env[:build_targets] = '' end end
Configure the current build numbers. Handles the –build option.
# File lib/sproutcore/tools.rb, line 150 def prepare_build_numbers! return unless (numbers = options[:build]) 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
Configure the expected log level and log target. Handles the –verbose, –very-verbose and –logfile options
# File lib/sproutcore/tools.rb, line 124 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
Configure the current build mode. Handles the –mode and –environment options. (–environment is provided for backwards compatibility)
# File lib/sproutcore/tools.rb, line 132 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
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.
# File lib/sproutcore/tools.rb, line 182 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 unless project_path 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 # This is the root project, so we must also load the "include targets" used to # make additional frameworks available to SC apps. SC.include_targets.each {|target| target_path = File.expand_path target[:path] target_name = File.join "/", target[:name] # Note: target names must begin with / to be valid. t = ret.add_target target_name, :framework, { :source_root => target_path } if t.config[:allow_nested_targets] ret.find_targets_for(target_path, target_name, t.config) end } info "Loaded project at: #{ret.project_root}" unless ret.nil? @project = ret end
Set the current project. This is used mostly for unit testing.
# File lib/sproutcore/tools.rb, line 170 def project=(a_project) @project = a_project end
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.
# File lib/sproutcore/tools.rb, line 226 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 exactly one target.
# File lib/sproutcore/tools.rb, line 309 def requires_target!(*targets) requires_targets!(*targets).first end
Wraps around find_targets
but raises an exception if no target is specified.
# File lib/sproutcore/tools.rb, line 295 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
# File lib/sproutcore/tools/server.rb, line 38 def server if options.help help('server') return end 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] SC.env[:whitelist_name] = options.whitelist SC.env[:blacklist_name] = options.blacklist SC.env[:accept_name] = options.accept # 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.logger << "SproutCore v#{SC::VERSION} Development Server\n" begin SC::Rack::Service.start(options.merge(:project => project)) rescue => e if e.message =~ /no acceptor/ raise "No acceptor. Most likely the port is already in use. Try using --port to specify a different port." else raise e end end end end
# File lib/sproutcore/tools.rb, line 174 def set_test_project(a_project) @project = a_project @discovered_project = true end
# File lib/sproutcore/tools/gen.rb, line 32 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
# File lib/sproutcore/tools.rb, line 62 def version puts "SproutCore #{SC::VERSION}" end
Log this when you need to issue a warning.
# File lib/sproutcore/tools.rb, line 110 def warn(description) SC.logger.warn(description) end