fs-gitlab 4.18.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +270 -0
  3. data/LICENSE.txt +24 -0
  4. data/README.md +260 -0
  5. data/exe/gitlab +11 -0
  6. data/lib/gitlab/api.rb +24 -0
  7. data/lib/gitlab/cli.rb +89 -0
  8. data/lib/gitlab/cli_helpers.rb +243 -0
  9. data/lib/gitlab/client/access_requests.rb +103 -0
  10. data/lib/gitlab/client/application_settings.rb +172 -0
  11. data/lib/gitlab/client/avatar.rb +21 -0
  12. data/lib/gitlab/client/award_emojis.rb +137 -0
  13. data/lib/gitlab/client/boards.rb +146 -0
  14. data/lib/gitlab/client/branches.rb +135 -0
  15. data/lib/gitlab/client/broadcast_messages.rb +75 -0
  16. data/lib/gitlab/client/build_variables.rb +135 -0
  17. data/lib/gitlab/client/builds.rb +108 -0
  18. data/lib/gitlab/client/commits.rb +216 -0
  19. data/lib/gitlab/client/container_registry.rb +85 -0
  20. data/lib/gitlab/client/deployments.rb +34 -0
  21. data/lib/gitlab/client/environments.rb +89 -0
  22. data/lib/gitlab/client/epic_issues.rb +23 -0
  23. data/lib/gitlab/client/epics.rb +73 -0
  24. data/lib/gitlab/client/events.rb +60 -0
  25. data/lib/gitlab/client/features.rb +48 -0
  26. data/lib/gitlab/client/group_badges.rb +88 -0
  27. data/lib/gitlab/client/group_boards.rb +141 -0
  28. data/lib/gitlab/client/group_labels.rb +88 -0
  29. data/lib/gitlab/client/group_milestones.rb +94 -0
  30. data/lib/gitlab/client/groups.rb +358 -0
  31. data/lib/gitlab/client/issue_links.rb +48 -0
  32. data/lib/gitlab/client/issues.rb +231 -0
  33. data/lib/gitlab/client/jobs.rb +250 -0
  34. data/lib/gitlab/client/keys.rb +29 -0
  35. data/lib/gitlab/client/labels.rb +88 -0
  36. data/lib/gitlab/client/lint.rb +19 -0
  37. data/lib/gitlab/client/markdown.rb +23 -0
  38. data/lib/gitlab/client/merge_request_approvals.rb +265 -0
  39. data/lib/gitlab/client/merge_requests.rb +386 -0
  40. data/lib/gitlab/client/milestones.rb +106 -0
  41. data/lib/gitlab/client/namespaces.rb +22 -0
  42. data/lib/gitlab/client/notes.rb +313 -0
  43. data/lib/gitlab/client/packages.rb +95 -0
  44. data/lib/gitlab/client/pipeline_schedules.rb +147 -0
  45. data/lib/gitlab/client/pipeline_triggers.rb +103 -0
  46. data/lib/gitlab/client/pipelines.rb +105 -0
  47. data/lib/gitlab/client/project_badges.rb +85 -0
  48. data/lib/gitlab/client/project_clusters.rb +83 -0
  49. data/lib/gitlab/client/project_release_links.rb +76 -0
  50. data/lib/gitlab/client/project_releases.rb +79 -0
  51. data/lib/gitlab/client/projects.rb +708 -0
  52. data/lib/gitlab/client/protected_tags.rb +59 -0
  53. data/lib/gitlab/client/remote_mirrors.rb +51 -0
  54. data/lib/gitlab/client/repositories.rb +113 -0
  55. data/lib/gitlab/client/repository_files.rb +131 -0
  56. data/lib/gitlab/client/repository_submodules.rb +27 -0
  57. data/lib/gitlab/client/resource_label_events.rb +82 -0
  58. data/lib/gitlab/client/resource_state_events.rb +57 -0
  59. data/lib/gitlab/client/runners.rb +211 -0
  60. data/lib/gitlab/client/search.rb +66 -0
  61. data/lib/gitlab/client/services.rb +53 -0
  62. data/lib/gitlab/client/sidekiq.rb +39 -0
  63. data/lib/gitlab/client/snippets.rb +95 -0
  64. data/lib/gitlab/client/system_hooks.rb +64 -0
  65. data/lib/gitlab/client/tags.rb +97 -0
  66. data/lib/gitlab/client/templates.rb +100 -0
  67. data/lib/gitlab/client/todos.rb +46 -0
  68. data/lib/gitlab/client/user_snippets.rb +114 -0
  69. data/lib/gitlab/client/users.rb +397 -0
  70. data/lib/gitlab/client/versions.rb +18 -0
  71. data/lib/gitlab/client/wikis.rb +79 -0
  72. data/lib/gitlab/client.rb +95 -0
  73. data/lib/gitlab/configuration.rb +57 -0
  74. data/lib/gitlab/error.rb +170 -0
  75. data/lib/gitlab/file_response.rb +48 -0
  76. data/lib/gitlab/help.rb +94 -0
  77. data/lib/gitlab/objectified_hash.rb +51 -0
  78. data/lib/gitlab/page_links.rb +35 -0
  79. data/lib/gitlab/paginated_response.rb +110 -0
  80. data/lib/gitlab/request.rb +109 -0
  81. data/lib/gitlab/shell.rb +83 -0
  82. data/lib/gitlab/shell_history.rb +57 -0
  83. data/lib/gitlab/version.rb +5 -0
  84. data/lib/gitlab.rb +56 -0
  85. metadata +204 -0
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Gitlab::Client
4
+ # Defines methods related to wikis.
5
+ # @see https://docs.gitlab.com/ce/api/wikis.html
6
+ module Wikis
7
+ # Get all wiki pages for a given project.
8
+ #
9
+ # @example
10
+ # Gitlab.wikis(3)
11
+ # Gitlab.wikis(3, {with_content: 'Some wiki content'})
12
+ #
13
+ # @param [Integer, String] project The ID or name of a project.
14
+ # @param [Hash] options A customizable set of options.
15
+ # @option options [String] with_content(optional) Include pages content
16
+ # @return [Array<Gitlab::ObjectifiedHash>]
17
+ def wikis(project, options = {})
18
+ get("/projects/#{url_encode project}/wikis", query: options)
19
+ end
20
+
21
+ # Get a wiki page for a given project.
22
+ #
23
+ # @example
24
+ # Gitlab.wiki(3, 'home')
25
+ #
26
+ # @param [Integer, String] project The ID or name of a project.
27
+ # @param [String] slug The slug (a unique string) of the wiki page
28
+ # @return [Gitlab::ObjectifiedHash]
29
+ def wiki(project, slug)
30
+ get("/projects/#{url_encode project}/wikis/#{slug}")
31
+ end
32
+
33
+ # Creates a new wiki page for the given repository with the given title, slug, and content.
34
+ #
35
+ # @example
36
+ # Gitlab.create_wiki(3, 'Some Title', 'Some Content')
37
+ # Gitlab.create_wiki(3, 'Some Title', 'Some Content', { format: 'rdoc' })
38
+ #
39
+ # @param [Integer, String] project The ID or name of a project.
40
+ # @param [String] content The content of the wiki page.
41
+ # @param [String] title The title of the wiki page.
42
+ # @param [Hash] options A customizable set of options.
43
+ # @option options [String] format (optional) The format of the wiki page. Available formats are: markdown (default), rdoc, and asciidoc.
44
+ # @return [Gitlab::ObjectifiedHash] Information about created wiki page.
45
+ def create_wiki(project, title, content, options = {})
46
+ body = { content: content, title: title }.merge(options)
47
+ post("/projects/#{url_encode project}/wikis", body: body)
48
+ end
49
+
50
+ # Updates an existing wiki page. At least one parameter is required to update the wiki page.
51
+ #
52
+ # @example
53
+ # Gitlab.update_wiki(6, 'home', { title: 'New title' })
54
+ # Gitlab.update_wiki(6, 'home', { title: 'New title', content: 'New Message', format: 'rdoc' })
55
+ #
56
+ # @param [Integer, String] project The ID or name of a project.
57
+ # @param [String] slug The slug (a unique string) of the wiki page.
58
+ # @param [Hash] options A customizable set of options.
59
+ # @option options [String] content The content of the wiki page.
60
+ # @option options [String] title The title of the wiki page.
61
+ # @option options [String] format (optional) The format of the wiki page. Available formats are: markdown (default), rdoc, and asciidoc.
62
+ # @return [Gitlab::ObjectifiedHash] Information about updated wiki page.
63
+ def update_wiki(project, slug, options = {})
64
+ put("/projects/#{url_encode project}/wikis/#{slug}", body: options)
65
+ end
66
+
67
+ # Deletes a wiki page with a given slug.
68
+ #
69
+ # @example
70
+ # Gitlab.delete_wiki(42, 'foo')
71
+ #
72
+ # @param [Integer, String] project The ID or name of a project.
73
+ # @param [String] slug The slug (a unique string) of the wiki page.
74
+ # @return [Gitlab::ObjectifiedHash] An empty objectified hash
75
+ def delete_wiki(project, slug)
76
+ delete("/projects/#{url_encode project}/wikis/#{slug}")
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ # Wrapper for the Gitlab REST API.
5
+ class Client < API
6
+ Dir[File.expand_path('client/*.rb', __dir__)].each { |f| require f }
7
+
8
+ # Please keep in alphabetical order
9
+ include AccessRequests
10
+ include ApplicationSettings
11
+ include Avatar
12
+ include AwardEmojis
13
+ include Boards
14
+ include Branches
15
+ include BroadcastMessages
16
+ include BuildVariables
17
+ include Builds
18
+ include Commits
19
+ include ContainerRegistry
20
+ include Deployments
21
+ include Environments
22
+ include EpicIssues
23
+ include Epics
24
+ include Events
25
+ include Features
26
+ include GroupBadges
27
+ include GroupBoards
28
+ include GroupLabels
29
+ include GroupMilestones
30
+ include Groups
31
+ include IssueLinks
32
+ include Issues
33
+ include Jobs
34
+ include Keys
35
+ include Labels
36
+ include Lint
37
+ include Markdown
38
+ include MergeRequestApprovals
39
+ include MergeRequests
40
+ include Milestones
41
+ include Namespaces
42
+ include Notes
43
+ include PipelineSchedules
44
+ include PipelineTriggers
45
+ include Pipelines
46
+ include ProjectBadges
47
+ include ProjectClusters
48
+ include ProjectReleaseLinks
49
+ include ProjectReleases
50
+ include Projects
51
+ include ProtectedTags
52
+ include RemoteMirrors
53
+ include Repositories
54
+ include RepositoryFiles
55
+ include RepositorySubmodules
56
+ include ResourceLabelEvents
57
+ include ResourceStateEvents
58
+ include Runners
59
+ include Search
60
+ include Services
61
+ include Sidekiq
62
+ include Snippets
63
+ include SystemHooks
64
+ include Tags
65
+ include Templates
66
+ include Todos
67
+ include Users
68
+ include UserSnippets
69
+ include Versions
70
+ include Wikis
71
+
72
+ # Text representation of the client, masking private token.
73
+ #
74
+ # @return [String]
75
+ def inspect
76
+ inspected = super
77
+ inspected.sub! @private_token, only_show_last_four_chars(@private_token) if @private_token
78
+ inspected
79
+ end
80
+
81
+ # Utility method for URL encoding of a string.
82
+ # Copied from https://ruby-doc.org/stdlib-2.7.0/libdoc/erb/rdoc/ERB/Util.html
83
+ #
84
+ # @return [String]
85
+ def url_encode(url)
86
+ url.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) { |m| sprintf('%%%02X', m.unpack1('C')) } # rubocop:disable Style/FormatString
87
+ end
88
+
89
+ private
90
+
91
+ def only_show_last_four_chars(token)
92
+ "#{'*' * (token.size - 4)}#{token[-4..]}"
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gitlab/cli_helpers'
4
+ module Gitlab
5
+ # Defines constants and methods related to configuration.
6
+ module Configuration
7
+ # An array of valid keys in the options hash when configuring a Gitlab::API.
8
+ VALID_OPTIONS_KEYS = %i[endpoint private_token user_agent sudo httparty].freeze
9
+
10
+ # The user agent that will be sent to the API endpoint if none is set.
11
+ DEFAULT_USER_AGENT = "Gitlab Ruby Gem #{Gitlab::VERSION}"
12
+
13
+ # @private
14
+ attr_accessor(*VALID_OPTIONS_KEYS)
15
+ # @private
16
+ alias auth_token= private_token=
17
+
18
+ # Sets all configuration options to their default values
19
+ # when this module is extended.
20
+ def self.extended(base)
21
+ base.reset
22
+ end
23
+
24
+ # Convenience method to allow configuration options to be set in a block.
25
+ def configure
26
+ yield self
27
+ end
28
+
29
+ # Creates a hash of options and their values.
30
+ def options
31
+ VALID_OPTIONS_KEYS.inject({}) do |option, key|
32
+ option.merge!(key => send(key))
33
+ end
34
+ end
35
+
36
+ # Resets all configuration options to the defaults.
37
+ def reset
38
+ self.endpoint = ENV['GITLAB_API_ENDPOINT'] || ENV['CI_API_V4_URL']
39
+ self.private_token = ENV['GITLAB_API_PRIVATE_TOKEN'] || ENV['GITLAB_API_AUTH_TOKEN']
40
+ self.httparty = get_httparty_config(ENV['GITLAB_API_HTTPARTY_OPTIONS'])
41
+ self.sudo = nil
42
+ self.user_agent = DEFAULT_USER_AGENT
43
+ end
44
+
45
+ private
46
+
47
+ # Allows HTTParty config to be specified in ENV using YAML hash.
48
+ def get_httparty_config(options)
49
+ return if options.nil?
50
+
51
+ httparty = Gitlab::CLI::Helpers.yaml_load(options)
52
+ raise ArgumentError, 'HTTParty config should be a Hash.' unless httparty.is_a? Hash
53
+
54
+ Gitlab::CLI::Helpers.symbolize_keys httparty
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module Error
5
+ # Custom error class for rescuing from all Gitlab errors.
6
+ class Error < StandardError; end
7
+
8
+ # Raised when API endpoint credentials not configured.
9
+ class MissingCredentials < Error; end
10
+
11
+ # Raised when impossible to parse response body.
12
+ class Parsing < Error; end
13
+
14
+ # Custom error class for rescuing from HTTP response errors.
15
+ class ResponseError < Error
16
+ POSSIBLE_MESSAGE_KEYS = %i[message error_description error].freeze
17
+
18
+ def initialize(response)
19
+ @response = response
20
+ super(build_error_message)
21
+ end
22
+
23
+ # Status code returned in the HTTP response.
24
+ #
25
+ # @return [Integer]
26
+ def response_status
27
+ @response.code
28
+ end
29
+
30
+ # Body content returned in the HTTP response
31
+ #
32
+ # @return [String]
33
+ def response_message
34
+ @response.parsed_response.message
35
+ end
36
+
37
+ # Additional error context returned by some API endpoints
38
+ #
39
+ # @return [String]
40
+ def error_code
41
+ if @response.respond_to?(:error_code)
42
+ @response.error_code
43
+ else
44
+ ''
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # Human friendly message.
51
+ #
52
+ # @return [String]
53
+ def build_error_message
54
+ parsed_response = classified_response
55
+ message = check_error_keys(parsed_response)
56
+ "Server responded with code #{@response.code}, message: " \
57
+ "#{handle_message(message)}. " \
58
+ "Request URI: #{@response.request.base_uri}#{@response.request.path}"
59
+ end
60
+
61
+ # Error keys vary across the API, find the first key that the parsed_response
62
+ # object responds to and return that, otherwise return the original.
63
+ def check_error_keys(resp)
64
+ key = POSSIBLE_MESSAGE_KEYS.find { |k| resp.respond_to?(k) }
65
+ key ? resp.send(key) : resp
66
+ end
67
+
68
+ # Parse the body based on the classification of the body content type
69
+ #
70
+ # @return parsed response
71
+ def classified_response
72
+ if @response.respond_to?('headers')
73
+ @response.headers['content-type'] == 'text/plain' ? { message: @response.to_s } : @response.parsed_response
74
+ else
75
+ @response.parsed_response
76
+ end
77
+ rescue Gitlab::Error::Parsing
78
+ # Return stringified response when receiving a
79
+ # parsing error to avoid obfuscation of the
80
+ # api error.
81
+ #
82
+ # note: The Gitlab API does not always return valid
83
+ # JSON when there are errors.
84
+ @response.to_s
85
+ end
86
+
87
+ # Handle error response message in case of nested hashes
88
+ def handle_message(message)
89
+ case message
90
+ when Gitlab::ObjectifiedHash
91
+ message.to_h.sort.map do |key, val|
92
+ "'#{key}' #{(val.is_a?(Hash) ? val.sort.map { |k, v| "(#{k}: #{v.join(' ')})" } : [val].flatten).join(' ')}"
93
+ end.join(', ')
94
+ when Array
95
+ message.join(' ')
96
+ else
97
+ message
98
+ end
99
+ end
100
+ end
101
+
102
+ # Raised when API endpoint returns the HTTP status code 400.
103
+ class BadRequest < ResponseError; end
104
+
105
+ # Raised when API endpoint returns the HTTP status code 401.
106
+ class Unauthorized < ResponseError; end
107
+
108
+ # Raised when API endpoint returns the HTTP status code 403.
109
+ class Forbidden < ResponseError; end
110
+
111
+ # Raised when API endpoint returns the HTTP status code 404.
112
+ class NotFound < ResponseError; end
113
+
114
+ # Raised when API endpoint returns the HTTP status code 405.
115
+ class MethodNotAllowed < ResponseError; end
116
+
117
+ # Raised when API endpoint returns the HTTP status code 406.
118
+ class NotAcceptable < ResponseError; end
119
+
120
+ # Raised when API endpoint returns the HTTP status code 409.
121
+ class Conflict < ResponseError; end
122
+
123
+ # Raised when API endpoint returns the HTTP status code 422.
124
+ class Unprocessable < ResponseError; end
125
+
126
+ # Raised when API endpoint returns the HTTP status code 429.
127
+ class TooManyRequests < ResponseError; end
128
+
129
+ # Raised when API endpoint returns the HTTP status code 500.
130
+ class InternalServerError < ResponseError; end
131
+
132
+ # Raised when API endpoint returns the HTTP status code 502.
133
+ class BadGateway < ResponseError; end
134
+
135
+ # Raised when API endpoint returns the HTTP status code 503.
136
+ class ServiceUnavailable < ResponseError; end
137
+
138
+ # Raised when API endpoint returns the HTTP status code 522.
139
+ class ConnectionTimedOut < ResponseError; end
140
+
141
+ # HTTP status codes mapped to error classes.
142
+ STATUS_MAPPINGS = {
143
+ 400 => BadRequest,
144
+ 401 => Unauthorized,
145
+ 403 => Forbidden,
146
+ 404 => NotFound,
147
+ 405 => MethodNotAllowed,
148
+ 406 => NotAcceptable,
149
+ 409 => Conflict,
150
+ 422 => Unprocessable,
151
+ 429 => TooManyRequests,
152
+ 500 => InternalServerError,
153
+ 502 => BadGateway,
154
+ 503 => ServiceUnavailable,
155
+ 522 => ConnectionTimedOut
156
+ }.freeze
157
+
158
+ # Returns error class that should be raised for this response. Returns nil
159
+ # if the response status code is not 4xx or 5xx.
160
+ #
161
+ # @param [HTTParty::Response] response The response object.
162
+ # @return [Class<Error::ResponseError>, nil]
163
+ def self.klass(response)
164
+ error_klass = STATUS_MAPPINGS[response.code]
165
+ return error_klass if error_klass
166
+
167
+ ResponseError if response.server_error? || response.client_error?
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ # Wrapper class of file response.
5
+ class FileResponse
6
+ HEADER_CONTENT_DISPOSITION = 'Content-Disposition'
7
+
8
+ attr_reader :filename
9
+
10
+ def initialize(file)
11
+ @file = file
12
+ end
13
+
14
+ # @return [bool] Always false
15
+ def empty?
16
+ false
17
+ end
18
+
19
+ # @return [Hash] A hash consisting of filename and io object
20
+ def to_hash
21
+ { filename: @filename, data: @file }
22
+ end
23
+ alias to_h to_hash
24
+
25
+ # @return [String] Formatted string with the class name, object id and filename.
26
+ def inspect
27
+ "#<#{self.class}:#{object_id} {filename: #{filename.inspect}}>"
28
+ end
29
+
30
+ def method_missing(name, *args, &block)
31
+ if @file.respond_to?(name)
32
+ @file.send(name, *args, &block)
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ def respond_to_missing?(method_name, include_private = false)
39
+ super || @file.respond_to?(method_name, include_private)
40
+ end
41
+
42
+ # Parse filename from the 'Content Disposition' header.
43
+ def parse_headers!(headers)
44
+ @filename = headers[HEADER_CONTENT_DISPOSITION].split('filename=')[1]
45
+ @filename = @filename[1...-1] if @filename[0] == '"' # Unquote filenames
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gitlab'
4
+ require 'gitlab/cli_helpers'
5
+
6
+ module Gitlab::Help
7
+ extend Gitlab::CLI::Helpers
8
+
9
+ class << self
10
+ # Returns the (modified) help from the 'ri' command or returns an error.
11
+ #
12
+ # @return [String]
13
+ def get_help(cmd)
14
+ cmd_namespace = namespace cmd
15
+
16
+ if cmd_namespace
17
+ ri_output = `#{ri_cmd} -T #{cmd_namespace} 2>&1`.chomp
18
+
19
+ if $CHILD_STATUS == 0
20
+ change_help_output! cmd, ri_output
21
+ yield ri_output if block_given?
22
+
23
+ ri_output
24
+ else
25
+ "Ri docs not found for #{cmd}, please install the docs to use 'help'."
26
+ end
27
+ else
28
+ "Unknown command: #{cmd}."
29
+ end
30
+ end
31
+
32
+ # Finds the location of 'ri' on a system.
33
+ #
34
+ # @return [String]
35
+ def ri_cmd
36
+ which_ri = `which ri`.chomp
37
+ raise "'ri' tool not found in $PATH. Please install it to use the help." if which_ri.empty?
38
+
39
+ which_ri
40
+ end
41
+
42
+ # A hash map that contains help topics (Branches, Groups, etc.)
43
+ # and a list of commands that are defined under a topic (create_branch,
44
+ # branches, protect_branch, etc.).
45
+ #
46
+ # @return [Hash<Array>]
47
+ def help_map
48
+ @help_map ||=
49
+ actions.each_with_object({}) do |action, hsh|
50
+ key = client.method(action)
51
+ .owner.to_s.gsub(/Gitlab::(?:Client::)?/, '')
52
+ hsh[key] ||= []
53
+ hsh[key] << action.to_s
54
+ end
55
+ end
56
+
57
+ # Table with available commands.
58
+ #
59
+ # @return [Terminal::Table]
60
+ def actions_table(topic = nil)
61
+ rows = topic ? help_map[topic] : help_map.keys
62
+ table do |t|
63
+ t.title = topic || 'Help Topics'
64
+
65
+ # add_row expects an array and we have strings hence the map.
66
+ rows.sort.map { |r| [r] }.each_with_index do |row, index|
67
+ t.add_row row
68
+ t.add_separator unless rows.size - 1 == index
69
+ end
70
+ end
71
+ end
72
+
73
+ # Returns full namespace of a command (e.g. Gitlab::Client::Branches.cmd)
74
+ def namespace(cmd)
75
+ method_owners.select { |method| method[:name] == cmd }
76
+ .map { |method| "#{method[:owner]}.#{method[:name]}" }
77
+ .shift
78
+ end
79
+
80
+ # Massage output from 'ri'.
81
+ def change_help_output!(cmd, output_str)
82
+ output_str = +output_str
83
+ output_str.gsub!(/#{cmd}(\(.*?\))/m, "#{cmd}\\1")
84
+ output_str.gsub!(/,\s*/, ', ')
85
+
86
+ # Ensure @option descriptions are on a single line
87
+ output_str.gsub!(/\n\[/, " \[")
88
+ output_str.gsub!(/\s(@)/, "\n@")
89
+ output_str.gsub!(/(\])\n(:)/, '\\1 \\2')
90
+ output_str.gsub!(/(:.*)(\n)(.*\.)/, '\\1 \\3')
91
+ output_str.gsub!(/\{(.+)\}/, '"{\\1}"')
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ # Converts hashes to the objects.
5
+ class ObjectifiedHash
6
+ # Creates a new ObjectifiedHash object.
7
+ def initialize(hash)
8
+ @hash = hash
9
+ @data = hash.each_with_object({}) do |(key, value), data|
10
+ value = self.class.new(value) if value.is_a? Hash
11
+ value = value.map { |v| v.is_a?(Hash) ? self.class.new(v) : v } if value.is_a? Array
12
+ data[key.to_s] = value
13
+ end
14
+ end
15
+
16
+ # @return [Hash] The original hash.
17
+ def to_hash
18
+ hash
19
+ end
20
+ alias to_h to_hash
21
+
22
+ # @return [String] Formatted string with the class name, object id and original hash.
23
+ def inspect
24
+ "#<#{self.class}:#{object_id} {hash: #{hash.inspect}}"
25
+ end
26
+
27
+ def [](key)
28
+ data[key]
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :hash, :data
34
+
35
+ # Respond to messages for which `self.data` has a key
36
+ def method_missing(method_name, *args, &block)
37
+ if data.key?(method_name.to_s)
38
+ data[method_name.to_s]
39
+ elsif data.respond_to?(method_name)
40
+ warn 'WARNING: Please convert ObjectifiedHash object to hash before calling Hash methods on it.'
41
+ data.send(method_name, *args, &block)
42
+ else
43
+ super
44
+ end
45
+ end
46
+
47
+ def respond_to_missing?(method_name, include_private = false)
48
+ hash.keys.map(&:to_sym).include?(method_name.to_sym) || super
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ # Parses link header.
5
+ #
6
+ # @private
7
+ class PageLinks
8
+ HEADER_LINK = 'Link'
9
+ DELIM_LINKS = ','
10
+ LINK_REGEX = /<([^>]+)>; rel="([^"]+)"/.freeze
11
+ METAS = %w[last next first prev].freeze
12
+
13
+ attr_accessor(*METAS)
14
+
15
+ def initialize(headers)
16
+ link_header = headers[HEADER_LINK]
17
+
18
+ extract_links(link_header) if link_header && link_header =~ /(next|first|last|prev)/
19
+ end
20
+
21
+ private
22
+
23
+ def extract_links(header)
24
+ header.split(DELIM_LINKS).each do |link|
25
+ LINK_REGEX.match(link.strip) do |match|
26
+ url = match[1]
27
+ meta = match[2]
28
+ next if !url || !meta || METAS.index(meta).nil?
29
+
30
+ send("#{meta}=", url)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end