class Pantograph::PantFile

Constants

SharedValues

Attributes

current_platform[RW]

the platform in which we're currently in when parsing the Pantfile This is used to identify the platform in which the lane is in

runner[RW]

Stores all relevant information from the currently running process

Public Class Methods

new(path = nil) click to toggle source

@return The runner which can be executed to trigger the given actions

# File pantograph/lib/pantograph/pant_file.rb, line 15
def initialize(path = nil)
  return unless (path || '').length > 0
  UI.user_error!("Could not find Pantfile at path '#{path}'") unless File.exist?(path)
  @path = File.expand_path(path)
  content = File.read(path, encoding: "utf-8")

  # From https://github.com/orta/danger/blob/master/lib/danger/Dangerfile.rb
  if content.tr!('“”‘’‛', %(""'''))
    UI.error("Your #{File.basename(path)} has had smart quotes sanitised. " \
            'To avoid issues in the future, you should not use ' \
            'TextEdit for editing it. If you are not using TextEdit, ' \
            'you should turn off smart quotes in your editor of choice.')
  end

  content.scan(/^\s*require ["'](.*?)["']/).each do |current|
    gem_name = current.last
    next if gem_name.include?(".") # these are local gems

    begin
      require(gem_name)
    rescue LoadError
      UI.important("You have required a gem, if this is a third party gem, please use `pantograph_require '#{gem_name}'` to ensure the gem is installed locally.")
    end
  end

  parse(content, @path)
end
sh(*command, log: true, error_callback: nil, &b) click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 215
def self.sh(*command, log: true, error_callback: nil, &b)
  command_header = log ? Actions.shell_command_from_args(*command) : "shell command"
  Actions.execute_action(command_header) do
    Actions.sh_no_action(*command, log: log, error_callback: error_callback, &b)
  end
end

Public Instance Methods

action_completed(action_name, status: nil) click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 388
def action_completed(action_name, status: nil)
  completion_context = PantographCore::ActionCompletionContext.context_for_action_name(action_name,
                                                                                       args: ARGV,
                                                                                       status: status)
  PantographCore.session.action_completed(completion_context: completion_context)
end
action_launched(action_name) click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 381
def action_launched(action_name)
  action_launch_context = PantographCore::ActionLaunchContext.context_for_action_name(action_name,
                                                                                      pantograph_client_language: :ruby,
                                                                                      args: ARGV)
  PantographCore.session.action_launched(launch_context: action_launch_context)
end
actions_path(path) click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 183
def actions_path(path)
  UI.crash!("Path '#{path}' not found!") unless File.directory?(path)

  Actions.load_external_actions(path)
end
after_all(&block) click to toggle source

Is executed after each test run

# File pantograph/lib/pantograph/pant_file.rb, line 143
def after_all(&block)
  @runner.set_after_all(@current_platform, block)
end
after_each(&block) click to toggle source

Is executed before each lane

# File pantograph/lib/pantograph/pant_file.rb, line 148
def after_each(&block)
  @runner.set_after_each(@current_platform, block)
end
before_all(&block) click to toggle source

Is executed before each test run

# File pantograph/lib/pantograph/pant_file.rb, line 133
def before_all(&block)
  @runner.set_before_all(@current_platform, block)
end
before_each(&block) click to toggle source

Is executed before each lane

# File pantograph/lib/pantograph/pant_file.rb, line 138
def before_each(&block)
  @runner.set_before_each(@current_platform, block)
end
desc(string) click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 222
def desc(string)
  desc_collection << string
end
desc_collection() click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 226
def desc_collection
  @desc_collection ||= []
end
error(&block) click to toggle source

Is executed if an error occurred during pantograph execution

# File pantograph/lib/pantograph/pant_file.rb, line 153
def error(&block)
  @runner.set_error(@current_platform, block)
end
fetch_remote_tags(folder: nil) click to toggle source

@!group Versioning helpers

# File pantograph/lib/pantograph/pant_file.rb, line 337
def fetch_remote_tags(folder: nil)
  UI.message("Fetching remote git tags...")
  Helper.with_env_values('GIT_TERMINAL_PROMPT' => '0') do
    Actions.sh("cd #{folder.shellescape} && git fetch --all --tags -q")
  end

  # Fetch all possible tags
  git_tags_string = Actions.sh("cd #{folder.shellescape} && git tag -l")
  git_tags = git_tags_string.split("\n")

  # Sort tags based on their version number
  return git_tags
         .select { |tag| PantographCore::TagVersion.correct?(tag) }
         .sort_by { |tag| PantographCore::TagVersion.new(tag) }
end
generated_pantfile_id(id) click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 234
def generated_pantfile_id(id)
  UI.important("The `generated_pantfile_id` action was deprecated, you can remove the line from your `Pantfile`")
end
import(path = nil) click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 238
def import(path = nil)
  if path.nil?
    UI.user_error!("Please pass a path to the `import` action")
  end

  path = path.dup.gsub("~", Dir.home)
  unless Pathname.new(path).absolute? # unless an absolute path
    path = File.join(File.expand_path('..', @path), path)
  end

  unless File.exist?(path)
    UI.user_error!("Could not find Pantfile at path '#{path}'")
  end

  # First check if there are local actions to import in the same directory as the Pantfile
  actions_path = File.join(File.expand_path("..", path), 'actions')
  Pantograph::Actions.load_external_actions(actions_path) if File.directory?(actions_path)

  action_launched('import')

  return_value = parse(File.read(path), path)

  action_completed('import', status: PantographCore::ActionCompletionStatus::SUCCESS)

  return return_value
end
import_from_git(url: nil, branch: 'master', path: 'pantograph/Pantfile', dependencies: [], version: nil) click to toggle source

@param url [String] The git URL to clone the repository from @param branch [String] The branch to checkout in the repository @param path [String] The path to the Pantfile @param version [String, Array] Version requirement for repo tags

# File pantograph/lib/pantograph/pant_file.rb, line 269
def import_from_git(url: nil, branch: 'master', path: 'pantograph/Pantfile', dependencies: [], version: nil)
  if url.to_s.length == 0
    UI.user_error!("Please pass the git url to the `import_from_git` action")
  end

  Actions.execute_action('import_from_git') do
    require 'tmpdir'

    action_launched('import_from_git')

    # Checkout the repo
    repo_name      = url.split('/').last
    checkout_param = branch

    Dir.mktmpdir('pant_clone') do |tmp_path|
      clone_folder = File.join(tmp_path, repo_name)

      checkout_dependencies = dependencies.map(&:shellescape).join(" ")
      checkout_path         = "#{path.shellescape} #{checkout_dependencies}"

      UI.message('Cloning remote git repo...')
      Helper.with_env_values('GIT_TERMINAL_PROMPT' => '0') do
        Actions.sh("git clone #{url.shellescape} #{clone_folder.shellescape} --depth 1 -n --branch #{branch}")
      end

      unless version.nil?
        req = Gem::Requirement.new(version)
        all_tags = fetch_remote_tags(folder: clone_folder)
        checkout_param = all_tags.select { |t| req =~ PantographCore::TagVersion.new(t) }.last
        UI.user_error!("No tag found matching #{version.inspect}") if checkout_param.nil?
      end

      Actions.sh("cd #{clone_folder.shellescape} && git checkout #{checkout_param.shellescape} #{checkout_path}")

      # We also want to check out all the local actions of this pantograph setup
      containing     = path.split(File::SEPARATOR)[0..-2]
      containing     = "." if containing.count == 0
      actions_folder = File.join(containing, "actions")

      begin
        Actions.sh("cd #{clone_folder.shellescape} && git checkout #{checkout_param.shellescape} #{actions_folder.shellescape}")
      rescue
        # We don't care about a failure here, as local actions are optional
      end

      return_value = nil

      if dependencies.any?
        return_value = [import(File.join(clone_folder, path))]
        return_value += dependencies.map { |file_path| import(File.join(clone_folder, file_path)) }
      else
        return_value = import(File.join(clone_folder, path))
      end

      action_completed(
        'import_from_git',
        status: PantographCore::ActionCompletionStatus::SUCCESS
      )

      return return_value
    end
  end
end
is_platform_block?(key) click to toggle source

Is the given key a platform block or a lane?

# File pantograph/lib/pantograph/pant_file.rb, line 167
def is_platform_block?(key)
  UI.crash!('No key given') unless key

  return false if self.runner.lanes.fetch(nil, {}).fetch(key.to_sym, nil)
  return true if self.runner.lanes[key.to_sym].kind_of?(Hash)

  if key.to_sym == :update
    # The user ran `pantograph update`, instead of `pantograph update_pantograph`
    # We're gonna be nice and understand what the user is trying to do
    require 'pantograph/one_off'
    Pantograph::OneOff.run(action: "update_pantograph", parameters: {})
  else
    UI.user_error!("Could not find '#{key}'. Available lanes: #{self.runner.available_lanes.join(', ')}")
  end
end
lane(lane_name, &block) click to toggle source

User defines a new lane

# File pantograph/lib/pantograph/pant_file.rb, line 83
def lane(lane_name, &block)
  UI.user_error!("You have to pass a block using 'do' for lane '#{lane_name}'. Make sure you read the docs on GitHub.") unless block

  self.runner.add_lane(Lane.new(platform: self.current_platform,
                                   block: block,
                             description: desc_collection,
                                    name: lane_name,
                              is_private: false))

  @desc_collection = nil # reset the collected description again for the next lane
end
method_missing(method_sym, *arguments, &_block) click to toggle source

Is used to look if the method is implemented as an action

# File pantograph/lib/pantograph/pant_file.rb, line 158
def method_missing(method_sym, *arguments, &_block)
  self.runner.trigger_action_by_name(method_sym, nil, false, *arguments)
end
override_lane(lane_name, &block) click to toggle source

User defines a lane that can overwrite existing lanes. Useful when importing a Pantfile

# File pantograph/lib/pantograph/pant_file.rb, line 109
def override_lane(lane_name, &block)
  UI.user_error!("You have to pass a block using 'do' for lane '#{lane_name}'. Make sure you read the docs on GitHub.") unless block

  self.runner.add_lane(Lane.new(platform: self.current_platform,
                                   block: block,
                             description: desc_collection,
                                    name: lane_name,
                              is_private: false), true)

  @desc_collection = nil # reset the collected description again for the next lane
end
pantograph_require(gem_name) click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 230
def pantograph_require(gem_name)
  PantographRequire.install_gem_if_needed(gem_name: gem_name, require_gem: true)
end
parse(data, path = nil) click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 47
def parse(data, path = nil)
  @runner ||= Runner.new

  Dir.chdir(PantographCore::PantographFolder.path || Dir.pwd) do # context: pantograph subfolder
    # create nice path that we want to print in case of some problem
    relative_path = path.nil? ? '(eval)' : Pathname.new(path).relative_path_from(Pathname.new(Dir.pwd)).to_s

    begin
      # We have to use #get_binding method, because some test files defines method called `path` (for example SwitcherPantfile)
      # and local variable has higher priority, so it causes to remove content of original Pantfile for example. With #get_binding
      # is this always clear and safe to declare any local variables we want, because the eval function uses the instance scope
      # instead of local.

      # rubocop:disable Security/Eval
      eval(data, parsing_binding, relative_path) # using eval is ok for this case
      # rubocop:enable Security/Eval
    rescue SyntaxError => ex
      match = ex.to_s.match(/#{Regexp.escape(relative_path)}:(\d+)/)
      if match
        line = match[1]
        UI.content_error(data, line)
        UI.user_error!("Syntax error in your Pantfile on line #{line}: #{ex}")
      else
        UI.user_error!("Syntax error in your Pantfile: #{ex}")
      end
    end
  end

  self
end
parsing_binding() click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 43
def parsing_binding
  binding
end
platform(platform_name) { || ... } click to toggle source

User defines a platform block

# File pantograph/lib/pantograph/pant_file.rb, line 122
def platform(platform_name)
  SupportedPlatforms.verify!(platform_name)

  self.current_platform = platform_name

  yield

  self.current_platform = nil
end
private_lane(lane_name, &block) click to toggle source

User defines a new private lane, which can't be called from the CLI

# File pantograph/lib/pantograph/pant_file.rb, line 96
def private_lane(lane_name, &block)
  UI.user_error!("You have to pass a block using 'do' for lane '#{lane_name}'. Make sure you read the docs on GitHub.") unless block

  self.runner.add_lane(Lane.new(platform: self.current_platform,
                                   block: block,
                             description: desc_collection,
                                    name: lane_name,
                              is_private: true))

  @desc_collection = nil # reset the collected description again for the next lane
end
puts(value) { || ... } click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 366
def puts(value)
  # Overwrite this, since there is already a 'puts' method defined in the Ruby standard library
  value ||= yield if block_given?

  action_launched('puts')
  return_value = Pantograph::Actions::PutsAction.run([value])
  action_completed('puts', status: PantographCore::ActionCompletionStatus::SUCCESS)
  return return_value
end
say(value) { || ... } click to toggle source

Speak out loud

# File pantograph/lib/pantograph/pant_file.rb, line 358
def say(value)
  # Overwrite this, since there is already a 'say' method defined in the Ruby standard library
  value ||= yield

  value = { text: value } if value.kind_of?(String) || value.kind_of?(Array)
  self.runner.trigger_action_by_name(:say, nil, false, value)
end
sh(*args, &b) click to toggle source

Execute shell command Accepts arguments with with and without the command named keyword so that sh behaves like other actions with named keywords github.com/urbanquakers/pantograph/issues/14930

Example:

sh("ls")
sh("ls", log: false)
sh(command: "ls")
sh(command: "ls", log: false)
# File pantograph/lib/pantograph/pant_file.rb, line 199
def sh(*args, &b)
  # First accepts hash (or named keywords) like other actions
  # Otherwise uses sh method that doesn't have an interface like an action
  if args.count == 1 && args.first.kind_of?(Hash)
    hash = args.first
    command = hash.delete(:command)

    raise ArgumentError, "sh requires :command keyword in argument" if command.nil?

    new_args = [*command, hash]
    PantFile.sh(*new_args, &b)
  else
    PantFile.sh(*args, &b)
  end
end
test(params = {}) click to toggle source
# File pantograph/lib/pantograph/pant_file.rb, line 376
def test(params = {})
  # Overwrite this, since there is already a 'test' method defined in the Ruby standard library
  self.runner.try_switch_to_lane(:test, [params])
end