Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
+163
View File
@@ -0,0 +1,163 @@
require 'faraday'
require 'addressable/template'
module Sawyer
class Agent
NO_BODY = Set.new([:get, :head])
attr_accessor :links_parser
attr_accessor :allow_undefined_methods
class << self
attr_writer :serializer
end
def self.serializer
@serializer ||= Serializer.any_json
end
def self.encode(data)
serializer.encode(data)
end
def self.decode(data)
serializer.decode(data)
end
# Agents handle making the requests, and passing responses to
# Sawyer::Response.
#
# endpoint - String URI of the API entry point.
# options - Hash of options.
# :allow_undefined_methods - Allow relations to call all the HTTP verbs,
# not just the ones defined.
# :faraday - Optional Faraday::Connection to use.
# :links_parser - Optional parser to parse link relations
# Defaults: Sawyer::LinkParsers::Hal.new
# :serializer - Optional serializer Class. Defaults to
# self.serializer_class.
#
# Yields the Faraday::Connection if a block is given.
def initialize(endpoint, options = nil)
@endpoint = endpoint
@conn = (options && options[:faraday]) || Faraday.new
@serializer = (options && options[:serializer]) || self.class.serializer
@links_parser = (options && options[:links_parser]) || Sawyer::LinkParsers::Hal.new
@allow_undefined_methods = (options && options[:allow_undefined_methods])
@conn.url_prefix = @endpoint
yield @conn if block_given?
end
# Public: Close the underlying connection.
def close
@conn.close if @conn.respond_to?(:close)
end
# Public: Retains a reference to the root relations of the API.
#
# Returns a Sawyer::Relation::Map.
def rels
@rels ||= root.data._rels
end
# Public: Retains a reference to the root response of the API.
#
# Returns a Sawyer::Response.
def root
@root ||= start
end
# Public: Hits the root of the API to get the initial actions.
#
# Returns a Sawyer::Response.
def start
call :get, @endpoint
end
# Makes a request through Faraday.
#
# method - The Symbol name of an HTTP method.
# url - The String URL to access. This can be relative to the Agent's
# endpoint.
# data - The Optional Hash or Resource body to be sent. :get or :head
# requests can have no body, so this can be the options Hash
# instead.
# options - Hash of option to configure the API request.
# :headers - Hash of API headers to set.
# :query - Hash of URL query params to set.
#
# Returns a Sawyer::Response.
def call(method, url, data = nil, options = nil)
if NO_BODY.include?(method)
options ||= data
data = nil
end
options ||= {}
url = expand_url(url, options[:uri])
started = nil
res = @conn.send method, url do |req|
if data
req.body = data.is_a?(String) ? data : encode_body(data)
end
if params = options[:query]
req.params.update params
end
if headers = options[:headers]
req.headers.update headers
end
started = Time.now
end
Response.new self, res, :sawyer_started => started, :sawyer_ended => Time.now
end
# Encodes an object to a string for the API request.
#
# data - The Hash or Resource that is being sent.
#
# Returns a String.
def encode_body(data)
@serializer.encode(data)
end
# Decodes a String response body to a resource.
#
# str - The String body from the response.
#
# Returns an Object resource (Hash by default).
def decode_body(str)
@serializer.decode(str)
end
def parse_links(data)
@links_parser.parse(data)
end
def expand_url(url, options = nil)
tpl = url.respond_to?(:expand) ? url : Addressable::Template.new(url.to_s)
tpl.expand(options || {}).to_s
end
def allow_undefined_methods?
!!@allow_undefined_methods
end
def inspect
%(<#{self.class} #{@endpoint}>)
end
# private
def to_yaml_properties
[:@endpoint]
end
def marshal_dump
[@endpoint]
end
def marshal_load(dumped)
@endpoint = *dumped.shift(1)
end
end
end
@@ -0,0 +1,15 @@
module Sawyer
module LinkParsers
class Hal
def parse(data)
links = data.delete(:_links)
return data, links
end
end
end
end
@@ -0,0 +1,29 @@
module Sawyer
module LinkParsers
class Simple
LINK_REGEX = /_?url$/
# Public: Parses simple *_url style links on resources
#
# data - Hash of resource data
#
# Returns a Hash of data with separate links Hash
def parse(data)
links = {}
inline_links = data.keys.select {|k| k.to_s[LINK_REGEX] }
inline_links.each do |key|
rel_name = key.to_s == 'url' ? 'self' : key.to_s.gsub(LINK_REGEX, '')
links[rel_name.to_sym] = data[key]
end
return data, links
end
end
end
end
@@ -0,0 +1,272 @@
module Sawyer
class Relation
class Map
# Tracks the available next actions for a resource, and
# issues requests for them.
def initialize
@map = {}
end
# Adds a Relation to the map.
#
# rel - A Relation.
#
# Returns nothing.
def <<(rel)
@map[rel.name] = rel if rel
end
# Gets the raw Relation by its name.
#
# key - The Symbol name of the Relation.
#
# Returns a Relation.
def [](key)
@map[key.to_sym]
end
# Gets the number of mapped Relations.
#
# Returns an Integer.
def size
@map.size
end
# Gets a list of the Relation names.
#
# Returns an Array of Symbols in no specific order.
def keys
@map.keys
end
def to_hash
pairs = @map.map do |k, v|
[(k.to_s + "_url").to_sym, v.href]
end
Hash[pairs]
end
alias :to_h :to_hash
def inspect
hash = to_hash
hash.respond_to?(:pretty_inspect) ? hash.pretty_inspect : hash.inspect
end
end
attr_reader :agent,
:name,
:href_template,
:method,
:available_methods
# Public: Builds an index of Relations from the value of a `_links`
# property in a resource. :get is the default method. Any links with
# multiple specified methods will get multiple relations created.
#
# index - The Hash mapping Relation names to the Hash Relation
# options.
# rels - A Relation::Map to store the Relations.
#
# Returns a Relation::Map
def self.from_links(agent, index, rels = Map.new)
if index.is_a?(Array)
raise ArgumentError, "Links must be a hash of rel => {_href => '...'}: #{index.inspect}"
end
index.each do |name, options|
rels << from_link(agent, name, options)
end if index
rels
end
# Public: Builds a single Relation from the given options. These are
# usually taken from a `_links` property in a resource.
#
# agent - The Sawyer::Agent that made the request.
# name - The Symbol name of the Relation.
# options - A Hash containing the other Relation properties.
# :href - The String URL of the next action's location.
# :method - The optional String HTTP method.
#
# Returns a Relation.
def self.from_link(agent, name, options)
case options
when Hash
new agent, name, options[:href], options[:method]
when String
new agent, name, options
end
end
# A Relation represents an available next action for a resource.
#
# agent - The Sawyer::Agent that made the request.
# name - The Symbol name of the relation.
# href - The String URL of the location of the next action.
# method - The Symbol HTTP method. Default: :get
def initialize(agent, name, href, method = nil)
@agent = agent
@name = name.to_sym
@href = href
@href_template = Addressable::Template.new(href.to_s)
methods = nil
if method.is_a? String
if method.size.zero?
method = nil
else
method.downcase!
methods = method.split(',').map! do |m|
m.strip!
m.to_sym
end
method = methods.first
end
end
@method = (method || :get).to_sym
@available_methods = Set.new methods || [@method]
end
# Public: Makes an API request with the curent Relation using HEAD.
#
# data - The Optional Hash or Resource body to be sent. :get or :head
# requests can have no body, so this can be the options Hash
# instead.
# options - Hash of option to configure the API request.
# :headers - Hash of API headers to set.
# :query - Hash of URL query params to set.
# :method - Symbol HTTP method.
#
# Returns a Sawyer::Response.
def head(options = nil)
options ||= {}
options[:method] = :head
call options
end
# Public: Makes an API request with the curent Relation using GET.
#
# data - The Optional Hash or Resource body to be sent. :get or :head
# requests can have no body, so this can be the options Hash
# instead.
# options - Hash of option to configure the API request.
# :headers - Hash of API headers to set.
# :query - Hash of URL query params to set.
# :method - Symbol HTTP method.
#
# Returns a Sawyer::Response.
def get(options = nil)
options ||= {}
options[:method] = :get
call options
end
# Public: Makes an API request with the curent Relation using POST.
#
# data - The Optional Hash or Resource body to be sent.
# options - Hash of option to configure the API request.
# :headers - Hash of API headers to set.
# :query - Hash of URL query params to set.
# :method - Symbol HTTP method.
#
# Returns a Sawyer::Response.
def post(data = nil, options = nil)
options ||= {}
options[:method] = :post
call data, options
end
# Public: Makes an API request with the curent Relation using PUT.
#
# data - The Optional Hash or Resource body to be sent.
# options - Hash of option to configure the API request.
# :headers - Hash of API headers to set.
# :query - Hash of URL query params to set.
# :method - Symbol HTTP method.
#
# Returns a Sawyer::Response.
def put(data = nil, options = nil)
options ||= {}
options[:method] = :put
call data, options
end
# Public: Makes an API request with the curent Relation using PATCH.
#
# data - The Optional Hash or Resource body to be sent.
# options - Hash of option to configure the API request.
# :headers - Hash of API headers to set.
# :query - Hash of URL query params to set.
# :method - Symbol HTTP method.
#
# Returns a Sawyer::Response.
def patch(data = nil, options = nil)
options ||= {}
options[:method] = :patch
call data, options
end
# Public: Makes an API request with the curent Relation using DELETE.
#
# data - The Optional Hash or Resource body to be sent.
# options - Hash of option to configure the API request.
# :headers - Hash of API headers to set.
# :query - Hash of URL query params to set.
# :method - Symbol HTTP method.
#
# Returns a Sawyer::Response.
def delete(data = nil, options = nil)
options ||= {}
options[:method] = :delete
call data, options
end
# Public: Makes an API request with the curent Relation using OPTIONS.
#
# data - The Optional Hash or Resource body to be sent.
# options - Hash of option to configure the API request.
# :headers - Hash of API headers to set.
# :query - Hash of URL query params to set.
# :method - Symbol HTTP method.
#
# Returns a Sawyer::Response.
def options(data = nil, opt = nil)
opt ||= {}
opt[:method] = :options
call data, opt
end
def href(options = nil)
return @href if @href_template.nil?
@href_template.expand(options || {}).to_s
end
# Public: Makes an API request with the curent Relation.
#
# data - The Optional Hash or Resource body to be sent. :get or :head
# requests can have no body, so this can be the options Hash
# instead.
# options - Hash of option to configure the API request.
# :headers - Hash of API headers to set.
# :query - Hash of URL query params to set.
# :method - Symbol HTTP method.
#
# Raises ArgumentError if the :method value is not in @available_methods.
# Returns a Sawyer::Response.
def call(data = nil, options = nil)
m = options && options[:method]
if m && !@agent.allow_undefined_methods? && !@available_methods.include?(m == :head ? :get : m)
raise ArgumentError, "method #{m.inspect} is not available: #{@available_methods.to_a.inspect}"
end
@agent.call m || @method, @href_template, data, options
end
def inspect
%(#<#{self.class}: #{@name}: #{@method} #{@href_template}>)
end
end
end
@@ -0,0 +1,160 @@
require 'forwardable'
module Sawyer
class Resource
SPECIAL_METHODS = Set.new(%w(agent rels fields))
attr_reader :_agent, :_rels, :_fields
attr_reader :attrs
include Enumerable
extend Forwardable
# Initializes a Resource with the given data.
#
# agent - The Sawyer::Agent that made the API request.
# data - Hash of key/value properties.
def initialize(agent, data = {})
@_agent = agent
data, links = agent.parse_links(data)
@_rels = Relation.from_links(agent, links)
@_fields = Set.new
@_metaclass = (class << self; self; end)
@attrs = {}
data.each do |key, value|
@_fields << key
@attrs[key.to_sym] = process_value(value)
end
@_metaclass.send(:attr_accessor, *data.keys)
end
# Processes an individual value of this resource. Hashes get exploded
# into another Resource, and Arrays get their values processed too.
#
# value - An Object value of a Resource's data.
#
# Returns an Object to set as the value of a Resource key.
def process_value(value)
case value
when Hash then self.class.new(@_agent, value)
when Array then value.map { |v| process_value(v) }
else value
end
end
# Checks to see if the given key is in this resource.
#
# key - A Symbol key.
#
# Returns true if the key exists, or false.
def key?(key)
@_fields.include? key
end
# Allow fields to be retrieved via Hash notation
#
# method - key name
#
# Returns the value from attrs if exists
def [](method)
send(method.to_sym)
rescue NoMethodError
nil
end
# Allow fields to be set via Hash notation
#
# method - key name
# value - value to set for the attr key
#
# Returns - value
def []=(method, value)
send("#{method}=", value)
rescue NoMethodError
nil
end
def_delegators :attrs, :dig, :fetch
ATTR_SETTER = '='.freeze
ATTR_PREDICATE = '?'.freeze
# Provides access to a resource's attributes.
def method_missing(method, *args)
attr_name, suffix = method.to_s.scan(/([a-z0-9\_]+)(\?|\=)?$/i).first
if suffix == ATTR_SETTER
@_metaclass.send(:attr_accessor, attr_name)
@_fields << attr_name.to_sym
send(method, args.first)
elsif attr_name && @_fields.include?(attr_name.to_sym)
value = @attrs[attr_name.to_sym]
case suffix
when nil
@_metaclass.send(:attr_accessor, attr_name)
value
when ATTR_PREDICATE then !!value
end
elsif suffix.nil? && SPECIAL_METHODS.include?(attr_name)
instance_variable_get "@_#{attr_name}"
elsif attr_name && !@_fields.include?(attr_name.to_sym)
nil
else
super
end
end
# Wire up accessor methods to pull from attrs
def self.attr_accessor(*attrs)
attrs.each do |attribute|
class_eval do
define_method attribute do
@attrs[attribute.to_sym]
end
define_method "#{attribute}=" do |value|
@attrs[attribute.to_sym] = value
end
define_method "#{attribute}?" do
!!@attrs[attribute.to_sym]
end
end
end
end
def inspect
to_attrs.respond_to?(:pretty_inspect) ? to_attrs.pretty_inspect : to_attrs.inspect
end
def each(&block)
@attrs.each(&block)
end
# private
def to_yaml_properties
[:@attrs, :@_fields, :@_rels]
end
def to_attrs
hash = self.attrs.clone
hash.keys.each do |k|
if hash[k].is_a?(Sawyer::Resource)
hash[k] = hash[k].to_attrs
elsif hash[k].is_a?(Array) && hash[k].all?{|el| el.is_a?(Sawyer::Resource)}
hash[k] = hash[k].collect{|el| el.to_attrs}
end
end
hash
end
alias to_hash to_attrs
alias to_h to_attrs
def marshal_dump
[@attrs, @_fields, @_rels]
end
def marshal_load(dumped)
@attrs, @_fields, @_rels = *dumped.shift(3)
@_metaclass = (class << self; self; end)
end
end
end
@@ -0,0 +1,72 @@
module Sawyer
class Response
attr_reader :agent,
:status,
:headers,
:env,
:body,
:rels
# Builds a Response after a completed request.
#
# agent - The Sawyer::Agent that is managing the API connection.
# res - A Faraday::Response.
def initialize(agent, res, options = {})
@agent = agent
@status = res.status
@headers = res.headers
@env = res.env
@body = res.body
@rels = process_rels
@started = options[:sawyer_started]
@ended = options[:sawyer_ended]
end
def data
@data ||= begin
return(body) unless (headers[:content_type] =~ /json|msgpack/)
process_data(agent.decode_body(body))
end
end
# Turns parsed contents from an API response into a Resource or
# collection of Resources.
#
# data - Either an Array or Hash parsed from JSON.
#
# Returns either a Resource or Array of Resources.
def process_data(data)
case data
when Hash then Resource.new(agent, data)
when Array then data.map { |hash| process_data(hash) }
when nil then nil
else data
end
end
# Finds link relations from 'Link' response header
#
# Returns an array of Relations
def process_rels
links = ( @headers["Link"] || "" ).split(', ').map do |link|
href, name = link.match(/<(.*?)>; rel="(\w+)"/).captures
[name.to_sym, Relation.from_link(@agent, name, :href => href)]
end
Hash[*links.flatten]
end
def timing
@timing ||= @ended - @started
end
def time
@ended
end
def inspect
%(#<#{self.class}: #{@status} @rels=#{@rels.inspect} @data=#{data.inspect}>)
end
end
end
@@ -0,0 +1,129 @@
require 'date'
require 'time'
module Sawyer
class Serializer
def self.any_json
yajl || multi_json || json || begin
raise RuntimeError, "Sawyer requires a JSON gem: yajl, multi_json, or json"
end
end
def self.yajl
require 'yajl'
new(Yajl)
rescue LoadError
end
def self.json
require 'json'
new(JSON)
rescue LoadError
end
def self.multi_json
require 'multi_json'
new(MultiJson)
rescue LoadError
end
def self.message_pack
require 'msgpack'
new(MessagePack, :pack, :unpack)
rescue LoadError
end
# Public: Wraps a serialization format for Sawyer. Nested objects are
# prepared for serialization (such as changing Times to ISO 8601 Strings).
# Any serialization format that responds to #dump and #load will work.
def initialize(format, dump_method_name = nil, load_method_name = nil)
@format = format
@dump = @format.method(dump_method_name || :dump)
@load = @format.method(load_method_name || :load)
end
# Public: Encodes an Object (usually a Hash or Array of Hashes).
#
# data - Object to be encoded.
#
# Returns an encoded String.
def encode(data)
@dump.call(encode_object(data))
end
alias dump encode
# Public: Decodes a String into an Object (usually a Hash or Array of
# Hashes).
#
# data - An encoded String.
#
# Returns a decoded Object.
def decode(data)
return nil if data.nil? || data.strip.empty?
decode_object(@load.call(data))
end
alias load decode
def encode_object(data)
case data
when Hash then encode_hash(data)
when Array then data.map { |o| encode_object(o) }
else data
end
end
def encode_hash(hash)
hash.keys.each do |key|
case value = hash[key]
when Date then hash[key] = value.to_time.utc.xmlschema
when Time then hash[key] = value.utc.xmlschema
when Hash then hash[key] = encode_hash(value)
end
end
hash
end
def decode_object(data)
case data
when Hash then decode_hash(data)
when Array then data.map { |o| decode_object(o) }
else data
end
end
def decode_hash(hash)
hash.keys.each do |key|
hash[key.to_sym] = decode_hash_value(key, hash.delete(key))
end
hash
end
def decode_hash_value(key, value)
if time_field?(key, value)
if value.is_a?(String)
begin
Time.parse(value)
rescue ArgumentError
value
end
elsif value.is_a?(Integer) || value.is_a?(Float)
Time.at(value)
else
value
end
elsif value.is_a?(Hash)
decode_hash(value)
elsif value.is_a?(Array)
value.map { |o| decode_hash_value(key, o) }
else
value
end
end
def time_field?(key, value)
value && (key =~ /_(at|on)\z/ || key =~ /(\A|_)date\z/)
end
end
end