mob_rotation 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4eac9d67bd7b967609ce166c6785f810ff8e7310
4
+ data.tar.gz: 8921a18fe1c2501f1440ad93c75deba49583e9b0
5
+ SHA512:
6
+ metadata.gz: 5440387200cf66a07f8b019e24a2ab08efc448837c8e6859314bd8e0839acc2f54b917f21dd511460db2a2440330edaf3790f7f36efe7c07f69955f83a13c0f0
7
+ data.tar.gz: 27b0e608088f922085c72017a0f7543520f49b7dd92753735c3d8582e1d979245ae8e03b131d60f8609d150b85e9a8a31e98a6dba265be07285c30d98592794e
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ *~
15
+ *_test.*
16
+ rotation.txt
17
+ test*.txt
18
+ results.txt
19
+ rotate.txt
20
+ \#*\#
21
+ .\#*
22
+ tmp
23
+ vendor
24
+ *.swp
25
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.rubocop.yml ADDED
@@ -0,0 +1,33 @@
1
+ # Default settings here:
2
+ # https://github.com/bbatsov/rubocop/blob/master/config/default.yml
3
+ # Find enabled settings here:
4
+ # https://github.com/bbatsov/rubocop/blob/master/config/enabled.yml
5
+ # We also prefer `or` and `and` for control flow:
6
+ # http://devblog.avdi.org/2014/08/26/how-to-use-rubys-english-andor-operators-without-going-nuts/
7
+ AllCops:
8
+ Exclude:
9
+ - bin/**
10
+ - config/**/*
11
+ - script/**
12
+ # Follow the Jim Weirich school of thought on usage of do/end vs. {...}:
13
+ # http://devblog.avdi.org/2011/07/26/the-procedurefunction-block-convention-in-ruby/
14
+ Blocks:
15
+ Enabled: false
16
+ CollectionMethods:
17
+ PreferredMethods:
18
+ map: "collect"
19
+ find: "detect"
20
+ find_all: "select"
21
+ reduce: "inject"
22
+ Documentation:
23
+ Enabled: false
24
+ EachWithObject:
25
+ Enabled: false
26
+ Lambda:
27
+ Enabled: false
28
+ RegexpLiteral:
29
+ MaxSlashes: 0
30
+ StringLiterals:
31
+ EnforcedStyle: double_quotes
32
+ TrailingBlankLines:
33
+ EnforcedStyle: final_newline
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.1.2"
4
+ script: bundle exec rspec spec
data/Changelog.md ADDED
@@ -0,0 +1,4 @@
1
+ - Show the Driver and Navigator
2
+ - Add mobsters
3
+ - Remove mobsters
4
+ - Create CLI program
data/Contributors.md ADDED
@@ -0,0 +1,29 @@
1
+ * [Abhishek Bose](http://abhishekbose.com)
2
+ * [Alex Harms](http://twitter.com/onealexharms)
3
+ * [Alistair Cockburn](http://alistair.cockburn.us)
4
+ * [Amitai Schlair](http://www.schmonz.com)
5
+ * [Andrew Broman](http://github/afbroman)
6
+ * [Ben Thompson](http://github.com/kotay)
7
+ * [Dan Wagner](http://github.com/danwagnerco)
8
+ * [Harri Jauri](http://twitter.com/harrijauri)
9
+ * [Jacqueline S. Homan](http://github.com/jacqueline-homan)
10
+ * [Jared Smith](http://sublimecoding.com)
11
+ * [J. B. Rainsberger](http://jbrains.ca)
12
+ * [Jen Diamond](http://jendiamond.com)
13
+ * [Jonathan Whittington](http://github.com/ixil)
14
+ * Kocur4d
15
+ * [Lucas Mendelowski](http://github.com/lowski)
16
+ * [Luisa Lima](http://github.com/luisalima)
17
+ * [Luke DeWitt](http://github.com/ledewitt)
18
+ * [Luqi Pan](https://github.com/LuqiPan)
19
+ * [Mark](https://github.com/Digital-Learner)
20
+ * [Mike Bonifer](http://gamechangers.com)
21
+ * [Pat Maddox](http://patmaddox.com)
22
+ * [Paul Yeoh](http://github.com/paul-ylz)
23
+ * [Philip Coates](http://github.com/pcoates99)
24
+ * [Ralph Hafliger](http://github.com/heroturtle)
25
+ * [Rob Wilkinson](http://github.com/robawilkinson)
26
+ * [Russell Baker](http://twitter.com/_russellb)
27
+ * [Sean Miller](http://github.com/smiller)
28
+ * [Willem Larsen](http://github.com/willemlarsen)
29
+ * [Woody Zuill](http://zuill.us/WoodyZuill/)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mob_rotation.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Pat Maddox
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ Mob Rotation
2
+ ============
3
+
4
+ ### Features
5
+ * Command line interface to simulate the [mob programming][mob_wiki_link] environment
6
+ * Timer funtionality to indicate rotation
7
+ * Ability to tell who is the driver, navigator and mobsters
8
+
9
+ ### Instructions For Use
10
+ TODO
11
+
12
+ ### Gems and Dependencies
13
+ * Ruby 2.1.2
14
+ * RSpec
15
+
16
+ ### Upcoming Features
17
+ * Timer
18
+ * Randomization of Driver/Navigator
19
+ * Browser-based GUI
20
+
21
+ ### Additional Resources
22
+ * <http://mobprogramming.org/>
23
+
24
+ ### Credits
25
+ TODO
26
+
27
+ [mob_wiki_link]: http://en.wikipedia.org/wiki/Mob_programming
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/mob_rotation ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ require "mob_rotation"
3
+
4
+ file_name = ENV["DB_FILE"] || "rotate.txt"
5
+ command = ARGV[0] || "show"
6
+ mobster_names = ARGV[1..-1]
7
+ git_dir = ENV["MOB_GIT_DIR"] || File.join(Dir.pwd, ".git")
8
+
9
+ mob_rotator = MobRotation::Rotation.new(
10
+ MobRotation::Database.new(file_name),
11
+ git_dir
12
+ )
13
+ mob_rotator.command_router(command, mobster_names)
@@ -0,0 +1,7 @@
1
+ require "mob_rotation/version"
2
+ require "mob_rotation/database"
3
+ require "mob_rotation/rotation"
4
+
5
+ # Manage a mob list during a mobbing session
6
+ module MobRotation
7
+ end
@@ -0,0 +1,29 @@
1
+ module MobRotation
2
+ module CommandRouting
3
+ COMMAND_MAPPINGS = {}
4
+
5
+ module ClassMethods
6
+ def define_command(name, &block)
7
+ CommandRouting::COMMAND_MAPPINGS[name] = block
8
+ end
9
+ end
10
+
11
+ def command_router(command, mobster_names)
12
+ command_implementation = CommandRouting::COMMAND_MAPPINGS.fetch(command) {
13
+ lambda { |command|
14
+ inform_lovely_user(command)
15
+ }
16
+ }
17
+ case command_implementation.arity
18
+ when -2
19
+ instance_exec(command, *mobster_names, &command_implementation)
20
+ when -1
21
+ instance_exec(*mobster_names, &command_implementation)
22
+ when 1
23
+ instance_exec(command, &command_implementation)
24
+ else
25
+ instance_exec(&command_implementation)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ require "fileutils"
2
+
3
+ module MobRotation
4
+ class Database
5
+ def sanitized_entries_in
6
+ each_database_entry(@filename) do |entry|
7
+ yield(entry.to_s.strip)
8
+ end
9
+ end
10
+
11
+ def initialize(filename)
12
+ @filename = filename
13
+ FileUtils.touch(filename) unless File.exist?(filename)
14
+ end
15
+
16
+ def write(mobsters)
17
+ File.open(@filename, "w") do |file|
18
+ mobsters.each do |mobster|
19
+ file << format_mobster(mobster.name, mobster.email) << "\n"
20
+ end
21
+ end
22
+ end
23
+
24
+ def format_mobster(name, email)
25
+ name + (" <#{email}>" if email && !email.empty?).to_s
26
+ end
27
+
28
+ def each_database_entry(filename)
29
+ File.readlines(filename).collect { |entry| yield(entry) }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ module MobRotation
2
+ class Mobster
3
+ attr_reader :name, :email
4
+
5
+ def initialize(name, email = nil)
6
+ @name = name
7
+ @email = email
8
+ end
9
+
10
+ alias_method :to_s, :name
11
+ end
12
+ end
@@ -0,0 +1,210 @@
1
+ require "time"
2
+ require "mob_rotation/command_routing"
3
+ require "mob_rotation/mobster"
4
+
5
+ module MobRotation
6
+ class Rotation
7
+ include CommandRouting
8
+ extend CommandRouting::ClassMethods
9
+
10
+ define_command("show") do
11
+ show_mobsters
12
+ end
13
+
14
+ define_command("run_with_timer") do
15
+ countdown_to_rotate(ARGV[1].to_i)
16
+ end
17
+
18
+ define_command("rotate") do
19
+ rotate
20
+ show_mobsters
21
+ end
22
+
23
+ define_command("random") do
24
+ random(ARGV[1])
25
+ show_mobsters
26
+ end
27
+
28
+ define_command("add") do |*mobster_names|
29
+ add_mobster(*mobster_names)
30
+ show_mobsters
31
+ end
32
+
33
+ define_command("remove") do |*mobster_names|
34
+ remove_mobsters(*mobster_names)
35
+ show_mobsters
36
+ end
37
+
38
+ define_command("help") do
39
+ show_help
40
+ end
41
+
42
+ def self.annoying_and_probably_not_accidental_beep(n = number_of_beeps)
43
+ n.times { print("\a") || sleep(minimum_sleep_between_beeps) }
44
+ end
45
+
46
+ def self.minimum_sleep_between_beeps
47
+ 0.2
48
+ end
49
+
50
+ def self.number_of_beeps
51
+ 5
52
+ end
53
+
54
+ def initialize(database, git_dir)
55
+ @git_dir = git_dir
56
+ @database = database
57
+ @real_mobsters = @database.sanitized_entries_in do | entry |
58
+ build_mobster(entry)
59
+ end
60
+ end
61
+
62
+ def build_mobster(entry)
63
+ name = extract_name_from(entry)
64
+ email = extract_email_from(entry)
65
+ Mobster.new(name, email)
66
+ end
67
+
68
+ def extract_email_from(entry)
69
+ if entry =~ /\<(.*)\>/
70
+ Regexp.last_match[1]
71
+ end
72
+ end
73
+
74
+ def write(text)
75
+ puts(text)
76
+ end
77
+
78
+ def show_mobsters
79
+ @real_mobsters.each_with_index do |person, index|
80
+ case index
81
+ when 0
82
+ write("git username: #{person.name}")
83
+ write("git user email: #{person.email}")
84
+ format_mobster("Driver", person)
85
+ when 1
86
+ format_mobster("Navigator", person)
87
+ else
88
+ format_mobster("Mobster", person)
89
+ end
90
+ end
91
+ end
92
+
93
+ def format_mobster(role, person)
94
+ if person.email.to_s.empty?
95
+ write("#{role} #{person.name}")
96
+ else
97
+ write("#{role} #{person.name.strip} <#{person.email.strip}>")
98
+ end
99
+ end
100
+
101
+ def add_mobster(*mobsters_to_add)
102
+ mobsters_to_add.collect(&:to_s).collect(&:strip).each do |mobster_to_add|
103
+ fail if mobster_to_add.empty?
104
+
105
+ if @real_mobsters.collect(&:name).include?(mobster_to_add)
106
+ write("user name '#{mobster_to_add}' already exists")
107
+ next
108
+ end
109
+
110
+ @real_mobsters << Mobster.new(mobster_to_add)
111
+ end
112
+
113
+ sync
114
+ end
115
+
116
+ def remove_mobster(given_mobster)
117
+ @real_mobsters.each_with_index do |mobster, i|
118
+ if found_mobster(mobster, given_mobster)
119
+ @real_mobsters.delete_at(i)
120
+ end
121
+ end
122
+ sync
123
+ end
124
+
125
+ def remove_mobsters(*mobsters)
126
+ mobsters.each do |mobster|
127
+ remove_mobster(mobster)
128
+ end
129
+ end
130
+
131
+ def show_help
132
+ puts ["Available commands are:",
133
+ "show",
134
+ "help",
135
+ "rotate",
136
+ "random",
137
+ "add <name1> [name2]",
138
+ "remove <name1> [name2]",
139
+ "run_with_timer [seconds]"
140
+ ]
141
+ end
142
+
143
+
144
+ def countdown_to_rotate(seconds = 300)
145
+ sleep(seconds)
146
+ puts("Time to rotate")
147
+ self.class.annoying_and_probably_not_accidental_beep
148
+ end
149
+
150
+ def inform_lovely_user(command)
151
+ puts("Unknown command #{command}")
152
+ show_help
153
+ end
154
+
155
+ def rotate_mobsters(&rotation_algorithm)
156
+ rotation_algorithm.call
157
+ git_config_update
158
+ sync
159
+ end
160
+
161
+ def rotate
162
+ puts("")
163
+ rotate_mobsters do
164
+ @real_mobsters << @real_mobsters.shift
165
+ end
166
+ end
167
+
168
+ def random(seed = nil)
169
+ puts("Randomized Output")
170
+ rotate_mobsters do
171
+ srand(seed.to_i) if seed
172
+ @real_mobsters.shuffle!
173
+ end
174
+ end
175
+
176
+ def extract_next_mobster_email
177
+ email = @real_mobsters.first.email.to_s.strip
178
+ email.empty? ? "mob@rubysteps.com" : email
179
+ end
180
+
181
+ def time_to_rotate
182
+ puts("Time to rotate!")
183
+ end
184
+
185
+ private
186
+
187
+ def git_config_update
188
+ # FIX yo that rescue nil is bogus
189
+ system(git_config_for("name", @real_mobsters.first.name)) rescue nil
190
+ system(git_config_for("email", extract_next_mobster_email)) rescue nil
191
+ end
192
+
193
+ def git_config_for(property, value)
194
+ "git --git-dir=#{@git_dir} config user.#{property} '#{value}'"
195
+ end
196
+
197
+ def sync
198
+ @database.write(@real_mobsters)
199
+ end
200
+
201
+ def found_mobster(line, mobster)
202
+ line.to_s.strip == mobster.to_s
203
+ end
204
+
205
+ def extract_name_from(entry)
206
+ entry_to_array = entry.split("")
207
+ entry_to_array.take_while { |c| c != "<" }.join("")
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,3 @@
1
+ module MobRotation
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "mob_rotation/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mob_rotation"
8
+ spec.version = MobRotation::VERSION
9
+ spec.authors = ["Pat Maddox"]
10
+ spec.email = ["pat@rubysteps.com"]
11
+ spec.summary = "Manage a mob list during a mobbing session"
12
+ spec.description = "Provides an executable to manage a mob list"
13
+ spec.homepage = "https://github.com/RubySteps/mob_rotation"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.1"
24
+ end
@@ -0,0 +1,31 @@
1
+ require "spec_helper"
2
+
3
+ describe FooFighter do
4
+ it "raises an error when a class defines `#foo`" do
5
+ expect {
6
+ class TestClass
7
+ extend FooFighter
8
+
9
+ def foo
10
+ "shouldn't work"
11
+ end
12
+ end
13
+ }.to raise_error(/no foo for you/)
14
+ end
15
+
16
+ it "removes a method named `#foo`" do
17
+ begin
18
+ class TestClass2
19
+ extend FooFighter
20
+
21
+ def foo
22
+ "shouldn't work"
23
+ end
24
+ end
25
+ rescue => e
26
+ # no-op
27
+ ensure
28
+ expect(TestClass2.instance_methods - Object.methods).to_not include(:foo)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,324 @@
1
+ require "fileutils"
2
+ require "timeout"
3
+ require "mob_rotation"
4
+
5
+ describe "mob_rotation command line tool" do
6
+ let(:temp_rotation_db) { "/tmp/rotation_test.txt" }
7
+
8
+ before do
9
+ `rm -rf ./tmp/test_project`
10
+ `mkdir -p ./tmp/test_project`
11
+ `git init ./tmp/test_project`
12
+ end
13
+
14
+ def remove_temp_rotation_db
15
+ FileUtils.rm(temp_rotation_db) if File.exist?(temp_rotation_db)
16
+ end
17
+
18
+ def add_name_and_email_to_temp_db(name, email = nil)
19
+ s = email ? " <#{email}>" : ""
20
+ `echo "#{name} #{s}" >> #{temp_rotation_db}`
21
+ end
22
+
23
+ before do
24
+ remove_temp_rotation_db
25
+
26
+ add_name_and_email_to_temp_db("Bob")
27
+ add_name_and_email_to_temp_db("Phoebe")
28
+ end
29
+
30
+ def run_rotate(command = nil)
31
+ run_rotate_with_specified_redirect(command, "> /tmp/results.txt")
32
+ end
33
+
34
+ def run_rotate_with_specified_redirect(command = nil, redirect = nil)
35
+ # TODO: we have no idea why this is necessary, and don't like it
36
+ @our_output = nil
37
+
38
+ `MOB_GIT_DIR='./tmp/test_project/.git' DB_FILE='#{temp_rotation_db}' #{RbConfig.ruby} #{File.join(Dir.pwd, "bin/mob_rotation")} #{command} #{redirect}`
39
+ end
40
+
41
+ def our_output
42
+ @our_output ||= File.readlines("/tmp/results.txt").collect(&:strip)
43
+ .reject(&:empty?)
44
+ end
45
+
46
+ context "command: ruby mob_rotation" do
47
+ it "defaults to 'rotate.txt' when no database file is specified" do
48
+ if backup = File.exist?("./rotate.txt")
49
+ FileUtils.mv("./rotate.txt", "./rotate.txt.backup")
50
+ end
51
+ FileUtils.cp(temp_rotation_db, "./rotate.txt")
52
+ `#{RbConfig.ruby} #{File.join(Dir.pwd, "bin/mob_rotation")} > /tmp/results.txt`
53
+ begin
54
+ expect(our_output).to include("Driver Bob", "Navigator Phoebe")
55
+ ensure
56
+ FileUtils.mv("./rotate.txt.backup", "./rotate.txt") if backup
57
+ end
58
+ end
59
+
60
+ it "prints out the rotation order" do
61
+ run_rotate
62
+ expect(our_output).to include("Driver Bob", "Navigator Phoebe")
63
+ end
64
+ end
65
+
66
+ context "command: ruby mob_rotation help" do
67
+ it "prints out help" do
68
+ run_rotate("help")
69
+ expected = ["Available commands are:",
70
+ "show",
71
+ "help",
72
+ "rotate",
73
+ "random",
74
+ "add <name1> [name2]",
75
+ "remove <name1> [name2]",
76
+ "run_with_timer [seconds]"
77
+ ]
78
+
79
+ expect(our_output).to eq(expected)
80
+ end
81
+ end
82
+
83
+ context "command: ruby mob_rotation arbitrary" do
84
+ it "prints out help on an unknown command" do
85
+ run_rotate("arbitrary")
86
+ expected = ["Unknown command arbitrary",
87
+ "Available commands are:"
88
+ ]
89
+
90
+ expect(our_output).to include(*expected)
91
+ end
92
+ end
93
+
94
+ context "command: ruby mob_rotation random" do
95
+ it "Identifies list as randomized" do
96
+ run_rotate("random 1")
97
+
98
+ expect(our_output).to include("Randomized Output")
99
+ expect(our_output).to include("Driver Bob", "Navigator Phoebe")
100
+ end
101
+
102
+ it "produces a different order with a different seed" do
103
+ run_rotate("random 0")
104
+
105
+ expect(our_output).to include("Driver Phoebe", "Navigator Bob")
106
+ end
107
+
108
+ it "saves the rotation order" do
109
+ run_rotate("random 0")
110
+ run_rotate
111
+
112
+ expect(our_output).to include("Driver Phoebe", "Navigator Bob")
113
+ end
114
+
115
+ it "updates the git username" do
116
+ remove_temp_rotation_db
117
+ add_name_and_email_to_temp_db("Bob Example", "bob@example.com")
118
+ add_name_and_email_to_temp_db("Phoebe Example", "phoebe@example.com")
119
+
120
+ run_rotate("random 0")
121
+
122
+ git_username = `git --git-dir=./tmp/test_project/.git config user.name`
123
+ .strip
124
+ expect(git_username).to eq("Phoebe Example")
125
+ end
126
+ end
127
+
128
+ context "command: ruby mob_rotation rotate" do
129
+ it "changes the order of rotation" do
130
+ run_rotate("rotate")
131
+ expect(our_output).to include("Driver Phoebe", "Navigator Bob")
132
+ end
133
+
134
+ describe "git stuff" do
135
+ it "includes the email addresses in rotation output" do
136
+ remove_temp_rotation_db
137
+ add_name_and_email_to_temp_db("Phoebe Example", "phoebe@example.com")
138
+ add_name_and_email_to_temp_db("Bob Example", "bob@example.com")
139
+ add_name_and_email_to_temp_db("Joe Example", "joe@example.com")
140
+ run_rotate("rotate")
141
+
142
+ expect(our_output).to include(
143
+ "Driver Bob Example <bob@example.com>",
144
+ "Navigator Joe Example <joe@example.com>",
145
+ "Mobster Phoebe Example <phoebe@example.com>"
146
+ )
147
+ end
148
+
149
+ it "outputs the new git username when running rotate" do
150
+ remove_temp_rotation_db
151
+ add_name_and_email_to_temp_db("Phoebe Example")
152
+
153
+ run_rotate("rotate")
154
+
155
+ expect(our_output).to include(
156
+ "git username: Phoebe Example",
157
+ "Driver Phoebe Example"
158
+ )
159
+ end
160
+
161
+ it "outputs the new git email when running rotate" do
162
+ remove_temp_rotation_db
163
+ add_name_and_email_to_temp_db("Phoebe Example", "phoebe@example.com")
164
+
165
+ run_rotate "rotate"
166
+
167
+ expect(our_output).to include("git user email: phoebe@example.com")
168
+
169
+ add_name_and_email_to_temp_db(
170
+ "David Example",
171
+ "david-example@example.com"
172
+ )
173
+
174
+ run_rotate("rotate")
175
+
176
+ expect(our_output).to include(
177
+ "git user email: david-example@example.com"
178
+ )
179
+ end
180
+
181
+ it "outputs the new git email when rotating a list of multiple users" do
182
+ remove_temp_rotation_db
183
+ add_name_and_email_to_temp_db("Phoebe Example", "phoebe@example.com")
184
+ add_name_and_email_to_temp_db("David Example", "david@example.com")
185
+
186
+ run_rotate("rotate")
187
+
188
+ expect(our_output).to include("git user email: david@example.com")
189
+ end
190
+
191
+ it "updates the git user.name config when running rotate" do
192
+ remove_temp_rotation_db
193
+ add_name_and_email_to_temp_db("Phoebe Example", "phoebe@example.com")
194
+
195
+ run_rotate("rotate")
196
+
197
+ git_username = `git --git-dir=./tmp/test_project/.git config user.name`
198
+ .strip
199
+ expect(git_username).to eq("Phoebe Example")
200
+ end
201
+
202
+ it "updates the git user.email config when running rotate" do
203
+ remove_temp_rotation_db
204
+ add_name_and_email_to_temp_db("Phoebe Example", "phoebe@example.com")
205
+
206
+ run_rotate("rotate")
207
+
208
+ git_email = `git --git-dir=./tmp/test_project/.git config user.email`
209
+ .strip
210
+ expect(git_email).to eq("phoebe@example.com")
211
+ end
212
+
213
+ it "updates the email in the mobsters database when rotating" do
214
+ remove_temp_rotation_db
215
+ add_name_and_email_to_temp_db("Phoebe Example", "phoebe@example.com")
216
+ add_name_and_email_to_temp_db("Bob Example" "bob@example.com")
217
+ add_name_and_email_to_temp_db("Joe Example", "joe@example.com")
218
+
219
+ run_rotate("rotate")
220
+ run_rotate("rotate")
221
+
222
+ git_email = `git --git-dir=./tmp/test_project/.git config user.email`
223
+ .strip
224
+ expect(git_email).to eq("joe@example.com")
225
+ end
226
+
227
+ it "falls back to a default email address when the driver has none" do
228
+ remove_temp_rotation_db
229
+ add_name_and_email_to_temp_db("Phoebe Example", "phoebe@example.com")
230
+ add_name_and_email_to_temp_db("Bob Example")
231
+
232
+ run_rotate("rotate")
233
+
234
+ git_email = `git --git-dir=./tmp/test_project/.git config user.email`
235
+ .strip
236
+ expect(git_email).to eq("mob@rubysteps.com")
237
+ end
238
+
239
+ it "updates the email in the mobsters database when rotating, even when someone's missing an email address" do
240
+ remove_temp_rotation_db
241
+ add_name_and_email_to_temp_db("Phoebe Example", "phoebe@example.com")
242
+ add_name_and_email_to_temp_db("Bob Example")
243
+ add_name_and_email_to_temp_db("Joe Example", "joe@example.com")
244
+
245
+ run_rotate("rotate")
246
+ run_rotate("rotate")
247
+
248
+ git_email = `git --git-dir=./tmp/test_project/.git config user.email`
249
+ .strip
250
+ expect(git_email).to eq("joe@example.com")
251
+ end
252
+ end
253
+ end
254
+
255
+ context "command: ruby mob_rotation add one_name [two three]" do
256
+ it "adds one mobster to the mob list" do
257
+ run_rotate("add Joe")
258
+ expect(our_output).to include(
259
+ "Driver Bob",
260
+ "Navigator Phoebe",
261
+ "Mobster Joe"
262
+ )
263
+ end
264
+
265
+ it "adds multiple mobsters at once" do
266
+ run_rotate("add Phil Steve")
267
+ expect(our_output).to include("Mobster Phil", "Mobster Steve")
268
+ end
269
+
270
+ it "new name cannot match existing name" do
271
+ run_rotate("add Ralph")
272
+ run_rotate("add Ralph")
273
+
274
+ expect(our_output).to include("user name 'Ralph' already exists")
275
+ expect(our_output.select { |l| l.include?("Ralph") }.size).to eq(2)
276
+ end
277
+ end
278
+
279
+ context "command: ruby mob_rotation remove one_name [two three]" do
280
+ it "removes one mobster from the mob list" do
281
+ run_rotate("remove Bob")
282
+ expect(our_output).to include("Driver Phoebe")
283
+ end
284
+
285
+ it "removes multiple mobsters at once" do
286
+ # TODO: Doesn't test what it claims to test.
287
+ run_rotate("add Phil")
288
+ run_rotate("remove Bob Phoebe")
289
+ expect(our_output).to include("Driver Phil")
290
+ end
291
+ end
292
+
293
+ context "command: ruby mob_rotation run_with_timer N" do
294
+ it "it runs for a specific amount of time" do
295
+ ts = Time.now
296
+ run_rotate("run_with_timer 3")
297
+ tf = Time.now
298
+ threshold = 3.0 + MobRotation::Rotation.minimum_sleep_between_beeps * MobRotation::Rotation.number_of_beeps
299
+ expect(tf - ts).to be_within(1).of(threshold)
300
+ expect(our_output).to include("Time to rotate")
301
+ end
302
+
303
+ it "waits until time runs out before stating 'Time to Rotate'" do
304
+ expect {
305
+ Timeout.timeout(1) { run_rotate("run_with_timer 5") }
306
+ }.to raise_error(Timeout::Error)
307
+ expect(our_output).to eq([])
308
+ end
309
+
310
+ it "notifies with a beep" do
311
+ stdout_output = run_rotate_with_specified_redirect("run_with_timer")
312
+ expect(stdout_output).to include("\a")
313
+ end
314
+
315
+ it "runs for a specified amount of time and then notifies with a beep" do
316
+ ts = Time.now
317
+ stdout_output = run_rotate_with_specified_redirect("run_with_timer 2")
318
+ tf = Time.now
319
+ expect(tf - ts).to be_within(1).of(2.0 + MobRotation::Rotation.minimum_sleep_between_beeps * MobRotation::Rotation.number_of_beeps)
320
+ expect(stdout_output).to include("Time to rotate")
321
+ expect(stdout_output).to include("\a")
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,129 @@
1
+ require "spec_helper"
2
+
3
+ describe MobRotation::Rotation do
4
+ extend FooFighter
5
+
6
+ let(:mob_rotator) {
7
+ MobRotation::Rotation.new(
8
+ MobRotation::Database.new(file_name),
9
+ "./tmp/test_project/.git"
10
+ )
11
+ }
12
+
13
+ before { FileUtils.rm_f(file_name) }
14
+
15
+ describe "#show_mobsters" do
16
+ let(:file_name) { "test.txt" }
17
+ let(:output) { Output.new }
18
+
19
+ before do
20
+ File.open(file_name, "w") do |file|
21
+ file << "Bob\n" << "Phoebe\n" << "Joe\n"
22
+ end
23
+
24
+ $stdout = output
25
+
26
+ mob_rotator.show_mobsters
27
+ end
28
+
29
+ it "print out all the mobsters in the text file" do
30
+ expect(output.mobsters.join).to include("Mobster Joe")
31
+ end
32
+
33
+ it "prints out driver" do
34
+ expect(output.mobsters.join).to include("Driver Bob")
35
+ end
36
+
37
+ it "prints out navigator" do
38
+ expect(output.mobsters.join).to include("Navigator Phoebe")
39
+ end
40
+ end
41
+
42
+ describe "#add_mobster" do
43
+ let(:file_name) { "test.txt" }
44
+
45
+ it "adds a mobster to the file" do
46
+ mob_rotator.add_mobster("Jackie")
47
+ mob_rotator.add_mobster("Phil")
48
+
49
+ expect(File.read(file_name)).to eq("Jackie\nPhil\n")
50
+ end
51
+
52
+ it "accepts more than one mobster" do
53
+ mob_rotator.add_mobster("Jackie", "Phil")
54
+ expect(File.read(file_name)).to include("\nPhil")
55
+ end
56
+
57
+ it "skips adding duplicate mobster name" do
58
+ mob_rotator.add_mobster("Rosie", "Rosie")
59
+ expect(File.read(file_name)).to eq("Rosie\n")
60
+ end
61
+
62
+ it "strips leading and trailing whitespace" do
63
+ mob_rotator.add_mobster(" \n Rosie \n ")
64
+ expect(File.read(file_name)).to eq("Rosie\n")
65
+ end
66
+
67
+ it "does not allow blank usernames" do
68
+ expect { mob_rotator.add_mobster(" ") }
69
+ .to raise_error
70
+ end
71
+ end
72
+
73
+ describe "#remove_mobster" do
74
+ let(:file_name) { "test_remove.txt" }
75
+
76
+ before do
77
+ File.open(file_name, "w") do |file|
78
+ file << "Bob\n" << "Phoebe\n" << "Joe"
79
+ end
80
+ end
81
+
82
+ it "removes a mobster from the file" do
83
+ mob_rotator.remove_mobster("Bob")
84
+ expect(File.read(file_name)).not_to include("Bob")
85
+ end
86
+ end
87
+
88
+ describe "#rotate" do
89
+ let(:file_name) { "test_remove.txt" }
90
+
91
+ before do
92
+ File.open(file_name, "w") do |file|
93
+ file << "Bob\n" << "Phoebe\n" << "Joe\n"
94
+ end
95
+ end
96
+
97
+ it "rotates mobsters" do
98
+ mob_rotator.rotate
99
+ # new file to equal "Phoebe Joe Bob"
100
+ expect(File.read(file_name)).to eq("Phoebe\nJoe\nBob\n")
101
+ end
102
+ end
103
+
104
+ describe "#extract_email_from(entry)" do
105
+ let(:file_name) { "irrelevant_file" }
106
+
107
+ after(:each) do
108
+ FileUtils.rm_f(file_name)
109
+ end
110
+
111
+ it "returns the email address" do
112
+ email = MobRotation::Rotation.new(
113
+ MobRotation::Database.new(file_name),
114
+ "irrelevant git dir"
115
+ ).extract_email_from("a <b@example.com>")
116
+
117
+ expect(email).to eq("b@example.com")
118
+ end
119
+
120
+ it "it handles arbitary email addresses" do
121
+ email = MobRotation::Rotation.new(
122
+ MobRotation::Database.new(file_name),
123
+ "irrelevant git dir"
124
+ ).extract_email_from("bob <bob@example.com>")
125
+
126
+ expect(email).to eq("bob@example.com")
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,19 @@
1
+ require "fileutils"
2
+ require "mob_rotation"
3
+
4
+ class Output
5
+ attr_accessor :mobsters
6
+
7
+ def write(argument)
8
+ @mobsters ||= []
9
+ @mobsters << argument
10
+ end
11
+ end
12
+
13
+ module FooFighter
14
+ def method_added(m)
15
+ return unless m.to_s == "foo"
16
+ remove_method(m)
17
+ fail("no foo for you")
18
+ end
19
+ end
data/todo.md ADDED
@@ -0,0 +1,113 @@
1
+ - get a real test environment separated from production
2
+ - add email git config on rotate DONE
3
+ - in our tests change directory when running command line stuff
4
+ - get the tests passing DONE
5
+ - clean up this working directory
6
+ - rename unit and acceptance tests to be more intuitive (maybe)
7
+ - refactor MobRotation#initialize to read file once
8
+ - refactor MobRotation#initialize to use either 2 class methods or 2 instance methods (extract_<something>_from)
9
+ - add hook to emacs configuration to automatically show line numbers in (all?) files - DONE
10
+ - match pattern on remove (like remove Jacq*)
11
+ - shuffle the mobsters
12
+ - move test output to ./tmp instead of /tmp
13
+ - move todo/features to git issues/ clean up todo
14
+ - Timer
15
+
16
+ help w/o specifying database
17
+ make main class match file name **** Done
18
+ lots of commented out code in mob_rotation executable *** Done
19
+ refactor case statement***
20
+ include role information in database file
21
+ #add_mobster takes multiple mobsters
22
+ explicit path in acceptance tests only works on the mob station
23
+ auto-generate help based on known commands *
24
+ broken tests break production rotate.txt file - DONE
25
+
26
+ delete Timer *** DONE
27
+ each_with_index ? in remove_mobster ... * DONE
28
+ known default database file, overridable * WIP
29
+ refactor add_mobster to use sync!* DONE
30
+ Tmux integration: powerline
31
+ refactor to mob commands vs timer class (and stuff like that :) **
32
+ too much going on in mob_rotation executable? *
33
+ add run_with_timer to help.
34
+
35
+
36
+
37
+ Break functions out of mob_rotation.rb (God class)
38
+ clean up directory structure...**
39
+ do all the tests pass???****
40
+ understand what it actually does...?
41
+ play firecrackers whenever we rotate ***
42
+ prevent non-driver from interacting w/ terminal
43
+ get more information / permissions about participants (class-based?)
44
+ include the project history in README
45
+ create a README********
46
+ abandon this and move on to something
47
+ start a new project
48
+ build a browser-based gui
49
+ randomize driver / navigator (with limits) **
50
+ record a history of rotation
51
+ designated driver
52
+
53
+
54
+
55
+ TODO
56
+ - Unrecognized commands show help DONE
57
+ - Timer! *
58
+
59
+ Fails: ruby mob_rotation asdf cows
60
+
61
+ ____
62
+
63
+ Add/remove multiple mobsters in one call**
64
+ Timer ****
65
+ Detect mobsters
66
+ Tmux integration: powerline *
67
+
68
+
69
+ # Time.now() == 7min ->timeisup!
70
+
71
+ # naming acceptance ... again!~~~
72
+ # No Readme file ~~~
73
+ # End goal of the app ~~~
74
+ # add a timer feature ~~~
75
+ # is this going to stay a CLI, gem, rails app feature? Forward direction? ~~
76
+
77
+ # introduce helper spec~
78
+ # separate implementation from script~
79
+ # organizing cli vs unit specs ~
80
+ # Common structure for running tests
81
+ # our 'clean state' isnt that clean. Lots of duplicate files from before
82
+
83
+
84
+
85
+ # DONE adding remove mobster feature ----
86
+ # naming the acteptance---
87
+ # add timer feature---
88
+ # refactor to dependency injection --
89
+ # seperating implementation from the script --
90
+ # connect the app with ruby lobby api * -
91
+ # add new api via CLI-
92
+ # introducing helper_spec(moving)-
93
+
94
+ # do we keep features that are done or move to changelog?
95
+ # focus on refactoring vs adding features?
96
+ # adding directory structure
97
+
98
+ # DONE show mobster should display who navigates and who is driver ****
99
+ # DONE method to add
100
+ # remove mobsters ***
101
+ # remove dependency to / with file ***
102
+ # DONE give the describe block a name **
103
+ # operating on standard out... would it be worth it to refactor? **
104
+ # DONE change it statement... don't know what the subject is *
105
+
106
+ # --------
107
+
108
+ # store more mata data in db file(rotation time...)
109
+ # write specs for next method rotate?
110
+ # how important is it really?
111
+ # could change spec to add more meaning
112
+ # removing time_to_rotate
113
+
data/woot_camp_todo.md ADDED
@@ -0,0 +1,28 @@
1
+ * establish behavior in the tests ***** DONE
2
+ * pending tests drive the team nuts **** DONE
3
+
4
+ * what happens when two people have the same name? **** DONE
5
+
6
+ * blank user for no apparent reason *** DONE
7
+ * name with trailing whitespace? DONE
8
+
9
+
10
+ * disappearing emails...
11
+ * some emails nil, some blank?
12
+
13
+ * what should rotate do if next user has no email?
14
+ * encapsulate the mobster class DONE
15
+
16
+
17
+
18
+ BUGS
19
+
20
+
21
+ IDEA
22
+
23
+ * gemify so that we can run a stable version while developing
24
+ * erase production db?
25
+ * check email format
26
+ * validate email for GitHub user
27
+
28
+ * exchange twitter / email contact info (team)
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mob_rotation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Pat Maddox
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ description: Provides an executable to manage a mob list
56
+ email:
57
+ - pat@rubysteps.com
58
+ executables:
59
+ - mob_rotation
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".rubocop.yml"
66
+ - ".travis.yml"
67
+ - Changelog.md
68
+ - Contributors.md
69
+ - Gemfile
70
+ - LICENSE.txt
71
+ - README.md
72
+ - Rakefile
73
+ - bin/mob_rotation
74
+ - lib/mob_rotation.rb
75
+ - lib/mob_rotation/command_routing.rb
76
+ - lib/mob_rotation/database.rb
77
+ - lib/mob_rotation/mobster.rb
78
+ - lib/mob_rotation/rotation.rb
79
+ - lib/mob_rotation/version.rb
80
+ - mob_rotation.gemspec
81
+ - spec/foo_fighter_spec.rb
82
+ - spec/mob_rotation_executable_spec.rb
83
+ - spec/rotation_spec.rb
84
+ - spec/spec_helper.rb
85
+ - todo.md
86
+ - woot_camp_todo.md
87
+ homepage: https://github.com/RubySteps/mob_rotation
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.4.5
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Manage a mob list during a mobbing session
111
+ test_files:
112
+ - spec/foo_fighter_spec.rb
113
+ - spec/mob_rotation_executable_spec.rb
114
+ - spec/rotation_spec.rb
115
+ - spec/spec_helper.rb