# typed: false # frozen_string_literal: true require "utils/tty" module GitHub # Helper functions for interacting with GitHub Actions. # # @api private module Actions def self.escape(string) # See https://github.community/t/set-output-truncates-multiline-strings/16852/3. string.gsub("%", "%25") .gsub("\n", "%0A") .gsub("\r", "%0D") end # Helper class for formatting annotations on GitHub Actions. class Annotation def self.path_relative_to_workspace(path) workspace = Pathname(ENV.fetch("GITHUB_WORKSPACE", Dir.pwd)).realpath path = Pathname(path) return path unless path.exist? path.realpath.relative_path_from(workspace) end def initialize(type, message, file: nil, line: nil, column: nil) raise ArgumentError, "Unsupported type: #{type.inspect}" unless [:warning, :error].include?(type) @type = type @message = Tty.strip_ansi(message) @file = self.class.path_relative_to_workspace(file) if file @line = Integer(line) if line @column = Integer(column) if column end def to_s file = "file=#{Actions.escape(@file.to_s)}" if @file line = "line=#{@line}" if @line column = "col=#{@column}" if @column metadata = [*file, *line, *column].join(",").presence&.prepend(" ") "::#{@type}#{metadata}::#{Actions.escape(@message)}" end # An annotation is only relevant if the corresponding `file` is relative to # the `GITHUB_WORKSPACE` directory or if no `file` is specified. def relevant? return true if @file.nil? @file.descend.next.to_s != ".." end end end end