startling 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.ruby-gemset +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +78 -0
  7. data/Rakefile +15 -0
  8. data/bin/start +5 -0
  9. data/lib/generators/startling/configuration_generator.rb +78 -0
  10. data/lib/startling.rb +30 -0
  11. data/lib/startling/cache.rb +20 -0
  12. data/lib/startling/cli_options.rb +46 -0
  13. data/lib/startling/colorize_string.rb +58 -0
  14. data/lib/startling/command.rb +67 -0
  15. data/lib/startling/commands/base.rb +83 -0
  16. data/lib/startling/commands/check_for_local_mods.rb +17 -0
  17. data/lib/startling/commands/create_branch.rb +53 -0
  18. data/lib/startling/commands/create_pull_request.rb +35 -0
  19. data/lib/startling/commands/label_pull_request.rb +15 -0
  20. data/lib/startling/configuration.rb +106 -0
  21. data/lib/startling/git_local.rb +61 -0
  22. data/lib/startling/github.rb +16 -0
  23. data/lib/startling/github/api.rb +106 -0
  24. data/lib/startling/github/pull_request.rb +54 -0
  25. data/lib/startling/github/repo.rb +44 -0
  26. data/lib/startling/handlers/default_pull_request_handler.rb +21 -0
  27. data/lib/startling/handlers/pull_request_handler_base.rb +21 -0
  28. data/lib/startling/markdown.rb +7 -0
  29. data/lib/startling/shell.rb +11 -0
  30. data/lib/startling/version.rb +3 -0
  31. data/spec/spec_helper.rb +19 -0
  32. data/spec/startling/commands/base_spec.rb +32 -0
  33. data/spec/startling/configuration_spec.rb +127 -0
  34. data/spec/startling/git_local_spec.rb +22 -0
  35. data/spec/startling/github/pull_request_spec.rb +37 -0
  36. data/spec/startling_configuration_spec.rb +16 -0
  37. data/spec/startling_spec.rb +122 -0
  38. data/spec/support/dotenv.rb +2 -0
  39. data/spec/support/tokens.rb +5 -0
  40. data/spec/support/vcr.rb +16 -0
  41. data/spec/vcr_cassettes/bin_start_starts_stories.yml +564 -0
  42. data/spec/vcr_cassettes/bin_start_starts_stories_pr_body.yml +644 -0
  43. data/startling.gemspec +36 -0
  44. metadata +297 -0
@@ -0,0 +1,54 @@
1
+ module Startling
2
+ module Github
3
+ class PullRequest
4
+ attr_reader :attributes
5
+ attr_accessor :labels
6
+
7
+ def initialize(attributes, prefetch_data: true)
8
+ @attributes = attributes
9
+ prefetch_data if prefetch_data
10
+ end
11
+
12
+ def id
13
+ attributes.number
14
+ end
15
+
16
+ def title
17
+ attributes.title
18
+ end
19
+
20
+ def branch
21
+ attributes.head.ref
22
+ end
23
+
24
+ def in_progress?
25
+ label_names.any? { |label| label.match(/(WIP|REVIEW)/) }
26
+ end
27
+
28
+ def label_names
29
+ labels.map(&:name)
30
+ end
31
+
32
+ def url
33
+ attributes.rels[:html].href
34
+ end
35
+
36
+ def created_at
37
+ attributes.created_at
38
+ end
39
+
40
+ def updated_at
41
+ attributes.updated_at
42
+ end
43
+
44
+ def author
45
+ @author ||= attributes.user.rels[:self].get.data.name
46
+ end
47
+
48
+ private
49
+ def prefetch_data
50
+ author
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,44 @@
1
+ module Startling
2
+ module Github
3
+ class Repo
4
+ attr_reader :api, :name
5
+
6
+ def initialize(name, api)
7
+ @name = name
8
+ @api = api
9
+ end
10
+
11
+ def attributes
12
+ @attributes ||= api.repository_attributes(name)
13
+ end
14
+
15
+ def default_branch
16
+ attributes.default_branch
17
+ end
18
+
19
+ def pull_requests
20
+ @pull_requests ||= api.pull_requests(name)
21
+ end
22
+
23
+ def pull_request(branch)
24
+ pull_requests.find { |pr| pr.branch == branch }
25
+ end
26
+
27
+ def set_labels_for_issue(issue_id:, labels:)
28
+ labels = Array(labels)
29
+ api.set_labels_for_issue(repo_name: name, issue_id: issue_id, labels: labels)
30
+ end
31
+
32
+ def open_pull_request(title: nil, body: nil, branch: nil)
33
+ pull_request = api.open_pull_request(repo_name: name, destination_branch: default_branch,
34
+ branch: branch, title: title, body: body)
35
+ return pull_request if pull_request
36
+ pull_request(branch) # In case this is already open
37
+ end
38
+
39
+ def self.all
40
+ @all ||= Startling::REPOS.map { |name| new(name) }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ require_relative "pull_request_handler_base"
2
+
3
+ module Startling
4
+ module Handlers
5
+ class DefaultPullRequestHandler < PullRequestHandlerBase
6
+ def title
7
+ title = ask("Please input a pull request title: ")
8
+ abort "Title must be specified." if title.empty?
9
+ title
10
+ end
11
+
12
+ def body
13
+ Startling.pull_request_body
14
+ end
15
+
16
+ def commit_message
17
+ Startling.pull_request_commit_message
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Startling
2
+ module Handlers
3
+ class PullRequestHandlerBase
4
+ def initialize(args)
5
+ @args = args
6
+ end
7
+
8
+ def title
9
+ raise NotImplementedError
10
+ end
11
+
12
+ def body
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def commit_message
17
+ raise NotImplementedError
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module Startling
2
+ class Markdown
3
+ def self.escape(text)
4
+ text.gsub('[', '\[').gsub(']', '\]')
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Startling
2
+ class Shell
3
+ def self.run(command)
4
+ result = `#{command}`
5
+ unless $?.success?
6
+ exit 1
7
+ end
8
+ result
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Startling
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,19 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.run_all_when_everything_filtered = true
9
+ config.filter_run :focus
10
+
11
+ # Run specs in random order to surface order dependencies. If you find an
12
+ # order dependency and want to debug it, you can fix the order by providing
13
+ # the seed, which is printed after each run.
14
+ # --seed 1234
15
+ config.order = 'random'
16
+ end
17
+
18
+ require 'pry'
19
+ Dir['./spec/support/**/*.rb'].map {|f| require f}
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ require 'startling/commands/base'
4
+
5
+ describe Startling::Commands::Base do
6
+ it "should assigns key/values passed in as attributes" do
7
+ base = Startling::Commands::Base.new(foo: 'bar', baz: 'qux')
8
+ expect(base.foo).to eq 'bar'
9
+ expect(base.baz).to eq 'qux'
10
+ end
11
+
12
+ describe '#execute' do
13
+ subject { Startling::Commands::Base.new }
14
+ it 'should throws NotImplementedError' do
15
+ expect{ subject.execute }.to raise_error(NotImplementedError)
16
+ end
17
+ end
18
+
19
+ describe '#run' do
20
+ subject { Startling::Commands::Base.run(foo: 'bar')}
21
+
22
+ it 'should assign attributes and call execute' do
23
+ base = double :base_command
24
+ allow(Startling::Commands::Base).to receive(:new).and_return(base)
25
+
26
+ expect(Startling::Commands::Base).to receive(:new).with(foo: 'bar')
27
+ expect(base).to receive(:execute)
28
+
29
+ subject
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,127 @@
1
+ require 'spec_helper'
2
+ require 'startling'
3
+
4
+ module Startling
5
+ describe Configuration do
6
+ let(:configuration) { Configuration.new }
7
+
8
+ describe "Default settings" do
9
+ it "sets the default cache_dir to pwd" do
10
+ expect(configuration.cache_dir).to eql(Dir.pwd)
11
+ end
12
+
13
+ it "sets the default root_dir to pwd" do
14
+ expect(configuration.root_dir).to eql(Dir.pwd)
15
+ end
16
+
17
+ it "sets the default wip limit to WIP_LIMIT" do
18
+ expect(configuration.wip_limit)
19
+ .to eql(Configuration::DEFAULT_WIP_LIMIT)
20
+ end
21
+
22
+ it "sets the default repos to empty" do
23
+ expect(configuration.repos).to eql([])
24
+ end
25
+
26
+ it "sets the default pull request commit message" do
27
+ expect(configuration.pull_request_commit_message)
28
+ .to eql(Configuration::DEFAULT_COMMIT_MESSAGE)
29
+ end
30
+
31
+ it "sets the default pull request labels" do
32
+ expect(configuration.pull_request_labels).to eql([])
33
+ end
34
+ end
35
+
36
+ describe "#cache_dir" do
37
+ it "can set the value" do
38
+ configuration.cache_dir = "new dir"
39
+ expect(configuration.cache_dir).to eql("new dir")
40
+ end
41
+ end
42
+
43
+ describe "#root_dir" do
44
+ it "can set the value" do
45
+ configuration.root_dir = "new dir"
46
+ expect(configuration.root_dir).to eql("new dir")
47
+ end
48
+ end
49
+
50
+ describe "#wip_limit" do
51
+ it "can set the value" do
52
+ configuration.wip_limit = 6
53
+ expect(configuration.wip_limit).to eql(6)
54
+ end
55
+ end
56
+
57
+ describe "#repos" do
58
+ it "can set the value" do
59
+ configuration.repos << "repo path"
60
+ expect(configuration.repos).to eql(["repo path"])
61
+ end
62
+ end
63
+
64
+ describe "#pull_request_commit_message" do
65
+ it "can set the value" do
66
+ configuration.pull_request_commit_message = "The Commit"
67
+ expect(configuration.pull_request_commit_message).to eql("The Commit")
68
+ end
69
+ end
70
+
71
+ describe "#pull_request_labels" do
72
+ it "can set the value" do
73
+ configuration.pull_request_labels = ["WIP", "REVIEW"]
74
+ expect(configuration.pull_request_labels).to eql(["WIP", "REVIEW"])
75
+ end
76
+ end
77
+
78
+ describe ".load_configuration" do
79
+ let(:git) { double(GitLocal) }
80
+ let(:config_files) {
81
+ {
82
+ caps: "Startlingfile.rb",
83
+ lower: "startlingfile.rb"
84
+ }
85
+ }
86
+ let(:caps_config_file) { "Startlingfile.rb" }
87
+ let(:lower_config_file) { "startlingfile.rb" }
88
+
89
+ before do
90
+ allow(Startling::GitLocal).to receive(:new) { git }
91
+ allow(git).to receive(:project_root) { Dir.pwd }
92
+ end
93
+
94
+ after do
95
+ config_files.each do |_, v|
96
+ delete_config_file(v)
97
+ end
98
+ end
99
+
100
+ context "when no configuration file exists" do
101
+ it "uses default configuration" do
102
+ expect(Configuration.load_configuration).to eql(nil)
103
+ end
104
+ end
105
+
106
+ context "when a configuration file exists" do
107
+ it "loads configuration from Startlingfile.rb" do
108
+ create_config_file(config_files[:caps])
109
+ expect(Configuration.load_configuration).to eql(config_files[:caps])
110
+ end
111
+
112
+ it "loads configuration from startlingfile.rb" do
113
+ create_config_file(config_files[:lower])
114
+ expect(Configuration.load_configuration).to eql(config_files[:lower])
115
+ end
116
+ end
117
+ end
118
+
119
+ def create_config_file(config_file)
120
+ File.open(config_file, "w")
121
+ end
122
+
123
+ def delete_config_file(config_file)
124
+ File.delete(config_file) if File.exists? config_file
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+ require 'startling/git_local'
3
+
4
+ module Startling
5
+ describe GitLocal do
6
+ subject { GitLocal.new }
7
+
8
+ it "parses the repo name out of an https url" do
9
+ url = 'https://github.com/TeachingChannel/startling.git'
10
+ allow(subject).to receive(:remote_url) { url }
11
+
12
+ expect(subject.repo_name).to eq('TeachingChannel/startling')
13
+ end
14
+
15
+ it "parses the repo name out of an ssh url" do
16
+ url = 'git@github.com:TeachingChannel/startling.git'
17
+ allow(subject).to receive(:remote_url) { url }
18
+
19
+ expect(subject.repo_name).to eq('TeachingChannel/startling')
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+ require 'startling/github/pull_request'
3
+
4
+ module Startling
5
+ module Github
6
+ describe PullRequest, "#in_progress?" do
7
+
8
+ it "should be true for WIP label" do
9
+ pull_request = PullRequest.new(double(:attributes))
10
+ allow(pull_request).to receive(:label_names) { ["WIP"] }
11
+
12
+ expect(pull_request.in_progress?).to be_truthy
13
+ end
14
+
15
+ it "should be true for REVIEW label" do
16
+ pull_request = PullRequest.new(double(:attributes))
17
+ allow(pull_request).to receive(:label_names) { ["REVIEW"] }
18
+
19
+ expect(pull_request.in_progress?).to be_truthy
20
+ end
21
+
22
+ it "should be false if for other labels" do
23
+ pull_request = PullRequest.new(double(:attributes))
24
+ allow(pull_request).to receive(:label_names) { ["some other thing", "bug"] }
25
+
26
+ expect(pull_request.in_progress?).to be_falsey
27
+ end
28
+
29
+ it "should be false if no labels" do
30
+ pull_request = PullRequest.new(double(:attributes))
31
+ allow(pull_request).to receive(:label_names) { [] }
32
+
33
+ expect(pull_request.in_progress?).to be_falsey
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'startling'
3
+
4
+ module Startling
5
+ describe ".configure" do
6
+ before do
7
+ Startling.configure do |config|
8
+ config.cache_dir = "value"
9
+ end
10
+ end
11
+
12
+ it "sets the configuration attributes" do
13
+ expect(Startling.cache_dir).to eql("value")
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+ require 'startling'
4
+ require 'startling/git_local'
5
+ require 'startling/github'
6
+
7
+ describe "bin/start" do
8
+ let(:feature_name) { 'bin_start_starts_stories' }
9
+ let(:feature_branch) { "feature/#{feature_name}" }
10
+ let(:repo_default_branch) { 'master' }
11
+ let(:pull_request_body) { "This is a test body" }
12
+ let(:git) { Startling::GitLocal.new }
13
+ let(:repo) { Startling::Github.repo(git.repo_name) }
14
+ let(:pull_request) { repo.pull_request(feature_branch) }
15
+
16
+ before do
17
+ local_configuration = <<CONFIG
18
+ Startling.configure do |config|
19
+ # WIP Limit
20
+ # config.wip_limit = 4
21
+
22
+ # Repos to check against for WIP limit
23
+ # config.repos << 'substantial/startling-dev'
24
+
25
+ # Valid story estimations
26
+ # config.valid_estimates = [1, 2, 4, 8, 16, 32, 64, 128]
27
+
28
+ # Commands to be run before a story is stared
29
+ # config.hook_commands.before_story_start = [:check_wip]
30
+
31
+ # Command to be run after a story has started
32
+ # config.hook_commands.after_story_start = []
33
+
34
+ # Commands to be run before a pull request is created
35
+ # config.hook_commands.before_pull_request = []
36
+
37
+ # Commands to be run after a pull request is created
38
+ # config.hook_commands.after_pull_request = []
39
+
40
+ # Handler used to start a provider specific story related to the pull request
41
+ # config.story_handler = :pivotal_start
42
+
43
+ # Message for pull request commit
44
+ # config.pull_request_commit_message = "Startling"
45
+
46
+ # Message for pull request body
47
+ config.pull_request_body = "#{pull_request_body}"
48
+
49
+ # Labels for a pull request
50
+ # config.pull_request_labels = [WIP, REVIEW, HOLD]
51
+
52
+ # Handler used for setting the title and body of a pull request
53
+ config.pull_request_handler = :default_pull_request_handler
54
+ end
55
+ CONFIG
56
+
57
+ File.open('startlingfile.rb', 'w') { |file| file.write(local_configuration) }
58
+
59
+ Startling::Commands::Base.load_configuration
60
+
61
+ test_repo_path = "tmp/test_repo"
62
+ FileUtils.mkdir_p "tmp"
63
+
64
+ unless File.exists? "tmp/test_repo/.git"
65
+ `git clone git@github.com:substantial/startling-testing.git #{test_repo_path}`
66
+ end
67
+
68
+ File.write File.join(test_repo_path, ".github_access_token"), Tokens.github
69
+
70
+ FileUtils.cd test_repo_path
71
+
72
+ Startling.root_dir = Startling.cache_dir = "."
73
+
74
+ git.checkout_branch repo_default_branch
75
+ git.destroy_branch feature_branch
76
+ end
77
+
78
+ after do
79
+ FileUtils.cd "#{__dir__}/.."
80
+ FileUtils.rm "startlingfile.rb"
81
+ end
82
+
83
+ # VCR Preconditions:
84
+ # * There should be no open pull requests:
85
+ # https://github.com/substantial/startling-testing
86
+
87
+ it "starts stories from origin/master",
88
+ vcr: { cassette_name: "bin_start_starts_stories" } do
89
+
90
+ allow_any_instance_of(Startling::Handlers::DefaultPullRequestHandler)
91
+ .to receive(:ask)
92
+ .with("Please input a pull request title: ") { "The Title" }
93
+
94
+ allow_any_instance_of(Startling::Commands::CreateBranch)
95
+ .to receive(:ask)
96
+ .with("Enter branch name (enter for current branch): ") { feature_name }
97
+
98
+ command = Startling::Command.new(args: [])
99
+ command.execute
100
+
101
+ expect(git.remote_branches).to include feature_branch
102
+ expect(git.current_branch).to eq feature_branch
103
+ expect(repo.default_branch).to eq repo_default_branch
104
+ end
105
+
106
+ it "The pull request body is the same as the config",
107
+ vcr: { cassette_name: "bin_start_starts_stories_pr_body" } do
108
+
109
+ allow_any_instance_of(Startling::Handlers::DefaultPullRequestHandler)
110
+ .to receive(:ask)
111
+ .with("Please input a pull request title: ") { "The Title" }
112
+
113
+ allow_any_instance_of(Startling::Commands::CreateBranch)
114
+ .to receive(:ask)
115
+ .with("Enter branch name (enter for current branch): ") { feature_name }
116
+
117
+ command = Startling::Command.new(args: [])
118
+ command.execute
119
+
120
+ expect(pull_request.attributes.body).to eq pull_request_body
121
+ end
122
+ end