class Gena::Application

Public Class Methods

check_dependencies(plugin_name, dependencies) click to toggle source
# File lib/cli/cli.rb, line 136
def self.check_dependencies(plugin_name, dependencies)

  missing_gems = {}

  dependencies.each do |gem_name, version|
    begin
      require gem_name
    rescue LoadError
      missing_gems[gem_name] = version
    end
  end

  if missing_gems.count > 0
    puts "The following gems required for #{plugin_name.yellow}:\n\t#{missing_gems.keys.join(', ')}"
    puts "Would you like to install them? (Yn)"
    answer = STDIN.gets.chomp
    if answer == 'Y' || answer == 'y' || answer == '' || answer == 'yes'
      gems = missing_gems.collect { |k, v| "#{k}:#{v}" }
      command = "gem install #{gems.join(" ")}"
      puts command
      result = system(command)
      if result
        puts "All dependencies installed successfully! Run your command again.".green
      else
        puts "Error occured while installing dependencies. Please install them manually and try again.".red
      end
      return false
    else
      puts "Unable to run #{plugin_name}. Please install dependencies and try again.".red
      return false
    end
  else
    return true
  end
end
class_for_command() click to toggle source

class_for_command - hash to store custom classes (actually Plugin subclasses) for each command registered with gena

# File lib/cli/cli.rb, line 11
def class_for_command
  @class_for_command ||= Hash.new
end
class_for_command=(commands) click to toggle source
# File lib/cli/cli.rb, line 15
def class_for_command=(commands)
  @class_for_command = commands
end
finish() click to toggle source
# File lib/cli/cli.rb, line 93
def finish
  XcodeUtils.shared.save_project
  Application.new.check_gena_version
end
help(shell, subcommand = false) click to toggle source

Override help to forward

# File lib/cli/cli.rb, line 25
def help(shell, subcommand = false)

  #List plugin commands separately from Gena general commands
  plugins = []
  class_for_command.each do |command, klass|
    plugins += klass.printable_commands(false)
  end

  plugins.uniq!

  list = printable_commands(true, subcommand)
  Thor::Util.thor_classes_in(self).each do |klass|
    list += klass.printable_commands(false)
  end

  list -= plugins

  # Remove this line to disable alphabetical sorting
  # list.sort! { |a, b| a[0] <=> b[0] }

  # Add this line to remove the help-command itself from the output
  # list.reject! {|l| l[0].split[1] == 'help'}

  if defined?(@package_name) && @package_name
    shell.say "#{@package_name} commands:"
  else
    shell.say "General commands:"
  end

  shell.print_table(list, :indent => 2, :truncate => true)
  shell.say
  class_options_help(shell)

  shell.say "Plugins:"
  shell.print_table(plugins, :indent => 2, :truncate => true)
end
plugin_classes() click to toggle source
# File lib/cli/cli.rb, line 19
def plugin_classes
  class_for_command.values.uniq
end
start(given_args = ARGV, config = {}) click to toggle source

Override start to do custom dispatch (looking for plugin for unknown command)

# File lib/cli/cli.rb, line 65
def start(given_args = ARGV, config = {})

  config[:shell] ||= Thor::Base.shell.new

  command_name = normalize_command_name(retrieve_command_name(given_args.dup))
  clazz = command_name ? class_for_command[command_name] : nil

  if command_name && clazz
    if check_dependencies(command_name, clazz.dependencies)
      clazz.dispatch(nil, given_args.dup, nil, config)
    end
  else
    dispatch(nil, given_args.dup, nil, config)
  end

  finish

rescue Thor::Error => e
  config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
  exit(1) if exit_on_failure?
rescue Errno::EPIPE
  # This happens if a thor command is piped to something like `head`,
  # which closes the pipe when it's done reading. This will also
  # mean that if the pipe is closed, further unnecessary
  # computation will not occur.
  exit(0)
end

Public Instance Methods

ask_with_default(message, default) click to toggle source
# File lib/cli/init.rb, line 267
def ask_with_default(message, default)
  value = ask(message)
  if value.empty?
    value = default
  end
  value
end
check() click to toggle source
# File lib/cli/init.rb, line 107
def check

  $verbose = (ARGV.include? '-v')
  if $verbose
    ARGV.reject! { |n| n == '-v' || n == '--verbose' }
  end

  unless Config.exists?
    if ARGV == ['init']
      init
      ARGV.replace([])
    else
      unless no? "'gena.plist' is not exists. Do you want to create new one? (Y/n)", Color::YELLOW
        init
      end
    end
  end

  $config = Gena::Config.new
  $config.load_plist_config

  unless $config.data
    say '\'gena.plist\' is corrupted! Try recreating', Color::RED
    abort
  end

  if !$config.data['plugins_url'] || $config.data['plugins_url'].count == 0
    say "'plugins_url' key is missing inside 'gena.plist'", Color::RED
    abort
  end
  download_plugins
  load_plugins
  save_plugin_configs
end
check_gena_version() click to toggle source
# File lib/cli/cli.rb, line 104
def check_gena_version
  # Read cache
  say "Checking for update.." if $verbose

  version_path = File.expand_path "#{GENA_HOME}/version.plist"
  plist = File.exists?(version_path) ? Plist::parse_xml(version_path) : Hash.new

  data = nil

  if !plist['timestamp'] || (DateTime.now.to_time.to_i - plist['timestamp'].to_i) > GENA_UPDATE_CHECK_INTERVAL
    last_release_text = `curl https://api.github.com/repos/alexgarbarev/gena/releases/latest -s`
    data = JSON.parse(last_release_text)

    plist['data'] = data
    plist['timestamp'] = DateTime.now.to_time.to_i
    File.open(version_path, 'w') { |f| f.write plist.to_plist }
  else
    data = plist['data']
  end

  tag_name = data['tag_name']

  if tag_name > VERSION
    say "New update v#{tag_name} is available for gena.\nSee release notes: #{data['url']}", Color::YELLOW
    if data['body'].length > 0
      say "---------------------\n#{data['body']}\n---------------------", Color::YELLOW
    end
    say "Please update by:\n#{set_color('gem install gena', Color::GREEN)}", Color::YELLOW
  end

end
common_path_in_target(target, except_match) click to toggle source
# File lib/cli/init.rb, line 286
def common_path_in_target(target, except_match)
  common = ''
  target.source_build_phase.files.each do |file|
    unless file.file_ref.real_path.to_s[except_match]
      path_components = file.file_ref.real_path.to_s #.split('/')
      if common.empty?
        common = path_components
      else
        common = common.path_intersection path_components
      end
    end
  end

  if File.file? common
    common = common.delete_last_path_component
  end
  if common[-1] == '/'
    common = common[0..-2]
  end
  common
end
download_path_for(url) click to toggle source
# File lib/cli/init.rb, line 194
def download_path_for(url)
  hash = Digest::MD5.hexdigest url
  File.expand_path "#{GENA_HOME}/plugins/#{hash}"
end
download_plugin_at(url) click to toggle source
# File lib/cli/init.rb, line 199
def download_plugin_at(url)
  say "Downloading plugin from '#{url}'..", Color::GREEN, ' '
  output_path = File.expand_path(download_path_for(url))
  FileUtils.rmtree output_path
  FileUtils.mkdir_p output_path
  result = gena_system "git clone --depth 1 #{url} #{output_path}"
  if result
    say 'success!', Color::GREEN
    @downloaded_urls[key_for_url(url)] = {
        'hash' => `git ls-remote #{url} refs/heads/master | cut -f 1`.strip,
        'timestamp' => DateTime.now.to_time.to_i
    }

  else
    say 'Failed! Run with \'-v\' to debug', Color::RED
  end
end
download_plugins() click to toggle source
# File lib/cli/init.rb, line 142
def download_plugins
  load_downloaded_urls
  $config.data['plugins_url'].each do |plugin_url|
    if remote_url? plugin_url
      is_old = old_url?(plugin_url)
      if !downloaded_url?(plugin_url) || is_old
        message = is_old ? 'not up to date' : 'not downloaded yet'
        say "Plugin at '#{plugin_url}' #{message}", Color::YELLOW
        download_plugin_at plugin_url
      end
    end
  end
  save_downloaded_urls
end
downloaded_url?(url) click to toggle source
# File lib/cli/init.rb, line 161
def downloaded_url?(url)
  File.exists? File.expand_path(download_path_for(url))
end
file_exists_in_target?(target, file_name) click to toggle source
# File lib/cli/init.rb, line 308
def file_exists_in_target?(target, file_name)
  target.source_build_phase.files.each do |file|
    if File.basename(file.file_ref.real_path.to_s) == file_name
      return true
    end
  end
  false
end
init() click to toggle source
# File lib/cli/init.rb, line 28
def init

  xcode_project_name = `find . -name *.xcodeproj`
  xcode_project_name.strip!

  xcode_project_name = ask_with_default("Enter path for #{set_color('project', Color::YELLOW)} or ENTER to continue (#{xcode_project_name}):", xcode_project_name)

  xcode_project = Xcodeproj::Project.open(xcode_project_name)


  main_target = nil
  test_target = nil
  xcode_project.native_targets.each do |target|
    if target.product_type == 'com.apple.product-type.application'
      main_target = target
    elsif target.product_type == 'com.apple.product-type.bundle.unit-test'
      test_target = target
    end
  end

  hash = Hash.new
  hash[:plugins_url] = [
      'https://github.com/alexgarbarev/gena-plugins.git'
  ]

  unless main_target
    say "Can't find application target in your Xcode project. Please create application target and try again", Color::RED
    abort
  end

  default_build_configuration = main_target.build_configuration_list.default_configuration_name || 'Debug'
  info_plist_value = main_target.build_configuration_list.get_setting('INFOPLIST_FILE')[default_build_configuration]
  if info_plist_value['$(SRCROOT)/']
    info_plist_value['$(SRCROOT)/'] = ''
  end

  hash['company'] = xcode_project.root_object.attributes['ORGANIZATIONNAME'].to_s
  hash['prefix'] = xcode_project.root_object.attributes['CLASSPREFIX'].to_s
  hash['project_name'] = xcode_project.root_object.name
  hash['info_plist'] = info_plist_value

  if main_target
    sources_path = common_path_in_target(main_target, 'main.m')
    sources_path = relative_to_current_dir(sources_path)

    hash['project_target'] = main_target.name
    hash['sources_dir'] = ask_with_default("Enter path for #{set_color('sources', Color::YELLOW)} or ENTER to continue (#{sources_path}):", sources_path)
 end

  if test_target
    tests_path = common_path_in_target(test_target, "#{sources_path}/")
    tests_path = relative_to_current_dir(tests_path)

    hash['test_target'] = test_target.name
    hash['tests_dir'] = ask_with_default("Enter path for #{set_color('tests', Color::YELLOW)} or ENTER to continue (#{tests_path}):", tests_path)
  else
    say "Can't find target for UnitTests. You setup it later using 'test_target' and 'tests_dir' key (similar to sources)", Color::YELLOW
    hash['test_target'] = ''
    hash['tests_dir'] = ''
  end

  language = file_exists_in_target?(main_target, 'main.m') ? "objc" : "swift"
  hash['language'] = ask_with_default("Enter main #{set_color('language', Color::YELLOW)} or ENTER to continue (#{language}):", language)

  say '===============================================================', Color::YELLOW
  print_table(hash)
  say '===============================================================', Color::YELLOW

  File.open('gena.plist', 'w') { |f| f.write hash.to_plist }


  say "'gena.plist' created..", Color::GREEN
end
key_for_url(url) click to toggle source
# File lib/cli/init.rb, line 190
def key_for_url(url)
  Digest::MD5.hexdigest url
end
load_downloaded_urls() click to toggle source
# File lib/cli/init.rb, line 177
def load_downloaded_urls
  plist_path = File.expand_path "#{GENA_HOME}/plugins.plist"
  if File.exists? plist_path
    @downloaded_urls = Plist::parse_xml(plist_path)
  else
    @downloaded_urls = Hash.new
  end
end
load_plugins() click to toggle source
# File lib/cli/init.rb, line 217
def load_plugins
  registered = []
  $config.data['plugins_url'].each do |plugin_url|
    path = remote_url?(plugin_url) ? download_path_for(plugin_url) : plugin_url
    Dir["#{File.expand_path(path)}/**/*.rb"].each do |file|
      template_name = file.split(File::SEPARATOR)[-2]
      unless registered.include? template_name
        registered << template_name
        say "Loading '#{file}'..", Color::YELLOW if $verbose
        require file
      end
    end
  end
  Gena::Plugin.descendants.each do |clazz|
    clazz.setup_thor_commands
  end
end
old_url?(url) click to toggle source
# File lib/cli/init.rb, line 165
def old_url?(url)
  entity = @downloaded_urls[key_for_url(url)]
  return true unless entity
  elapsed_seconds = DateTime.now.to_time.to_i - entity['timestamp'].to_i
  if elapsed_seconds > GENA_UPDATE_CHECK_INTERVAL
    current_hash = `git ls-remote #{url} refs/heads/master | cut -f 1`.strip
    entity['timestamp'] = DateTime.now.to_time.to_i
    return current_hash != entity['hash']
  end
  false
end
reconfigure(command) click to toggle source
# File lib/cli/init.rb, line 6
def reconfigure(command)

  $config = Gena::Config.new
  $config.load_plist_config

  data = $config.data[GENA_PLUGINS_CONFIG_KEY] || Hash.new

  Application.plugin_classes.each do |klass|

    plugin_config_name = klass.plugin_config_name
    if plugin_config_name == command
      say "Setting defaults for '#{command}'..", Color::GREEN
      data[plugin_config_name] = klass.plugin_config_defaults
      $config.data[GENA_PLUGINS_CONFIG_KEY] = data
      $config.save!
      return
    end
  end
  say "Can't find plugin with name '#{command}'", Color::RED
end
relative_to_current_dir(path) click to toggle source
# File lib/cli/init.rb, line 275
def relative_to_current_dir(path)

  if path.empty? || !path["#{Dir.pwd}/"]
    path
  else
    result = path.dup
    result["#{Dir.pwd}/"] = ''
    result
  end
end
remote_url?(url) click to toggle source
# File lib/cli/init.rb, line 157
def remote_url?(url)
  url =~ /(.+@)*([\w\d\.]+):(.*)/
end
save_downloaded_urls() click to toggle source
# File lib/cli/init.rb, line 186
def save_downloaded_urls
  File.open(File.expand_path("#{GENA_HOME}/plugins.plist"), 'w') { |f| f.write @downloaded_urls.to_plist }
end
save_plugin_configs() click to toggle source
# File lib/cli/init.rb, line 235
def save_plugin_configs

  data = $config.data[GENA_PLUGINS_CONFIG_KEY] || Hash.new

  Application.plugin_classes.each do |klass|

    defaults = klass.plugin_config_defaults

    if defaults.count > 0
      plugin_config_name = klass.plugin_config_name

      if !data[plugin_config_name] || data[plugin_config_name].count == 0
        say "Writing config defaults for plugin '#{plugin_config_name}'..", Color::GREEN
        data[plugin_config_name] = defaults
      elsif !data[plugin_config_name].keys.to_set.eql? defaults.keys.to_set
        missing_keys = defaults.keys - data[plugin_config_name].keys
        if missing_keys.count > 0
          say "Adding missing config keys #{missing_keys} for plugin '#{plugin_config_name}.'", Color::GREEN
          missing_keys.each { |key| data[plugin_config_name][key] = defaults[key] }
        end
      end
    end

  end

  $config.data[GENA_PLUGINS_CONFIG_KEY] = data
  $config.save!

end