codersdojo 0.9.7 → 0.9.8

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.
@@ -0,0 +1,108 @@
1
+ require 'shell_wrapper'
2
+
3
+ class ConsoleView
4
+
5
+ @@VERSION = '0.9'
6
+
7
+ def show_help
8
+ puts <<-helptext
9
+ Personal CodersDojo, Version #{@@VERSION}, http://www.codersdojo.com, Copyright by it-agile GmbH (http://www.it-agile.de)
10
+ PersonalCodersDojo automatically runs your tests of a code kata.
11
+
12
+ helptext
13
+ show_usage
14
+ end
15
+
16
+ def show_usage
17
+ puts <<-helptext
18
+ Usage: #{$0} command [options]
19
+ Commands:
20
+ help, -h, --help Print this help text.
21
+ help <command> See the details of the command.
22
+ setup <framework> <kata_file> Setup the environment for running the kata.
23
+ upload <framework> <session_dir> Upload the kata to http://www.codersdojo.org
24
+
25
+ Report bugs to <codersdojo@it-agile.de>
26
+ helptext
27
+ end
28
+
29
+ def show_detailed_help command
30
+ if command == 'setup' then
31
+ show_help_setup
32
+ elsif command == 'start' then
33
+ show_help_start
34
+ elsif command == 'upload' then
35
+ show_help_upload
36
+ else
37
+ show_help_unknown command
38
+ end
39
+ end
40
+
41
+ def show_help_setup
42
+ templates = ShellWrapper.new.list_templates
43
+ puts <<-helptext
44
+
45
+ setup <framework> <kata_file_no_ext> Setup the environment for the kata for the given framework and kata file.
46
+ The kata_file should not have an extension. Use 'prime' and not 'prime.java'.
47
+ By now <framework> is one of #{templates}.
48
+ Use ??? as framework if your framework isn't in the list.
49
+
50
+ Example:
51
+ :/dojo/my_kata% #{$0} setup ruby.test/unit prime
52
+ Show the instructions how to setup the environment for kata execution with Ruby and test/unit.
53
+ helptext
54
+ end
55
+
56
+ def show_help_start
57
+ puts <<-helptext
58
+
59
+ start <shell_command> <kata_file> Start the continuous test runner, that runs <shell-command> whenever <kata_file>
60
+ changes. The <kata_file> has to include the whole source code of the kata.
61
+ Whenever the test runner is started, it creates a new session directory in the
62
+ directory .codersdojo where it logs the steps of the kata.
63
+ helptext
64
+ end
65
+
66
+ def show_help_upload
67
+ templates = ShellWrapper.new.list_templates
68
+ puts <<-helptext
69
+
70
+ upload <framework> <session_directory> Upload the kata written with <framework> in <session_directory> to codersdojo.com.
71
+ <session_directory> is relative to the working directory.
72
+ By now <framework> is one of #{templates}.
73
+ If you used another framework, use ??? and send an email to codersdojo@it-agile.de
74
+
75
+ Example:
76
+ :/dojo/my_kata$ #{$0} upload ruby.test/unit .codersdojo/2010-11-02_16-21-53
77
+ Upload the kata (written in Ruby with the test/unit framework) located in directory ".codersdojo/2010-11-02_16-21-53" to codersdojo.com.
78
+ helptext
79
+ end
80
+
81
+ def show_help_unknown command
82
+ puts <<-helptext
83
+ Command #{command} not known. Try '#{$0} help' to list the supported commands.
84
+ helptext
85
+ end
86
+
87
+ def show_start_kata command, file
88
+ puts "Starting PersonalCodersDojo with command #{command} and kata file #{file}. Use Ctrl+C to finish the kata."
89
+ end
90
+
91
+ def show_missing_command_argument_error command
92
+ puts "Command <#{command}> recognized but no argument was provided (at least one argument is required).\n\n"
93
+ show_usage
94
+ end
95
+
96
+ def show_upload_start hostname
97
+ puts "Start upload to #{hostname}"
98
+ end
99
+
100
+ def show_upload_result result
101
+ puts result
102
+ end
103
+
104
+ def show_socket_error command
105
+ puts "Encountered network error while <#{command}>. Is http://www.codersdojo.com down?"
106
+ end
107
+
108
+ end
data/app/controller.rb ADDED
@@ -0,0 +1,50 @@
1
+ class Controller
2
+
3
+ def initialize view, hostname
4
+ @view = view
5
+ @hostname = hostname
6
+ end
7
+
8
+ def help command=nil
9
+ if command then
10
+ @view.show_detailed_help command.downcase
11
+ else
12
+ @view.show_help
13
+ end
14
+ end
15
+
16
+ def generate framework, kata_file
17
+ shell = ShellWrapper.new
18
+ shell.scaffold framework
19
+ generator_text = IO.readlines("README").to_s;
20
+ generator_text = generator_text.gsub('#{kata_file}', kata_file)
21
+ generator_text = generator_text.gsub('#{$0}', $0)
22
+ generator_text = shell.make_os_specific generator_text
23
+ puts "\n" + generator_text
24
+ end
25
+
26
+ def start command, file
27
+ if not command or not file then
28
+ @view.show_missing_command_argument_error "start"
29
+ return
30
+ end
31
+ @view.show_start_kata command, file
32
+ dojo = Runner.new ShellWrapper.new, SessionIdGenerator.new
33
+ dojo.file = file
34
+ dojo.run_command = command
35
+ scheduler = Scheduler.new dojo
36
+ scheduler.start
37
+ end
38
+
39
+ def upload framework, session_directory
40
+ @view.show_upload_start @hostname
41
+ if session_directory then
42
+ uploader = Uploader.new @hostname, framework, session_directory
43
+ p uploader.upload
44
+ else
45
+ @view.show_missing_command_argument_error "upload"
46
+ end
47
+ end
48
+
49
+ end
50
+
@@ -0,0 +1,34 @@
1
+ class FilenameFormatter
2
+
3
+ CODERSDOJO_WORKSPACE = ".codersdojo"
4
+ RESULT_FILE = "result.txt"
5
+ STATE_DIR_PREFIX = "state_"
6
+
7
+ def self.state_dir_prefix
8
+ STATE_DIR_PREFIX
9
+ end
10
+
11
+ def source_code_file state_dir
12
+ Dir.entries(state_dir).each { |file|
13
+ return state_file state_dir, file unless file =='..' || file == '.' ||file == RESULT_FILE }
14
+ end
15
+
16
+ def result_file state_dir
17
+ state_file state_dir, RESULT_FILE
18
+ end
19
+
20
+ def state_file state_dir, file
21
+ "#{state_dir}/#{file}"
22
+ end
23
+
24
+ def state_dir session_id, step
25
+ session_directory = session_dir session_id
26
+ "#{session_directory}/#{STATE_DIR_PREFIX}#{step}"
27
+ end
28
+
29
+ def session_dir session_id
30
+ "#{CODERSDOJO_WORKSPACE}/#{session_id}"
31
+ end
32
+
33
+ end
34
+
data/app/progress.rb ADDED
@@ -0,0 +1,19 @@
1
+ class Progress
2
+
3
+ def self.write_empty_progress states
4
+ STDOUT.print "#{states} states to upload"
5
+ STDOUT.print "["+" "*states+"]"
6
+ STDOUT.print "\b"*(states+1)
7
+ STDOUT.flush
8
+ end
9
+
10
+ def self.next
11
+ STDOUT.print "."
12
+ STDOUT.flush
13
+ end
14
+
15
+ def self.end
16
+ STDOUT.puts
17
+ end
18
+ end
19
+
data/app/runner.rb ADDED
@@ -0,0 +1,39 @@
1
+ require "filename_formatter"
2
+
3
+ class Runner
4
+
5
+ attr_accessor :file, :run_command
6
+
7
+ def initialize shell, session_provider
8
+ @filename_formatter = FilenameFormatter.new
9
+ @shell = shell
10
+ @session_provider = session_provider
11
+ end
12
+
13
+ def start
14
+ init_session
15
+ execute
16
+ end
17
+
18
+ def init_session
19
+ @step = 0
20
+ @session_id = @session_provider.generate_id
21
+ @shell.mkdir_p(@filename_formatter.session_dir @session_id)
22
+ end
23
+
24
+ def execute
25
+ change_time = @shell.ctime @file
26
+ if change_time == @previous_change_time then
27
+ return
28
+ end
29
+ result = @shell.execute "#{@run_command} #{@file}"
30
+ state_dir = @filename_formatter.state_dir @session_id, @step
31
+ @shell.mkdir state_dir
32
+ @shell.cp @file, state_dir
33
+ @shell.write_file @filename_formatter.result_file(state_dir), result
34
+ @step += 1
35
+ @previous_change_time = change_time
36
+ end
37
+
38
+ end
39
+
data/app/scheduler.rb ADDED
@@ -0,0 +1,15 @@
1
+ class Scheduler
2
+
3
+ def initialize runner
4
+ @runner = runner
5
+ end
6
+
7
+ def start
8
+ @runner.start
9
+ while true do
10
+ sleep 1
11
+ @runner.execute
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,17 @@
1
+ class SessionIdGenerator
2
+
3
+ def generate_id time=Time.new
4
+ year = format_to_length time.year, 4
5
+ month = format_to_length time.month, 2
6
+ day = format_to_length time.day, 2
7
+ hour = format_to_length time.hour, 2
8
+ minute = format_to_length time.min, 2
9
+ second = format_to_length time.sec, 2
10
+ "#{year}-#{month}-#{day}_#{hour}-#{minute}-#{second}"
11
+ end
12
+
13
+ def format_to_length value, len
14
+ value.to_s.rjust len,"0"
15
+ end
16
+
17
+ end
@@ -0,0 +1,96 @@
1
+ require 'tempfile'
2
+
3
+ class ShellWrapper
4
+
5
+ MAX_STDOUT_LENGTH = 100000
6
+ ANY_TEMPLATE = "any"
7
+
8
+ def cp source, destination
9
+ FileUtils.cp source, destination
10
+ end
11
+
12
+ def mkdir dir
13
+ FileUtils.mkdir dir
14
+ end
15
+
16
+ def mkdir_p dirs
17
+ FileUtils.mkdir_p dirs
18
+ end
19
+
20
+ def execute command
21
+ spec_pipe = IO.popen(command, "r")
22
+ result = spec_pipe.read MAX_STDOUT_LENGTH
23
+ spec_pipe.close
24
+ puts result
25
+ result
26
+ end
27
+
28
+ def write_file filename, content
29
+ file = File.new filename, "w"
30
+ file << content
31
+ file.close
32
+ end
33
+
34
+ def read_file filename
35
+ file = File.new filename
36
+ content = file.read
37
+ file.close
38
+ content
39
+ end
40
+
41
+ def ctime filename
42
+ File.new(filename).ctime
43
+ end
44
+
45
+ def list_templates
46
+ templates = real_dir_entries template_path
47
+ templates.delete ANY_TEMPLATE
48
+ templates.join(', ')
49
+ end
50
+
51
+ def scaffold template
52
+ begin
53
+ dir_path = "#{template_path}/#{template}"
54
+ to_copy = real_dir_entries dir_path
55
+ rescue
56
+ dir_path = "#{template_path}/#{ANY_TEMPLATE}"
57
+ to_copy = real_dir_entries dir_path
58
+ end
59
+ to_copy.each do |item|
60
+ FileUtils.cp_r "#{dir_path}/#{item}", "."
61
+ end
62
+ end
63
+
64
+ def template_path
65
+ file_path_elements = __FILE__.split '/'
66
+ file_path_elements[-2..-1] = nil
67
+ (file_path_elements << 'templates').join '/'
68
+ end
69
+
70
+ def real_dir_entries dir
71
+ current_and_parent = 2
72
+ Dir.new(dir).entries.drop current_and_parent
73
+ end
74
+
75
+ def make_os_specific text
76
+ text.gsub('%sh%', shell_extension).gsub('%:%', path_separator).gsub('%rm%', remove_command_name)
77
+ end
78
+
79
+ def remove_command_name
80
+ windows? ? 'delete' : 'rm'
81
+ end
82
+
83
+ def shell_extension
84
+ windows? ? 'cmd' : 'sh'
85
+ end
86
+
87
+ def path_separator
88
+ windows? ? ';' : ':'
89
+ end
90
+
91
+ def windows?
92
+ RUBY_PLATFORM.downcase.include? "windows"
93
+ end
94
+
95
+ end
96
+
@@ -0,0 +1,57 @@
1
+ require 'filename_formatter'
2
+
3
+ class StateReader
4
+
5
+ attr_accessor :session_id, :next_step
6
+
7
+ def initialize shell
8
+ @filename_formatter = FilenameFormatter.new
9
+ @shell = shell
10
+ @next_step = 0
11
+ end
12
+
13
+ def state_count
14
+ dummy_dirs_current_and_parent = 2
15
+ Dir.new(@filename_formatter.session_dir @session_id).count - dummy_dirs_current_and_parent
16
+ end
17
+
18
+ def enough_states?
19
+ state_count >= states_needed_for_one_move
20
+ end
21
+
22
+ def states_needed_for_one_move
23
+ 2
24
+ end
25
+
26
+ def get_state_dir
27
+ @filename_formatter.state_dir(@session_id, @next_step)
28
+ end
29
+
30
+ def has_next_state
31
+ File.exist?(get_state_dir)
32
+ end
33
+
34
+ def read_next_state
35
+ state = State.new
36
+ state_dir = get_state_dir
37
+ state.time = @shell.ctime state_dir
38
+ state.code = @shell.read_file @filename_formatter.source_code_file(state_dir)
39
+ state.result = @shell.read_file @filename_formatter.result_file(state_dir)
40
+ @next_step += 1
41
+ state
42
+ end
43
+
44
+ end
45
+
46
+ class State
47
+
48
+ attr_accessor :time, :code, :result
49
+
50
+ def initialize time=nil, code=nil, result=nil
51
+ @time = time
52
+ @code = code
53
+ @result = result
54
+ end
55
+
56
+ end
57
+
data/app/uploader.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'state_reader'
2
+ require 'progress'
3
+ require 'xml_element_extractor'
4
+ require 'rest_client'
5
+
6
+ class Uploader
7
+
8
+ def initialize hostname, framework, session_dir, state_reader = StateReader.new(ShellWrapper.new)
9
+ @hostname = hostname
10
+ @framework = framework
11
+ @state_reader = state_reader
12
+ @state_reader.session_id = session_dir.gsub('.codersdojo/', '')
13
+ end
14
+
15
+ def upload_kata
16
+ RestClient.post "#{@hostname}#{@@kata_path}", {:framework => @framework}
17
+ end
18
+
19
+ def upload_state kata_id
20
+ state = @state_reader.read_next_state
21
+ RestClient.post "#{@hostname}#{@@kata_path}/#{kata_id}#{@@state_path}", {:code => state.code, :result => state.result, :created_at => state.time}
22
+ Progress.next
23
+ end
24
+
25
+ def upload_states kata_id
26
+ Progress.write_empty_progress @state_reader.state_count
27
+ while @state_reader.has_next_state
28
+ upload_state kata_id
29
+ end
30
+ Progress.end
31
+ end
32
+
33
+ def upload_kata_and_states
34
+ kata = upload_kata
35
+ upload_states(XMLElementExtractor.extract('kata/id', kata))
36
+ "This is the link to review and comment your kata #{XMLElementExtractor.extract('kata/short-url', kata)}"
37
+ end
38
+
39
+ def upload
40
+ return upload_kata_and_states if @state_reader.enough_states?
41
+ return "You need at least two states"
42
+ end
43
+
44
+ private
45
+ @@kata_path = '/katas'
46
+ @@state_path = '/states'
47
+
48
+ end
49
+
@@ -0,0 +1,11 @@
1
+ require "rexml/document"
2
+
3
+ class XMLElementExtractor
4
+ def self.extract element, xml_string
5
+ doc = REXML::Document.new xml_string
6
+ return doc.elements.each(element) do |found|
7
+ return found.text
8
+ end
9
+ end
10
+ end
11
+
data/bin/codersdojo CHANGED
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  codersdojo_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'app'))
3
+ infrastructure_dir = "#{codersdojo_dir}/infrastructure"
4
+ models_dir = "#{codersdojo_dir}/models"
3
5
  $LOAD_PATH.unshift(codersdojo_dir) unless $LOAD_PATH.include?(codersdojo_dir)
6
+ $LOAD_PATH.unshift(infrastructure_dir) unless $LOAD_PATH.include?(infrastructure_dir)
7
+ $LOAD_PATH.unshift(models_dir) unless $LOAD_PATH.include?(models_dir)
4
8
  require 'codersdojo'
@@ -0,0 +1,54 @@
1
+ require 'argument_parser'
2
+
3
+ describe ArgumentParser do
4
+
5
+ before (:each) do
6
+ @controller_mock = mock.as_null_object
7
+ @parser = ArgumentParser.new @controller_mock
8
+ end
9
+
10
+ it "should reject empty command" do
11
+ lambda{@parser.parse []}.should raise_error
12
+ end
13
+
14
+ it "should reject unknown command" do
15
+ lambda{@parser.parse "unknown command"}.should raise_error
16
+ end
17
+
18
+ it "should accept help command" do
19
+ @controller_mock.should_receive(:help).with(nil)
20
+ @parser.parse ["help"]
21
+ end
22
+
23
+ it "should accept start command" do
24
+ @controller_mock.should_receive(:start).with "aCommand", "aFile"
25
+ @parser.parse ["start", "aCommand","aFile"]
26
+ end
27
+
28
+ it "should prepend *.sh start scripts with 'bash'" do
29
+ @controller_mock.should_receive(:start).with "bash aCommand.sh", "aFile"
30
+ @parser.parse ["start", "aCommand.sh","aFile"]
31
+ end
32
+
33
+ it "should prepend *.bat start scripts with 'start'" do
34
+ @controller_mock.should_receive(:start).with "start aCommand.bat", "aFile"
35
+ @parser.parse ["start", "aCommand.bat","aFile"]
36
+ end
37
+
38
+ it "should prepend *.cmd start scripts with 'start'" do
39
+ @controller_mock.should_receive(:start).with "start aCommand.cmd", "aFile"
40
+ @parser.parse ["start", "aCommand.cmd","aFile"]
41
+ end
42
+
43
+ it "should accept upload command" do
44
+ @controller_mock.should_receive(:upload).with "framework", "dir"
45
+ @parser.parse ["upload", "framework", "dir"]
46
+ end
47
+
48
+ it "should accept uppercase commands" do
49
+ @controller_mock.should_receive(:help).with(nil)
50
+ @parser.parse ["HELP"]
51
+ end
52
+
53
+ end
54
+
@@ -0,0 +1,23 @@
1
+ require 'progress'
2
+
3
+ describe Progress do
4
+
5
+ it 'should print infos and empty progress in initialization' do
6
+ STDOUT.should_receive(:print).with("2 states to upload")
7
+ STDOUT.should_receive(:print).with("[ ]")
8
+ STDOUT.should_receive(:print).with("\b\b\b")
9
+ Progress.write_empty_progress 2
10
+ end
11
+
12
+ it 'should print dots and flush in next' do
13
+ STDOUT.should_receive(:print).with(".")
14
+ STDOUT.should_receive(:flush)
15
+ Progress.next
16
+ end
17
+
18
+ it 'should print empty line in end' do
19
+ STDOUT.should_receive(:puts)
20
+ Progress.end
21
+ end
22
+
23
+ end
@@ -0,0 +1,59 @@
1
+ require 'runner'
2
+
3
+ describe Runner, "in run mode" do
4
+
5
+ WORKSPACE_DIR = ".codersdojo"
6
+ SESSION_ID = "id0815"
7
+ SESSION_DIR = "#{WORKSPACE_DIR}/#{SESSION_ID}"
8
+ STATE_DIR_PREFIX = "#{SESSION_DIR}/state_"
9
+
10
+ before (:each) do
11
+ @shell_mock = mock.as_null_object
12
+ @session_id_provider_mock = mock.as_null_object
13
+ @session_id_provider_mock.should_receive(:generate_id).and_return SESSION_ID
14
+ @runner = Runner.new @shell_mock, @session_id_provider_mock
15
+ @runner.file = "my_file.rb"
16
+ @runner.run_command = "ruby"
17
+ end
18
+
19
+ it "should create codersdojo directory if it doesn't exist with session sub-directory" do
20
+ @shell_mock.should_receive(:mkdir_p).with SESSION_DIR
21
+ @runner.start
22
+ end
23
+
24
+ it "should run ruby command on kata file given as argument" do
25
+ @shell_mock.should_receive(:execute).with "ruby my_file.rb"
26
+ @runner.start
27
+ end
28
+
29
+ it "should create a state directory for every state" do
30
+ @shell_mock.should_receive(:ctime).with("my_file.rb").and_return 1
31
+ @shell_mock.should_receive(:mkdir).with "#{STATE_DIR_PREFIX}0"
32
+ @shell_mock.should_receive(:cp).with "my_file.rb", "#{STATE_DIR_PREFIX}0"
33
+ @runner.start
34
+ @shell_mock.should_receive(:ctime).with("my_file.rb").and_return 2
35
+ @shell_mock.should_receive(:mkdir).with "#{STATE_DIR_PREFIX}1"
36
+ @shell_mock.should_receive(:cp).with "my_file.rb", "#{STATE_DIR_PREFIX}1"
37
+ @runner.execute
38
+ end
39
+
40
+ it "should not run if the kata file wasn't modified" do
41
+ a_time = Time.new
42
+ @shell_mock.should_receive(:ctime).with("my_file.rb").and_return a_time
43
+ @shell_mock.should_receive(:mkdir).with "#{STATE_DIR_PREFIX}0"
44
+ @shell_mock.should_receive(:cp).with "my_file.rb", "#{STATE_DIR_PREFIX}0"
45
+ @runner.start
46
+ @shell_mock.should_receive(:ctime).with("my_file.rb").and_return a_time
47
+ @shell_mock.should_not_receive(:mkdir).with "#{STATE_DIR_PREFIX}1"
48
+ @shell_mock.should_not_receive(:cp).with "my_file.rb", "#{STATE_DIR_PREFIX}1"
49
+ @runner.execute
50
+ end
51
+
52
+ it "should capture run result into state directory" do
53
+ @shell_mock.should_receive(:execute).and_return "spec result"
54
+ @shell_mock.should_receive(:write_file).with "#{STATE_DIR_PREFIX}0/result.txt", "spec result"
55
+ @runner.start
56
+ end
57
+
58
+ end
59
+
@@ -0,0 +1,21 @@
1
+ require 'session_id_generator'
2
+
3
+ describe SessionIdGenerator do
4
+
5
+ before (:each) do
6
+ @time_mock = mock
7
+ @generator = SessionIdGenerator.new
8
+ end
9
+
10
+ it "should format id as yyyy-mm-dd_hh-mm-ss" do
11
+ @time_mock.should_receive(:year).and_return 2010
12
+ @time_mock.should_receive(:month).and_return 8
13
+ @time_mock.should_receive(:day).and_return 7
14
+ @time_mock.should_receive(:hour).and_return 6
15
+ @time_mock.should_receive(:min).and_return 5
16
+ @time_mock.should_receive(:sec).and_return 0
17
+ @generator.generate_id(@time_mock).should == "2010-08-07_06-05-00"
18
+ end
19
+
20
+ end
21
+