startling 0.0.2

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.
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