citizen-scripts 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,195 @@
1
+ require 'json'
2
+ require 'tempfile'
3
+
4
+ module CitizenCodeScripts
5
+ class Pivotal
6
+ attr_reader :token
7
+
8
+ def initialize(token)
9
+ @token = token
10
+ end
11
+
12
+ def request(url, method: "GET", body: nil)
13
+ cmd = %Q{curl --silent -X #{method} -H "X-TrackerToken: #{token}"}
14
+ cmd += %Q{ -H "Content-Type: application/json"}
15
+
16
+ if body
17
+ cmd += %Q{ -d '#{body}'}
18
+ end
19
+
20
+ cmd += %Q{ "#{url}"}
21
+
22
+ result = `#{cmd}`
23
+
24
+ JSON.parse(result.strip)
25
+ end
26
+ end
27
+
28
+ class Begin < Base
29
+ def self.description
30
+ "Starts your Pivotal Tracker story and opens a new PR on Github"
31
+ end
32
+
33
+ def self.help
34
+ <<-EOF
35
+ citizen begin #{colorize(:light_blue, "[story id] [branch name]")}
36
+
37
+ Example: $ #{colorize(:light_blue, 'citizen begin "#133717234"')}
38
+
39
+ This command will start your Pivotal Tracker story for you, open a pull
40
+ request on Github, and copy over the Pivotal Tracker story description to
41
+ the Github pull request description. As well, any tasks in your
42
+ Pivotal Tracker story will automatically become [x] checkbox tasks on the
43
+ Github PR.
44
+
45
+ * All branch names will be auto-converted to
46
+ kebab-case, lowercase
47
+
48
+ * Passing story id/branch name as arguments are optional - if
49
+ they are missing, you'll be prompted
50
+ EOF
51
+ end
52
+
53
+ def run
54
+ story_id, branch_name = argv
55
+
56
+ if !command?("hub")
57
+ abort <<-EOF
58
+ You need to install `hub` before you can use this program.
59
+
60
+ brew install hub
61
+ EOF
62
+ end
63
+
64
+ pivotal = Pivotal.new(git_config("user.pivotalApiToken"))
65
+ config_abort_if_blank!("user.pivotalApiToken", pivotal.token)
66
+
67
+ project_id = git_config("user.pivotalProjectId")
68
+ config_abort_if_blank!("user.pivotalProjectId", project_id)
69
+
70
+ story_id ||= prompt("Please paste the Pivotal story ID here")
71
+ story_id = story_id.gsub(/[^\d]/, '')
72
+
73
+ story_url = "https://www.pivotaltracker.com/services/v5"\
74
+ "/projects/#{project_id}/stories/#{story_id}"
75
+
76
+ story = pivotal.request(story_url)
77
+
78
+ default_branch_name = normalized_branch_name(story['name'])
79
+
80
+ branch_name ||= prompt("Please enter a branch name (#{default_branch_name})")
81
+
82
+ if branch_name.strip == ""
83
+ branch_name = default_branch_name
84
+ else
85
+ branch_name = normalized_branch_name(branch_name)
86
+ end
87
+
88
+ # Start the story
89
+ pivotal.request(story_url, method: "PUT", body: '{ "current_state":"started" }')
90
+
91
+ silent "git checkout master",
92
+ "git fetch origin",
93
+ "git reset --hard origin/master"
94
+
95
+ puts "==> Checking out #{branch_name}"
96
+
97
+ silent "git checkout -b #{branch_name}",
98
+ 'git commit --allow-empty -m "Initial commit for story #' + story_id + '"',
99
+ "git push origin #{branch_name}",
100
+ "git branch --set-upstream #{branch_name} origin/#{branch_name}"
101
+
102
+ tasks = pivotal.request(
103
+ "https://www.pivotaltracker.com/services/v5"\
104
+ "/projects/#{project_id}/stories/#{story_id}/tasks"
105
+ )
106
+
107
+ story_description = story['description']
108
+
109
+ if story_description.nil?
110
+ story_description = "_No story description given in Pivotal_"
111
+ end
112
+
113
+ description = <<-EOF
114
+ #{story['name']}
115
+
116
+ #{story['url']}
117
+
118
+ # Description
119
+
120
+ #{story_description}
121
+ EOF
122
+
123
+ description.strip!
124
+
125
+ unless tasks.empty?
126
+ description += "\n\n## TODO\n\n"
127
+
128
+ tasks.each do |task|
129
+ description += "- [ ] #{task['description']}\n"
130
+ end
131
+
132
+ description.strip!
133
+ end
134
+
135
+ puts "==> Opening pull request on GitHub"
136
+
137
+ tempfile = Tempfile.new('begin_pull_request')
138
+
139
+ begin
140
+ tempfile.write(description)
141
+ tempfile.close
142
+
143
+ labels = ['WIP', story['story_type']].join(',')
144
+
145
+ url = `hub pull-request -F #{tempfile.path} -l "#{labels}" -a "" -o`
146
+
147
+ # Copy it to your clipboard
148
+ system("echo #{url} | pbcopy")
149
+ puts url
150
+ ensure
151
+ tempfile.unlink
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ def silent(*cmds)
158
+ cmds.each { |cmd| system("#{cmd} >/dev/null 2>&1") }
159
+ end
160
+
161
+ def command?(name)
162
+ `which #{name}`
163
+ $?.success?
164
+ end
165
+
166
+ def git_config(key)
167
+ `git config --local --get #{key}`.strip
168
+ end
169
+
170
+ def config_abort_if_blank!(key, value)
171
+ if value.strip == ""
172
+ abort <<-EOF
173
+ You need to set the #{key} value in your git config!
174
+
175
+ git config --local --add #{key} [value]
176
+ EOF
177
+ end
178
+ end
179
+
180
+ def prompt(msg)
181
+ print "#{msg} > "
182
+ value = STDIN.gets.strip
183
+ puts
184
+ value
185
+ end
186
+
187
+ def normalized_branch_name(branch_name)
188
+ branch_name
189
+ .gsub(/[^\w\s-]/, '')
190
+ .gsub(/\s+/, '-')
191
+ .downcase
192
+ .gsub(/-*$/, '') # trailing dashes
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,39 @@
1
+ module CitizenCodeScripts::Colorize
2
+ extend self
3
+
4
+ def self.included(base)
5
+ colorize = self
6
+
7
+ base.class_eval do
8
+ extend colorize
9
+ end
10
+ end
11
+
12
+ COLOR_CODES = {
13
+ black: 30,
14
+ blue: 34,
15
+ brown: 33,
16
+ cyan: 36,
17
+ dark_gray: 90,
18
+ green: 32,
19
+ light_blue: 94,
20
+ light_cyan: 96,
21
+ light_gray: 37,
22
+ light_green: 92,
23
+ light_purple: 95,
24
+ light_red: 91,
25
+ light_yellow: 93,
26
+ purple: 35,
27
+ red: 31,
28
+ white: 97,
29
+ yellow: 33,
30
+
31
+ command: 96,
32
+ error: 91,
33
+ info: 93,
34
+ }
35
+
36
+ def colorize(color, string)
37
+ "\e[#{COLOR_CODES[color]}m#{string}\e[0m"
38
+ end
39
+ end
@@ -0,0 +1,179 @@
1
+ class CitizenCodeScripts::Doctor < CitizenCodeScripts::Base
2
+ class Check
3
+ include CitizenCodeScripts::Colorize
4
+
5
+ attr_reader :name, :command, :remedy, :problems
6
+
7
+ def initialize(name:, command:, remedy:)
8
+ @name = name
9
+ @command = command
10
+ @remedy = remedy
11
+ @problems = []
12
+ end
13
+
14
+ def run!
15
+ print "Checking: #{name}... "
16
+
17
+ success = if command.respond_to?(:call)
18
+ command.call
19
+ else
20
+ system "#{command} > /dev/null 2>&1"
21
+ end
22
+
23
+ if success
24
+ puts 'OK'
25
+ else
26
+ print colorize(:error, 'F')
27
+ fix = remedy.respond_to?(:join) ? remedy.join(" ") : remedy
28
+ puts "\n To fix: #{fix}\n\n"
29
+
30
+ problems << name
31
+ end
32
+ end
33
+ end
34
+
35
+ def self.description
36
+ "Checks the health of your development environment"
37
+ end
38
+
39
+ def initialize(*args)
40
+ super
41
+ @checks = []
42
+ end
43
+
44
+ def run
45
+ case argv.first
46
+ when "list"
47
+ list_default_checks
48
+ else
49
+ run_doctor
50
+ end
51
+ end
52
+
53
+ def self.help
54
+ "doctor - helps you diagnose any setup issues with this application\n"
55
+ end
56
+
57
+ def self.help_subcommands
58
+ {
59
+ "citizen doctor" => "runs health checks and gives a report",
60
+ "citizen doctor list" => "prints a list of default checks you can use when overriding doctor checks in your app"
61
+ }
62
+ end
63
+
64
+ def run_doctor
65
+ run_checks
66
+ report
67
+ end
68
+
69
+ def list_default_checks
70
+ puts "These default checks are available for use in your overrides:"
71
+ puts
72
+
73
+ default_checks.each do |name|
74
+ puts " - #{colorize(:light_blue, name)}"
75
+ end
76
+
77
+ puts
78
+ end
79
+
80
+ def check(**options)
81
+ check = Check.new(options)
82
+ @checks << check
83
+
84
+ check.run!
85
+ end
86
+
87
+ private
88
+
89
+ def run_checks
90
+ run_default_checks
91
+ end
92
+
93
+ def run_default_checks
94
+ default_checks.each do |check|
95
+ send(check)
96
+ end
97
+ end
98
+
99
+ def default_checks
100
+ %i[
101
+ check_postgres_launchctl
102
+ check_postgres_running
103
+ check_postgres_role
104
+ check_db_migrated
105
+ check_direnv_installed
106
+ check_phantomjs_installed
107
+ check_gemfile_dependencies
108
+ check_envrc_file_exists
109
+ ]
110
+ end
111
+
112
+ def check_postgres_launchctl
113
+ check \
114
+ name: "postgres launchctl script is linked",
115
+ command: "ls -1 ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist",
116
+ remedy: command("ln -sfv /usr/local/opt/postgresql/*.plist ~/Library/LaunchAgents")
117
+ end
118
+
119
+ def check_postgres_running
120
+ check \
121
+ name: "postgres is running",
122
+ command: "psql -l",
123
+ remedy: command("launchctl load ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist")
124
+ end
125
+
126
+ def check_postgres_role
127
+ check \
128
+ name: "postgres role exists",
129
+ command: "psql -U postgres -l",
130
+ remedy: command("createuser --superuser postgres")
131
+ end
132
+
133
+ def check_gemfile_dependencies
134
+ check \
135
+ name: "Gemfile dependencies are up to date",
136
+ command: "bundle check",
137
+ remedy: command("bundle")
138
+ end
139
+
140
+ def check_db_migrated
141
+ check \
142
+ name: "DB is migrated",
143
+ command: "source .envrc && rails runner 'ActiveRecord::Migration.check_pending!'",
144
+ remedy: command("rake db:migrate")
145
+ end
146
+
147
+ def check_direnv_installed
148
+ check \
149
+ name: "direnv installed",
150
+ command: "which direnv",
151
+ remedy: command("brew install direnv")
152
+ end
153
+
154
+ def check_phantomjs_installed
155
+ check \
156
+ name: "PhantomJS installed",
157
+ command: "which phantomjs",
158
+ remedy: command("brew install phantomjs")
159
+ end
160
+
161
+ def check_envrc_file_exists
162
+ check \
163
+ name: "envrc",
164
+ command: "stat .envrc",
165
+ remedy: "get your .envrc file from 1password"
166
+ end
167
+
168
+ def problems
169
+ @checks.map(&:problems).flatten
170
+ end
171
+
172
+ def report
173
+ exit problems.size
174
+ end
175
+
176
+ def command(s)
177
+ "run #{colorize :command, s}"
178
+ end
179
+ end
@@ -0,0 +1,53 @@
1
+ class CitizenCodeScripts::Help < CitizenCodeScripts::Base
2
+ def self.help
3
+ "Inception was a lame movie"
4
+ end
5
+
6
+ def self.help_subcommands
7
+ {}
8
+ end
9
+
10
+ def self.description
11
+ "Prints this message out"
12
+ end
13
+
14
+ def run
15
+ specific_script = argv[0]
16
+
17
+ if CitizenCodeScripts::Base.script_names.include?(specific_script)
18
+ script = CitizenCodeScripts::Base.scripts[specific_script]
19
+ puts full_help(script)
20
+ elsif specific_script
21
+ puts colorize(:red, "\"#{specific_script}\" does not exist, cannot display help")
22
+ else
23
+ basic_usage
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def full_help(script)
30
+ (script.help || "") + script.help_subcommands.map do |cmd, description|
31
+ " - #{colorize(:light_blue, cmd)} - #{description}"
32
+ end.join("\n")
33
+ end
34
+
35
+ def basic_usage
36
+ puts "Usage: citizen #{colorize(:light_blue, '[script name]')}"
37
+ puts
38
+ puts "Specify a specific script to run, options are: "
39
+ puts
40
+
41
+ names_and_descriptions = CitizenCodeScripts::Base.scripts.map do |name, script|
42
+ [colorize(:light_green, name), colorize(:light_blue, script.description)]
43
+ end
44
+
45
+ padding = names_and_descriptions.map { |name, _| name.length }.max
46
+
47
+ names_and_descriptions.each do |name, description|
48
+ puts " %-#{padding}s %s" % [name, description]
49
+ end
50
+
51
+ puts
52
+ end
53
+ end
@@ -0,0 +1,58 @@
1
+ require_relative './doctor'
2
+
3
+ class HerokuDoctor < CitizenCodeScripts::Doctor
4
+ def self.description
5
+ "Checks the health of your Heroku config"
6
+ end
7
+
8
+ def self.help
9
+ <<~TEXT
10
+ citizen heroku-doctor #{colorize(:light_blue, "environment")} = #{colorize(:yellow, "staging")}
11
+ TEXT
12
+ end
13
+
14
+ def run_checks
15
+ @env = argv[0] || "staging"
16
+ @heroku_app_name = app_names[@env.to_s]
17
+
18
+ puts "Environment: #{@env}"
19
+ puts "Heroku app: #{@heroku_app_name}"
20
+ puts
21
+
22
+ # this one should always be first - it will NEVER pass for the citizen-rails project which is OKAY!
23
+ check(
24
+ name: "The citizen.yml file has been configured properly",
25
+ command: "! grep 'citizen-rails-staging' citizen.yml",
26
+ remedy: "configure your citizen.yml file to have the correct app names set for all your Heroku environments"
27
+ )
28
+
29
+ check(
30
+ name: "app #{@heroku_app_name} exists",
31
+ command: "cat .git/config | grep git@heroku.com:#{@heroku_app_name}.git",
32
+ remedy: [command("heroku apps:create #{@heroku_app_name}"), "and/or", command("git remote add staging git@heroku.com:#{@heroku_app_name}.git")]
33
+ )
34
+
35
+ check_env("DEPLOY_TASKS", "db:migrate")
36
+ check_env("RAILS_ENV", "production")
37
+ check_env("DATABASE_URL", "postgres://", "go to https://dashboard.heroku.com/apps/#{@heroku_app_name}/resources and add the Heroku Postgress add-on")
38
+
39
+ check_buildpack("https://github.com/heroku/heroku-buildpack-ruby")
40
+ check_buildpack("https://github.com/gunpowderlabs/buildpack-ruby-rake-deploy-tasks")
41
+ end
42
+
43
+ def check_env(env_var, value, remedy=nil)
44
+ check(
45
+ name: env_var,
46
+ command: "heroku config:get #{env_var} -a #{@heroku_app_name} | grep '#{value}'",
47
+ remedy: remedy || command("heroku config:set #{env_var}=#{value} -a #{@heroku_app_name}")
48
+ )
49
+ end
50
+
51
+ def check_buildpack(url)
52
+ check(
53
+ name: url.split("/").last,
54
+ command: "heroku buildpacks -a #{@heroku_app_name} | grep '#{url}'",
55
+ remedy: command("heroku buildpacks:add #{url} -a #{@heroku_app_name}")
56
+ )
57
+ end
58
+ end
@@ -0,0 +1,22 @@
1
+ class CitizenCodeScripts::KillDbSessions < CitizenCodeScripts::Base
2
+ def self.description
3
+ "Kills active Postgres sessions"
4
+ end
5
+
6
+ def run
7
+ print "Loading Rails... "
8
+ require app_root.join("./config/environment")
9
+
10
+ puts "done"
11
+
12
+ print "Killing DB sessions... "
13
+ ActiveRecord::Base.connection.execute(<<-SQL)
14
+ SELECT pg_terminate_backend(pg_stat_activity.pid)
15
+ FROM pg_stat_activity
16
+ WHERE datname = current_database()
17
+ AND pid <> pg_backend_pid()
18
+ SQL
19
+
20
+ puts "done"
21
+ end
22
+ end
@@ -0,0 +1,40 @@
1
+ class Levenstein
2
+ def self.edit_distance(s, t)
3
+ m = s.length
4
+ n = t.length
5
+ return m if n == 0
6
+ return n if m == 0
7
+ d = Array.new(m+1) { Array.new(n+1) }
8
+
9
+ (0..m).each { |i| d[i][0] = i }
10
+ (0..n).each { |j| d[0][j] = j }
11
+ (1..n).each do |j|
12
+ (1..m).each do |i|
13
+ d[i][j] = if s[i-1] == t[j-1] # adjust index into string
14
+ d[i-1][j-1] # no operation required
15
+ else
16
+ [d[i-1][j]+1, # deletion
17
+ d[i][j-1]+1, # insertion
18
+ d[i-1][j-1]+1, # substitution
19
+ ].min
20
+ end
21
+ end
22
+ end
23
+ d[m][n]
24
+ end
25
+
26
+ def self.closest_match(needle, haystack)
27
+ min_distance = haystack.map(&:size).max
28
+ results = nil
29
+ haystack.each do |value|
30
+ distance = edit_distance(needle, value)
31
+ if distance < min_distance
32
+ min_distance = distance
33
+ results = [value]
34
+ elsif distance == min_distance
35
+ results << value
36
+ end
37
+ end
38
+ results
39
+ end
40
+ end
@@ -0,0 +1,23 @@
1
+ class CitizenCodeScripts::Pushit < CitizenCodeScripts::Base
2
+ def self.description
3
+ "Pulls code, runs test, pushes your code"
4
+ end
5
+
6
+ def self.help
7
+ <<-EOF
8
+ citizen pushit
9
+
10
+ Pulls the latest code, restarts, runs the tests, and pushes
11
+ your new code up.
12
+ EOF
13
+ end
14
+
15
+ def run
16
+ CitizenCodeScripts::Update.run
17
+ CitizenCodeScripts::Test.run
18
+
19
+ step "Pushing" do
20
+ system('git push')
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ class CitizenCodeScripts::Rspec < CitizenCodeScripts::Base
2
+ def self.description
3
+ "Runs your RSpec suite"
4
+ end
5
+
6
+ def run
7
+ begin
8
+ load(File.expand_path("./spring", __FILE__))
9
+ rescue LoadError
10
+ end
11
+
12
+ require 'bundler/setup'
13
+
14
+ step "Running RSpec" do
15
+ command = [Gem.bin_path('rspec-core', 'rspec')] + argv
16
+ system!(command.join(" "))
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ class CitizenCodeScripts::Test < CitizenCodeScripts::Base
2
+ def self.description
3
+ "Runs all test suites for CI/pushit"
4
+ end
5
+
6
+ def run
7
+ step "Running test suite" do
8
+ rspec
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def rspec
15
+ CitizenCodeScripts::Rspec.run
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ class CitizenCodeScripts::TodayI < CitizenCodeScripts::Base
2
+ def self.help
3
+ <<-EOF
4
+ citizen today-i
5
+
6
+ Prints out a list of commit message names that you worked on today.
7
+ EOF
8
+ end
9
+
10
+ def self.description
11
+ "Prints a list of commit msgs from today"
12
+ end
13
+
14
+ def run
15
+ date_string = Time.now.to_s.split(' ')[0]
16
+
17
+ lines = %x{
18
+ git log \
19
+ --date=local \
20
+ --oneline \
21
+ --after="#{date_string} 00:00" \
22
+ --before="#{date_string} 23:59"
23
+ }
24
+
25
+ lines.each_line do |line|
26
+ puts line.split(" ", 2)[1]
27
+ end
28
+ end
29
+ end