mob_rotation 0.1.0

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