fs-gitlab 4.18.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +270 -0
- data/LICENSE.txt +24 -0
- data/README.md +260 -0
- data/exe/gitlab +11 -0
- data/lib/gitlab/api.rb +24 -0
- data/lib/gitlab/cli.rb +89 -0
- data/lib/gitlab/cli_helpers.rb +243 -0
- data/lib/gitlab/client/access_requests.rb +103 -0
- data/lib/gitlab/client/application_settings.rb +172 -0
- data/lib/gitlab/client/avatar.rb +21 -0
- data/lib/gitlab/client/award_emojis.rb +137 -0
- data/lib/gitlab/client/boards.rb +146 -0
- data/lib/gitlab/client/branches.rb +135 -0
- data/lib/gitlab/client/broadcast_messages.rb +75 -0
- data/lib/gitlab/client/build_variables.rb +135 -0
- data/lib/gitlab/client/builds.rb +108 -0
- data/lib/gitlab/client/commits.rb +216 -0
- data/lib/gitlab/client/container_registry.rb +85 -0
- data/lib/gitlab/client/deployments.rb +34 -0
- data/lib/gitlab/client/environments.rb +89 -0
- data/lib/gitlab/client/epic_issues.rb +23 -0
- data/lib/gitlab/client/epics.rb +73 -0
- data/lib/gitlab/client/events.rb +60 -0
- data/lib/gitlab/client/features.rb +48 -0
- data/lib/gitlab/client/group_badges.rb +88 -0
- data/lib/gitlab/client/group_boards.rb +141 -0
- data/lib/gitlab/client/group_labels.rb +88 -0
- data/lib/gitlab/client/group_milestones.rb +94 -0
- data/lib/gitlab/client/groups.rb +358 -0
- data/lib/gitlab/client/issue_links.rb +48 -0
- data/lib/gitlab/client/issues.rb +231 -0
- data/lib/gitlab/client/jobs.rb +250 -0
- data/lib/gitlab/client/keys.rb +29 -0
- data/lib/gitlab/client/labels.rb +88 -0
- data/lib/gitlab/client/lint.rb +19 -0
- data/lib/gitlab/client/markdown.rb +23 -0
- data/lib/gitlab/client/merge_request_approvals.rb +265 -0
- data/lib/gitlab/client/merge_requests.rb +386 -0
- data/lib/gitlab/client/milestones.rb +106 -0
- data/lib/gitlab/client/namespaces.rb +22 -0
- data/lib/gitlab/client/notes.rb +313 -0
- data/lib/gitlab/client/packages.rb +95 -0
- data/lib/gitlab/client/pipeline_schedules.rb +147 -0
- data/lib/gitlab/client/pipeline_triggers.rb +103 -0
- data/lib/gitlab/client/pipelines.rb +105 -0
- data/lib/gitlab/client/project_badges.rb +85 -0
- data/lib/gitlab/client/project_clusters.rb +83 -0
- data/lib/gitlab/client/project_release_links.rb +76 -0
- data/lib/gitlab/client/project_releases.rb +79 -0
- data/lib/gitlab/client/projects.rb +708 -0
- data/lib/gitlab/client/protected_tags.rb +59 -0
- data/lib/gitlab/client/remote_mirrors.rb +51 -0
- data/lib/gitlab/client/repositories.rb +113 -0
- data/lib/gitlab/client/repository_files.rb +131 -0
- data/lib/gitlab/client/repository_submodules.rb +27 -0
- data/lib/gitlab/client/resource_label_events.rb +82 -0
- data/lib/gitlab/client/resource_state_events.rb +57 -0
- data/lib/gitlab/client/runners.rb +211 -0
- data/lib/gitlab/client/search.rb +66 -0
- data/lib/gitlab/client/services.rb +53 -0
- data/lib/gitlab/client/sidekiq.rb +39 -0
- data/lib/gitlab/client/snippets.rb +95 -0
- data/lib/gitlab/client/system_hooks.rb +64 -0
- data/lib/gitlab/client/tags.rb +97 -0
- data/lib/gitlab/client/templates.rb +100 -0
- data/lib/gitlab/client/todos.rb +46 -0
- data/lib/gitlab/client/user_snippets.rb +114 -0
- data/lib/gitlab/client/users.rb +397 -0
- data/lib/gitlab/client/versions.rb +18 -0
- data/lib/gitlab/client/wikis.rb +79 -0
- data/lib/gitlab/client.rb +95 -0
- data/lib/gitlab/configuration.rb +57 -0
- data/lib/gitlab/error.rb +170 -0
- data/lib/gitlab/file_response.rb +48 -0
- data/lib/gitlab/help.rb +94 -0
- data/lib/gitlab/objectified_hash.rb +51 -0
- data/lib/gitlab/page_links.rb +35 -0
- data/lib/gitlab/paginated_response.rb +110 -0
- data/lib/gitlab/request.rb +109 -0
- data/lib/gitlab/shell.rb +83 -0
- data/lib/gitlab/shell_history.rb +57 -0
- data/lib/gitlab/version.rb +5 -0
- data/lib/gitlab.rb +56 -0
- 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
|
data/lib/gitlab/error.rb
ADDED
@@ -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
|
data/lib/gitlab/help.rb
ADDED
@@ -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
|