gitlab_cli 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gitlab_cli/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "gitlab_cli"
8
+ gem.version = GitlabCli::VERSION
9
+ gem.authors = ["Drew Blessing"]
10
+ gem.email = ["drew.blessing@buckle.com"]
11
+ gem.description = %q{Gitlab Command Line Tool}
12
+ gem.summary = %q{Many people prefer to work from the CLI when possible. This tool aims to bring some of the GitLab functionality into the CLI to avoid repeated trips to the web UI}
13
+ gem.homepage = "https://github.com/drewblessing/gitlab-cli"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_runtime_dependency "thor", [">= 0.17.0", "<0.19"]
21
+ gem.add_runtime_dependency "json", "~> 1.7.7"
22
+ gem.add_runtime_dependency "rest-client", "~> 1.6.7"
23
+ end
@@ -0,0 +1,36 @@
1
+ require 'yaml'
2
+
3
+ module GitlabCli
4
+ class << self
5
+ attr_accessor :ui
6
+ end
7
+
8
+ autoload :Command, 'gitlab_cli/command'
9
+ autoload :Config, 'gitlab_cli/config'
10
+ autoload :Project, 'gitlab_cli/project'
11
+ autoload :Snippet, 'gitlab_cli/snippet'
12
+ autoload :User, 'gitlab_cli/user'
13
+ autoload :UI, 'gitlab_cli/ui'
14
+ autoload :Util, 'gitlab_cli/util'
15
+ autoload :Version, 'gitlab_cli/version'
16
+
17
+ GitlabCli.ui = STDOUT.tty? ? GitlabCli::UI::Color.new : GitlabCli::UI::Basic.new
18
+
19
+ CONFIG_PATH = '~/.gitlab.yml'
20
+ config = File.expand_path(CONFIG_PATH)
21
+ unless File.exist?(config)
22
+ File.open(File.expand_path(File.dirname(__FILE__) + "/../config.yml.sample", "r") )do |sample|
23
+ GitlabCli.ui.error "Could not find a config file. Create a config file, '#{CONFIG_PATH}', from the following sample config"
24
+ GitlabCli.ui.error "=" * 15 + " BEGIN (Do not copy this line) " + "=" * 15, :cyan
25
+ while (line = sample.gets)
26
+ GitlabCli.ui.error line, :white, false
27
+ end
28
+ GitlabCli.ui.error "=" * 15 + " END (Do not copy this line) " + "=" * 15, :cyan
29
+ end
30
+ exit(-1)
31
+ end
32
+ GitlabCli::Config.load(config)
33
+
34
+ # 3rd Party Gems
35
+ autoload :Shellwords, 'shellwords'
36
+ end
@@ -0,0 +1,94 @@
1
+ require 'thor'
2
+ require 'gitlab_cli'
3
+ require 'gitlab_cli/version'
4
+
5
+ module GitlabCli
6
+ class CLI < Thor
7
+ map "-v" => "version"
8
+ map "--version" => "version"
9
+
10
+ desc "--version", "print version"
11
+ long_desc <<-D
12
+ Print the Gitlab CLI tool version
13
+ D
14
+ def version
15
+ GitlabCli.ui.info "Gitlab CLI version %s" % [GitlabCli::VERSION]
16
+ end
17
+
18
+ desc "projects [OPTIONS]", "list projects"
19
+ long_desc <<-D
20
+ Get a list of projects. List will only include projects for which you have at least view privileges.\n
21
+ D
22
+ option :nopager, :desc => "Turn OFF pager output one time for this command", :required => false, :type => :boolean
23
+ option :pager, :desc => "Turn ON pager output one time for this command", :required => false, :type => :boolean
24
+ def projects
25
+ if options['pager'] && options['nopager']
26
+ GitlabCli.ui.error "Cannot specify --nopager and --pager options together. Choose one."
27
+ exit 1
28
+ end
29
+
30
+ projects = GitlabCli::Util::Projects.get_all
31
+ formatted_projects = ""
32
+ pager = ENV['pager'] || 'less'
33
+
34
+ projects.each do |p|
35
+ formatted_projects << "%s:\t%s\n" % [p.id, p.path_with_namespace]
36
+ end
37
+
38
+ if ((GitlabCli::Config[:display_results_in_pager] && !options['nopager']) || options['pager'])
39
+ unless system("echo %s | %s" % [Shellwords.escape(formatted_projects), pager])
40
+ GitlabCli.ui.error "Problem displaying projects in pager"
41
+ exit 1
42
+ end
43
+ else
44
+ GitlabCli.ui.info formatted_projects
45
+ end
46
+ end
47
+
48
+ desc "snippets [PROJECT] [OPTIONS]", "list snippets for a project"
49
+ long_desc <<-D
50
+ Get a list of snippets for a particular project. \n
51
+
52
+ [PROJECT] may be specified as [NAMESPACE]/[PROJECT] or [PROJECT_ID]. Use 'gitlab projects' to see a list of projects with their namespace and id.\n
53
+ D
54
+ option :nopager, :desc => "Turn OFF pager output one time for this command", :required => false, :type => :boolean
55
+ option :pager, :desc => "Turn ON pager output one time for this command", :required => false, :type => :boolean
56
+ def snippets(project)
57
+ if options['pager'] && options['nopager']
58
+ GitlabCli.ui.error "Cannot specify --nopager and --pager options together. Choose one."
59
+ exit 1
60
+ end
61
+
62
+ snippets = GitlabCli::Util::Snippets.get_all(project)
63
+ formatted_snippets = ""
64
+ pager = ENV['pager'] || 'less'
65
+
66
+ snippets.each do |s|
67
+ formatted_snippets << "%s:\t%s - %s\n" % [s.id, s.title, s.file_name]
68
+ end
69
+ GitlabCli.ui.info "This project does not have any snippets.\n" if snippets.size == 0
70
+
71
+ if ((GitlabCli::Config[:display_results_in_pager] && !options['nopager']) || options['pager'])
72
+ unless system("echo %s | %s" % [Shellwords.escape(formatted_snippets), pager])
73
+ GitlabCli.ui.error "Problem displaying snippets in pager"
74
+ exit 1
75
+ end
76
+ else
77
+ GitlabCli.ui.info formatted_snippets
78
+ end
79
+
80
+ end
81
+
82
+ desc "snippet [SUBCOMMAND]", "perform an action on a snippet"
83
+ long_desc <<-D
84
+ Perform an action on a snippet. To see available subcommands use 'gitlab snippet help.'
85
+ D
86
+ subcommand "snippet", GitlabCli::Command::Snippet
87
+
88
+ desc "project [SUBCOMMAND]", "perform an action on a project"
89
+ long_desc <<-D
90
+ Perform an action on a project. To see available subcommands use 'gitlab project help.'
91
+ D
92
+ subcommand "project", GitlabCli::Command::Project
93
+ end
94
+ end
@@ -0,0 +1,9 @@
1
+ module GitlabCli
2
+ module Command
3
+ autoload :Project, 'gitlab_cli/command/project'
4
+ autoload :Snippet, 'gitlab_cli/command/snippet'
5
+
6
+ autoload :Time, 'time'
7
+ end
8
+ end
9
+
@@ -0,0 +1,35 @@
1
+ module GitlabCli
2
+ module Command
3
+ class Project < Thor
4
+ def self.banner(task, namespace = true, subcommand = true)
5
+ "#{basename} #{task.formatted_usage(self, true, subcommand)}"
6
+ end
7
+
8
+ ## INFO
9
+ desc "info [PROJECT]", "view detailed info for a project"
10
+ long_desc <<-D
11
+ View detailed information about a project.\n
12
+ [PROJECT] may be specified as [NAMESPACE]/[PROJECT] or [PROJECT_ID]. Use 'gitlab projects' to see a list of projects with their namespace and id.
13
+ D
14
+ def info(project)
15
+ project = GitlabCli::Util::Project.get(project)
16
+
17
+ ui = GitlabCli.ui
18
+
19
+ ui.info "Project ID: %s" % [project.id]
20
+ ui.info "Name: %s" % [project.name]
21
+ ui.info "Path w/ Namespace: %s" % [project.path_with_namespace]
22
+ ui.info "Project URL: %s" % [project.project_url]
23
+ ui.info "Description: %s" % [project.description.nil? || project.description.empty? ? "N/A" : project.description]
24
+ ui.info "Default Branch: %s" % [project.default_branch.nil? ? "N/A" : project.default_branch]
25
+ ui.info "Owner: %s <%s>" % [project.owner.name, project.owner.email]
26
+ ui.info "Public?: %s" % [project.public.to_s]
27
+ ui.info "Issues enabled?: %s" % [project.issues_enabled.to_s]
28
+ ui.info "Merge Requests enabled?: %s" % [project.merge_requests_enabled.to_s]
29
+ ui.info "Wall enabled?: %s" % [project.wall_enabled.to_s]
30
+ ui.info "Wiki enabled?: %s" % [project.wiki_enabled.to_s]
31
+ ui.info "Created at: %s" % [Time.parse(project.created_at)]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,134 @@
1
+ module GitlabCli
2
+ module Command
3
+ class Snippet < Thor
4
+ map "save" => "download"
5
+
6
+ def self.banner(task, namespace = true, subcommand = true)
7
+ "#{basename} #{task.formatted_usage(self, true, subcommand)}"
8
+ end
9
+
10
+ # ADD
11
+ desc "add [PROJECT] [FILE] [OPTIONS]", "add a snippet"
12
+ long_desc <<-D
13
+ Add a snippet to a project. You may specify a file to create a snippet from or you may pipe content from cat.
14
+ [PROJECT] may be specified as [NAMESPACE]/[PROJECT] or [PROJECT_ID]. Use 'gitlab projects' to see a list of projects with their namespace and id.
15
+
16
+ $ gitlab snippet add namespace/project file1.txt
17
+
18
+ $ cat file1.txt | gitlab snippet add namespace/project
19
+ D
20
+ option :title, :desc => "The title to use for the new snippet", :required => true, :type => :string, :aliases => ["-t"]
21
+ option :file_name, :desc => "A file name for this snippet", :required => true, :type => :string, :aliases => ["-n", "-f"]
22
+ def add(project, file=nil)
23
+ snippet = GitlabCli::Util::Snippet.create(project, options['title'], options['file_name'], file)
24
+
25
+ GitlabCli.ui.success "Snippet created."
26
+ GitlabCli.ui.info "ID: %s" % [snippet.id]
27
+ GitlabCli.ui.info "URL: %s" % [snippet.view_url]
28
+ end
29
+
30
+ ## VIEW
31
+ desc "view [PROJECT] [SNIPPET_ID]", "view a snippet"
32
+ long_desc <<-D
33
+ View the content of a snippet. Content will be displayed in the default pager or in "less."
34
+
35
+ [PROJECT] may be specified as [NAMESPACE]/[PROJECT] or [PROJECT_ID]. Use 'gitlab projects' to see a list of projects with their namespace and id. [SNIPPET_ID] must be specified as the id of the snippet. Use 'gitlab snippets [PROJECT]' command to view the snippets available for a project.
36
+
37
+ $ gitlab snippet view namespace/project 6
38
+
39
+ $ gitlab snippet view 10 6
40
+ D
41
+ def view(project, snippet)
42
+ snippet = GitlabCli::Util::Snippet.view(project, snippet)
43
+
44
+ pager = ENV['pager'] || 'less'
45
+
46
+ unless system("echo %s | %s" % [Shellwords.escape(snippet), pager])
47
+ GitlabCli.ui.error "Problem displaying snippet"
48
+ exit 1
49
+ end
50
+ end
51
+
52
+ ## EDIT
53
+ desc "edit [PROJECT] [SNIPPET_ID]", "edit a snippet"
54
+ long_desc <<-D
55
+ Edit a snippet. Snippet will open in your default text editor or in "vi."
56
+
57
+ [PROJECT] may be specified as [NAMESPACE]/[PROJECT] or [PROJECT_ID]. Use 'gitlab projects' to see a list of projects with their namespace and id. [SNIPPET_ID] must be specified as the id of the snippet. Use 'gitlab snippets [PROJECT]' command to view the snippets available for a project.
58
+
59
+ $ gitlab snippet edit namespace/project 6
60
+
61
+ $ gitlab snippet edit 10 6
62
+ D
63
+ def edit(project, snippet)
64
+ snippet_obj = GitlabCli::Util::Snippet.get(project, snippet)
65
+ snippet_code = GitlabCli::Util::Snippet.view(project, snippet)
66
+
67
+ editor = ENV['editor'] || 'vi'
68
+
69
+ temp_file_path = "/tmp/snippet.%s" % [rand]
70
+ File.open(temp_file_path, 'w') { |file| file.write(snippet_code) }
71
+
72
+ system("vi %s" % [temp_file_path])
73
+
74
+ snippet_code = File.read(temp_file_path)
75
+
76
+ snippet = GitlabCli::Util::Snippet.update(project, snippet_obj, snippet_code)
77
+ GitlabCli.ui.success "Snippet updated."
78
+ GitlabCli.ui.info "URL: %s" % [snippet.view_url]
79
+ end
80
+
81
+ ## DELETE
82
+ desc "delete [PROJECT] [SNIPPET_ID]", "delete a snippet"
83
+ long_desc <<-D
84
+ Delete a snippet. \n
85
+ [PROJECT] may be specified as [NAMESPACE]/[PROJECT] or [PROJECT_ID]. Use 'gitlab projects' to see a list of projects with their namespace and id. [SNIPPET_ID] must be specified as the id of the snippet. Use 'gitlab snippets [PROJECT]' command to view the snippets available for a project.
86
+
87
+ $ gitlab snippet delete namespace/project 6
88
+
89
+ $ gitlab snippet delete 10 6
90
+ D
91
+ option :yes, :desc => "Delete without asking for confirmation", :required => false, :type => :boolean, :aliases => ["-y"]
92
+ def delete(project, snippet)
93
+ response = GitlabCli.ui.yes? "Are you sure you want to delete this snippet? (Yes\\No)" unless options['yes']
94
+ exit unless options['yes'] || response
95
+
96
+ snippet = GitlabCli::Util::Snippet.delete(project, snippet)
97
+
98
+ GitlabCli.ui.success "Successfully deleted the snippet."
99
+ end
100
+
101
+ ## INFO
102
+ desc "info [PROJECT] [SNIPPET_ID]", "view detailed info for a snippet"
103
+ long_desc <<-D
104
+ View detailed information about a snippet.\n
105
+ [PROJECT] may be specified as [NAMESPACE]/[PROJECT] or [PROJECT_ID]. Use 'gitlab projects' to see a list of projects with their namespace and id. [SNIPPET_ID] must be specified as the id of the snippet. Use 'gitlab snippets [PROJECT]' command to view the snippets available for a project.
106
+ D
107
+ def info(project, snippet)
108
+ snippet = GitlabCli::Util::Snippet.get(project, snippet)
109
+
110
+ ui = GitlabCli.ui
111
+
112
+ ui.info "Snippet ID: %s" % [snippet.id]
113
+ ui.info "Title: %s" % [snippet.title]
114
+ ui.info "File Name: %s" % [snippet.file_name]
115
+ ui.info "Author: %s <%s>" % [snippet.author.name, snippet.author.email]
116
+ ui.info "Created at: %s" % [Time.parse(snippet.created_at)]
117
+ ui.info "Updated at: %s" % [Time.parse(snippet.updated_at)]
118
+ ui.info "Expires at: %s" % [snippet.expires_at.nil? ? "Never" : Time.parse(snippet.expires_at)]
119
+ end
120
+
121
+ ## DOWNLOAD
122
+ desc "download|save [PROJECT] [SNIPPET_ID] [FILE]", "download/save a snippet locally"
123
+ long_desc <<-D
124
+ Download/Save the contents of a snippet in a local file\n
125
+ [PROJECT] may be specified as [NAMESPACE]/[PROJECT] or [PROJECT_ID]. Use 'gitlab projects' to see a list of projects with their namespace and id. [SNIPPET_ID] must be specified as the id of the snippet. Use 'gitlab snippets [PROJECT]' command to view the snippets available for a project.
126
+ D
127
+ def download(project, snippet, file_path)
128
+ snippet = GitlabCli::Util::Snippet.download(project, snippet, file_path)
129
+
130
+ GitlabCli.ui.success "Snippet file saved successfully.\n"
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,31 @@
1
+ module GitlabCli
2
+ class Config
3
+ class << self
4
+ def load(source)
5
+ @config = {:gitlab_url => nil,:private_token => nil,:display_results_in_pager => false}
6
+
7
+ if source.is_a?(String)
8
+ raise "Config file #{source} not found" unless File.exist?(source)
9
+
10
+ config = YAML.load_file(source)
11
+ @config.merge! config if config
12
+
13
+ elsif source.is_a?(Hash)
14
+ @config.merge! source
15
+ end
16
+ end
17
+
18
+ def include?(key)
19
+ @config.include?(key)
20
+ end
21
+
22
+ def [](key)
23
+ @config[key]
24
+ end
25
+
26
+ def to_yaml
27
+ @config.to_yaml
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ module GitlabCli
2
+ class Project
3
+ attr_accessor :id, :name, :description, :default_branch, :public, :path, :path_with_namespace, :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at, :project_url, :owner
4
+
5
+ def initialize(id, name, description, default_branch, public, path, path_with_namespace, issues_enabled, merge_requests_enabled, wall_enabled, wiki_enabled, created_at, owner=nil)
6
+ @id = id
7
+ @name = name
8
+ @description = description
9
+ @default_branch = default_branch
10
+ @public = public
11
+ @path = path
12
+ @path_with_namespace = path_with_namespace
13
+ @issues_enabled = issues_enabled
14
+ @merge_requests_enabled = merge_requests_enabled
15
+ @wall_enabled = wall_enabled
16
+ @wiki_enabled = wiki_enabled
17
+ @created_at = created_at
18
+ @project_url = get_project_url
19
+
20
+ @owner = owner.class == 'Gitlab::User' || owner.nil? ? owner : parse_owner(owner)
21
+ end
22
+
23
+ private
24
+ def get_project_url
25
+ URI.join(GitlabCli::Config[:gitlab_url],@path_with_namespace)
26
+ end
27
+
28
+ private
29
+ def parse_owner(owner)
30
+ GitlabCli::User.new(owner['id'],owner['username'],owner['email'],owner['name'],owner['blocked'],owner['created_at'])
31
+ end
32
+
33
+ end
34
+ end
35
+
@@ -0,0 +1,31 @@
1
+ module GitlabCli
2
+ class Snippet
3
+ attr_accessor :id, :title, :file_name, :expires_at, :updated_at, :created_at, :project_id, :view_url, :author
4
+
5
+ def initialize(id, title, file_name, expires_at, updated_at, created_at, project_id, author=nil)
6
+ @id = id
7
+ @title = title
8
+ @file_name = file_name
9
+ @expires_at = expires_at
10
+ @updated_at = updated_at
11
+ @created_at = created_at
12
+
13
+ @project_id = project_id
14
+ @view_url = get_view_url
15
+
16
+ @author = author.class == 'Gitlab::User' || author.nil? ? author : parse_author(author)
17
+ end
18
+
19
+ private
20
+ def get_view_url
21
+ project_path_with_namespace = GitlabCli::Util::Project.get_project_path_with_namespace(@project_id)
22
+ URI.join(GitlabCli::Config[:gitlab_url],"%s/snippets/%s" % [project_path_with_namespace,@id.to_s])
23
+ end
24
+
25
+ private
26
+ def parse_author(author)
27
+ GitlabCli::User.new(author['id'],author['username'],author['email'],author['name'],author['blocked'],author['created_at'])
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,98 @@
1
+ require 'thor/shell/basic'
2
+ require 'thor/shell/color'
3
+
4
+ ## Concepts borrowed from Vagrant UI class.
5
+
6
+ module GitlabCli
7
+ module UI
8
+ class Interface
9
+ #[:ask, :warn, :error, :info, :success].each do |method|
10
+ # define_method(method) do |message, *opts|
11
+ # # Log normal console messages
12
+ # @logger.info { "#{method}: #{message}" }
13
+ # end
14
+ #end
15
+ end
16
+
17
+ class Basic < Interface
18
+ def initialize
19
+ #super
20
+
21
+ @shell = Thor::Shell::Basic.new
22
+ end
23
+
24
+ # Use some light meta-programming to create the various methods to
25
+ # output text to the UI. These all delegate the real functionality
26
+ # to `say`.
27
+ [:info, :warn, :error, :success].each do |method|
28
+ class_eval <<-CODE
29
+ def #{method}(message)
30
+ #super(message)
31
+ @shell.say(message)
32
+ end
33
+ CODE
34
+ end
35
+
36
+ def ask(message, opts=nil)
37
+ #super(message)
38
+
39
+ # We can't ask questions when the output isn't a TTY.
40
+ #raise Errors::UIExpectsTTY if !$stdin.tty? && !Vagrant::Util::Platform.cygwin?
41
+
42
+ # Setup the options so that the new line is suppressed
43
+ #opts ||= {}
44
+ #opts[:new_line] = false if !opts.has_key?(:new_line)
45
+ #opts[:prefix] = false if !opts.has_key?(:prefix)
46
+
47
+ # Output the data
48
+ #@shell.ask(:info, message, opts)
49
+
50
+ # Get the results and chomp off the newline. We do a logical OR
51
+ # here because `gets` can return a nil, for example in the case
52
+ # that ctrl-D is pressed on the input.
53
+ @shell.ask message
54
+ end
55
+
56
+ def yes?(message, color=nil)
57
+ @shell.yes? message, color
58
+ end
59
+ end
60
+
61
+ class Color < Basic
62
+ def initialize
63
+ #super
64
+
65
+ @shell = Thor::Shell::Color.new
66
+ end
67
+
68
+ # Terminal colors
69
+ COLORS = {
70
+ :clear => "\e[0m",
71
+ :red => "\e[31m",
72
+ :green => "\e[32m",
73
+ :yellow => "\e[33m"
74
+ }
75
+
76
+ # Mapping between type of message and the color to output
77
+ COLOR_MAP = {
78
+ :info => nil,
79
+ :warn => :yellow,
80
+ :error => :red,
81
+ :success => :green
82
+ }
83
+
84
+ # Use some light meta-programming to create the various methods to
85
+ # output text to the UI. These all delegate the real functionality
86
+ # to `say`.
87
+ [:info, :warn, :error, :success].each do |method|
88
+ class_eval <<-CODE
89
+ def #{method}(message,color=nil,force_new_line = (message.to_s !~ /( |\t)\Z/))
90
+ say_color = color.nil? ? COLOR_MAP[:#{method}] : color
91
+ #super(message)
92
+ @shell.say(message,say_color,force_new_line)
93
+ end
94
+ CODE
95
+ end
96
+ end
97
+ end
98
+ end