fs-gitlab 4.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|