sensei 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/VERSION +1 -0
- data/bin/sensei +15 -0
- data/lib/sensei.rb +16 -0
- data/lib/sensei/dojo.rb +128 -0
- data/lib/sensei/git_tag_lister.rb +11 -0
- data/lib/sensei/iteration_list.rb +11 -0
- data/lib/sensei/repository.rb +10 -0
- data/rakefile +33 -0
- data/readme.markdown +63 -0
- data/spec/sensei/git_tag_lister_spec.rb +34 -0
- data/spec/sensei/iteration_list_spec.rb +32 -0
- data/spec/spec_helper.rb +2 -0
- metadata +108 -0
data/.gitignore
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/bin/sensei
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Add '.rb' to work around a bug in IronRuby's File#dirname
|
3
|
+
$:.unshift(File.dirname(__FILE__ + '.rb') + '/../lib') unless $:.include?(File.dirname(__FILE__ + '.rb') + '/../lib')
|
4
|
+
|
5
|
+
require 'sensei'
|
6
|
+
begin
|
7
|
+
failure = run_dojo(RealGit.new)
|
8
|
+
Kernel.exit(failure ? 1 : 0)
|
9
|
+
rescue SystemExit => e
|
10
|
+
Kernel.exit(e.status)
|
11
|
+
rescue Exception => e
|
12
|
+
STDERR.puts("#{e.message} (#{e.class})")
|
13
|
+
STDERR.puts(e.backtrace.join("\n"))
|
14
|
+
Kernel.exit 1
|
15
|
+
end
|
data/lib/sensei.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'sensei/iteration_list'
|
5
|
+
require 'sensei/git_tag_lister'
|
6
|
+
require 'sensei/repository'
|
7
|
+
require 'sensei/dojo'
|
8
|
+
|
9
|
+
def get_name
|
10
|
+
print "Enter your name: "
|
11
|
+
STDIN.gets
|
12
|
+
end
|
13
|
+
|
14
|
+
def run_dojo(git)
|
15
|
+
Dojo.new(get_name, git).run
|
16
|
+
end
|
data/lib/sensei/dojo.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
class Dojo
|
2
|
+
|
3
|
+
def initialize(name, git)
|
4
|
+
@git = git
|
5
|
+
@name = name
|
6
|
+
@iteration = 1
|
7
|
+
@git.do("checkout iteration-1")
|
8
|
+
@git.do("branch -D #{name}")
|
9
|
+
@git.do("checkout -b #{name}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def suggest_write_specs
|
13
|
+
puts
|
14
|
+
puts "FAIL: how about writing some specs in ./spec ?"
|
15
|
+
puts "FAIL! Hit [ENTER] to run them when you're ready..."
|
16
|
+
STDIN.gets
|
17
|
+
end
|
18
|
+
|
19
|
+
def cukes_failed
|
20
|
+
puts
|
21
|
+
puts "FAIL: Your cukes failed!"
|
22
|
+
end
|
23
|
+
|
24
|
+
def specs_failed
|
25
|
+
puts
|
26
|
+
puts "FAIL: Better fix those specs!"
|
27
|
+
puts "FAIL! Hit [ENTER] to run them when you're ready..."
|
28
|
+
STDIN.gets
|
29
|
+
end
|
30
|
+
|
31
|
+
def specs_passed
|
32
|
+
puts
|
33
|
+
puts "Your specs passed :-). Hit [ENTER] to try the cukes again"
|
34
|
+
STDIN.gets
|
35
|
+
end
|
36
|
+
|
37
|
+
def run
|
38
|
+
@max_iterations = IterationList.new(Dir.pwd).iterations
|
39
|
+
until @iteration > @max_iterations
|
40
|
+
|
41
|
+
run_cukes
|
42
|
+
unless @cukes_passed
|
43
|
+
cukes_failed
|
44
|
+
suggest_write_specs
|
45
|
+
run_specs
|
46
|
+
until @specs_passed
|
47
|
+
specs_failed
|
48
|
+
run_specs
|
49
|
+
end
|
50
|
+
specs_passed
|
51
|
+
run_cukes
|
52
|
+
end
|
53
|
+
|
54
|
+
if success?
|
55
|
+
if @iteration < @max_iterations
|
56
|
+
commit("iteration #{@iteration} tests passing.")
|
57
|
+
|
58
|
+
puts
|
59
|
+
puts
|
60
|
+
puts "WIN!"
|
61
|
+
puts "Perhaps you would care for a spot of refactoring at this point?"
|
62
|
+
|
63
|
+
refactoring_loop
|
64
|
+
next_iteration
|
65
|
+
else
|
66
|
+
puts "EPIC WIN! Now to play the refactoring game..."
|
67
|
+
until false
|
68
|
+
system("rake spec features")
|
69
|
+
if success?
|
70
|
+
flog
|
71
|
+
commit "refactoring"
|
72
|
+
end
|
73
|
+
puts "Not bad. Can you do any better? (Hit a key to re-run the flog)"
|
74
|
+
STDIN.gets
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def success?
|
82
|
+
$?.exitstatus == 0
|
83
|
+
end
|
84
|
+
|
85
|
+
def run_cukes
|
86
|
+
system("cucumber features")
|
87
|
+
@cukes_passed = success?
|
88
|
+
end
|
89
|
+
|
90
|
+
def run_specs
|
91
|
+
system("rake spec")
|
92
|
+
@specs_passed = success?
|
93
|
+
end
|
94
|
+
|
95
|
+
def next_iteration
|
96
|
+
commit("iteration #{@iteration} refactored.")
|
97
|
+
@iteration = @iteration+1
|
98
|
+
@git.do("merge iteration-#{@iteration}")
|
99
|
+
end
|
100
|
+
|
101
|
+
def commit message
|
102
|
+
@git.do("add .")
|
103
|
+
@git.do(%Q{commit -m "#{message}"})
|
104
|
+
end
|
105
|
+
|
106
|
+
def flog
|
107
|
+
system("flog lib/*.rb")
|
108
|
+
end
|
109
|
+
|
110
|
+
def refactoring_loop
|
111
|
+
go_next = false
|
112
|
+
until go_next
|
113
|
+
message = "Press [ENTER] to run the tests again (with refactoring tips)"
|
114
|
+
message << ", [SPACE ENTER] to move to the next iteration" if success?
|
115
|
+
puts message
|
116
|
+
|
117
|
+
go_next = (STDIN.gets == " \n")
|
118
|
+
|
119
|
+
unless go_next && success?
|
120
|
+
system("rake spec features")
|
121
|
+
if success?
|
122
|
+
flog
|
123
|
+
end
|
124
|
+
go_next = false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "spec/rake/spectask"
|
2
|
+
|
3
|
+
task :default => :spec do
|
4
|
+
end
|
5
|
+
|
6
|
+
desc "Run all specs"
|
7
|
+
Spec::Rake::SpecTask.new do |t|
|
8
|
+
t.spec_files = FileList["spec/**/*.rb"]
|
9
|
+
t.spec_opts = ['--format specdoc --color']
|
10
|
+
end
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'jeweler'
|
14
|
+
Jeweler::Tasks.new do |gemspec|
|
15
|
+
gemspec.name = "sensei"
|
16
|
+
gemspec.summary = "Sensei runs your coding dojo for you."
|
17
|
+
gemspec.description = "Sensei runs your dojo for you. You start with a single failing cucumber feature. As you make each feature pass, the next feature is brought into play. While you have failing features, you have the opportunity to write rspec specifications. Sensei will encourage you to refactor at appropriate moments, showing you your flog score as encouragement."
|
18
|
+
gemspec.email = "alex.scordellis@gmail.com"
|
19
|
+
gemspec.homepage = "http://github.com/alexscordellis/sensei"
|
20
|
+
gemspec.authors = ["Alex Scordellis"]
|
21
|
+
|
22
|
+
# gemspec.test_files = Dir['spec/**/*'] + Dir['spec/*']
|
23
|
+
|
24
|
+
gemspec.add_dependency('rake', '>= 0.8.7')
|
25
|
+
gemspec.add_dependency('rspec', '>= 1.2.6')
|
26
|
+
gemspec.add_dependency('cucumber', '>= 0.3.3')
|
27
|
+
gemspec.add_dependency('flog', '>= 2.1.0')
|
28
|
+
|
29
|
+
end
|
30
|
+
Jeweler::GemcutterTasks.new
|
31
|
+
rescue LoadError
|
32
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
33
|
+
end
|
data/readme.markdown
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Sensei
|
2
|
+
|
3
|
+
Sensei runs your dojo for you. You start with a single failing cucumber feature. As you make each feature pass, the next feature is brought into play. While you have failing features, you have the opportunity to write rspec specifications. Sensei will encourage you to refactor at appropriate moments, showing you your [flog](http://blog.zenspider.com/rubysadism/flog/) score as encouragement.
|
4
|
+
|
5
|
+
This should replicate an outside-in development style, where the cucumber features represent acceptance/integration tests and the specs are lower level unit tests that help you develop the individual methods and classes.
|
6
|
+
|
7
|
+
# Running a dojo
|
8
|
+
|
9
|
+
gem install sensei
|
10
|
+
git clone git://github.com/alexscordellis/kata-minesweeper.git (or other dojo repository)
|
11
|
+
cd kata-minesweeper
|
12
|
+
sensei
|
13
|
+
|
14
|
+
Then follow the instructions. Each time you get to green, sensei will make a commit to git. This way you'll have a history of all your implementation and refactorings. You could then push those changes to a github fork of the dojo repository and compare your solution to others'.
|
15
|
+
|
16
|
+
## Tips
|
17
|
+
|
18
|
+
* Try to write the simplest code that will make the next cucumber scenario pass. Try not to anticipate future requirements.
|
19
|
+
* If the jump from one scenario to the next is too big, or affects more than one behaviour of your program, consider writing rspec specifications that make smaller steps.
|
20
|
+
* If the cucumber scenario requires a significant change in the implementation, consider refactoring before implementing the new scenario. Make sure that all the previous scenarios are still passing after your refactoring and before you add support for the new scenario.
|
21
|
+
* Make sure you refactor each time you get to green, to make sure that you're in a good state for the net scenario. Use the flog score to guide your refactoring - a lower flog score is better.
|
22
|
+
|
23
|
+
|
24
|
+
# Prepared dojos
|
25
|
+
|
26
|
+
## Minesweeper
|
27
|
+
|
28
|
+
Source is [on github](http://github.com/alexscordellis/kata-minesweeper "Minesweeper source"). This is a simple implementation of the classic Windows minesweeper game. The features currently available specify how the completed board should appear given the size and the mine locations. This is the original dojo from Matt's session.
|
29
|
+
|
30
|
+
## Bowling Game
|
31
|
+
|
32
|
+
Source is [on github](http://github.com/alexscordellis/sensei-bowling-game "Bowling Game source"). This is an interpretation of Uncle Bob's classic Bowling Game kata.
|
33
|
+
|
34
|
+
# Prerequisites
|
35
|
+
|
36
|
+
You will need the following gems
|
37
|
+
|
38
|
+
* rake
|
39
|
+
* rspec
|
40
|
+
* cucumber
|
41
|
+
* flog
|
42
|
+
|
43
|
+
# Preparing your own dojo
|
44
|
+
|
45
|
+
Your project must respond to the commands `rake spec` and `cucumber features`, issued from the command line in the root directory. Only code matching `./lib/*.rb` will be included in the flog score.
|
46
|
+
|
47
|
+
# Credits
|
48
|
+
|
49
|
+
The original idea came from [Matt Wynne](http://blog.mattwynne.net/)'s session at the [September 2009 London Ruby Users Group](http://lrug.org/meetings/2009/09/18/october-2009-meeting/). I've added the rspec part of the workflow and I'm working on the packaging. The features for the minesweeper kata are his.
|
50
|
+
|
51
|
+
# Future plans
|
52
|
+
|
53
|
+
* Get gem published somewhere
|
54
|
+
* Provide online repository of known katas
|
55
|
+
* Add reset functionality (reset current iteration, or reset whole kata, cleaning up branches).
|
56
|
+
* Autodetect number of iterations (currently assumes 5).
|
57
|
+
|
58
|
+
Ultimately the user should be able to do something like:
|
59
|
+
|
60
|
+
gem install sensei
|
61
|
+
sensei init minesweeper (downloads dojo code for minesweeper)
|
62
|
+
cd minesweeper
|
63
|
+
sensei start
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
describe GitTagLister, "when listing tags in a git repo containing tags" do
|
4
|
+
|
5
|
+
before do
|
6
|
+
Dir.mkdir '/tmp/foo'
|
7
|
+
Dir.chdir '/tmp/foo' do
|
8
|
+
`git init`
|
9
|
+
`touch first.txt`
|
10
|
+
`git add .`
|
11
|
+
`git commit -am "Initial commit"`
|
12
|
+
`git tag tag-1`
|
13
|
+
`touch test.txt`
|
14
|
+
`git add test.txt`
|
15
|
+
`git commit -m "Added test file"`
|
16
|
+
`git tag otherOne`
|
17
|
+
end
|
18
|
+
@current_dir = Dir.pwd
|
19
|
+
@tags = GitTagLister.new("/tmp/foo").tags
|
20
|
+
end
|
21
|
+
|
22
|
+
after do
|
23
|
+
`rm -rf /tmp/foo`
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should list the available tags" do
|
27
|
+
@tags.should == ["otherOne", "tag-1"]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should put the pwd back when it's done" do
|
31
|
+
Dir.pwd.should == @current_dir
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
describe IterationList, "when listing sensei iterations in a git repo containing appropriate tags" do
|
4
|
+
|
5
|
+
before do
|
6
|
+
Dir.mkdir '/tmp/foo'
|
7
|
+
Dir.chdir '/tmp/foo' do
|
8
|
+
`git init`
|
9
|
+
`touch first.txt`
|
10
|
+
`git add .`
|
11
|
+
`git commit -am "Initial commit"`
|
12
|
+
`git tag iteration-1`
|
13
|
+
`touch test.txt`
|
14
|
+
`git add test.txt`
|
15
|
+
`git commit -m "Added test file"`
|
16
|
+
`git tag not-an-iteration`
|
17
|
+
`touch test2.txt`
|
18
|
+
`git add test2.txt`
|
19
|
+
`git commit -m "Added test2 file"`
|
20
|
+
`git tag iteration-2`
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
after do
|
25
|
+
`rm -rf /tmp/foo`
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should count the number of iterations, ignoring tags that are not iterations" do
|
29
|
+
IterationList.new("/tmp/foo").iterations.should == 2
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sensei
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Scordellis
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-22 00:00:00 +01:00
|
13
|
+
default_executable: sensei
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rake
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.8.7
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.2.6
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: cucumber
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.3.3
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: flog
|
47
|
+
type: :runtime
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.1.0
|
54
|
+
version:
|
55
|
+
description: Sensei runs your dojo for you. You start with a single failing cucumber feature. As you make each feature pass, the next feature is brought into play. While you have failing features, you have the opportunity to write rspec specifications. Sensei will encourage you to refactor at appropriate moments, showing you your flog score as encouragement.
|
56
|
+
email: alex.scordellis@gmail.com
|
57
|
+
executables:
|
58
|
+
- sensei
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files: []
|
62
|
+
|
63
|
+
files:
|
64
|
+
- .gitignore
|
65
|
+
- VERSION
|
66
|
+
- bin/sensei
|
67
|
+
- lib/sensei.rb
|
68
|
+
- lib/sensei/dojo.rb
|
69
|
+
- lib/sensei/git_tag_lister.rb
|
70
|
+
- lib/sensei/iteration_list.rb
|
71
|
+
- lib/sensei/repository.rb
|
72
|
+
- rakefile
|
73
|
+
- readme.markdown
|
74
|
+
- spec/sensei/git_tag_lister_spec.rb
|
75
|
+
- spec/sensei/iteration_list_spec.rb
|
76
|
+
- spec/spec_helper.rb
|
77
|
+
has_rdoc: true
|
78
|
+
homepage: http://github.com/alexscordellis/sensei
|
79
|
+
licenses: []
|
80
|
+
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options:
|
83
|
+
- --charset=UTF-8
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: "0"
|
91
|
+
version:
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: "0"
|
97
|
+
version:
|
98
|
+
requirements: []
|
99
|
+
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 1.3.5
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: Sensei runs your coding dojo for you.
|
105
|
+
test_files:
|
106
|
+
- spec/sensei/git_tag_lister_spec.rb
|
107
|
+
- spec/sensei/iteration_list_spec.rb
|
108
|
+
- spec/spec_helper.rb
|