codersdojo 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ class ArgumentParser
2
+
3
+ def initialize controller
4
+ @controller = controller
5
+ end
6
+
7
+ def parse params
8
+ command = params[0] ? params[0] : ""
9
+ if command.downcase == "help" then
10
+ @controller.help params[1]
11
+ elsif command.downcase == "setup" then
12
+ @controller.generate params[1], params[2] ? params[2] : '<kata_file>'
13
+ elsif command.downcase == "upload" then
14
+ @controller.upload params[1], params[2]
15
+ elsif command.downcase == "start" then
16
+ run_command = expand_run_command params[1]
17
+ @controller.start run_command, params[2]
18
+ elsif command.downcase == "spec" then
19
+ # 'spec" is for testing purpose only: do nothing special
20
+ else
21
+ raise ArgumentError
22
+ end
23
+ end
24
+
25
+ def expand_run_command command
26
+ if command.end_with?(".sh") then
27
+ "bash #{command}"
28
+ elsif command.end_with?(".bat") or command.end_with?(".cmd") then
29
+ "start #{command}"
30
+ else
31
+ command
32
+ end
33
+ end
34
+
35
+ end
36
+
data/app/codersdojo.rb CHANGED
@@ -1,622 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "tempfile"
4
- require "rexml/document"
5
- require 'rest_client'
6
-
7
- class Scheduler
8
-
9
- def initialize runner
10
- @runner = runner
11
- end
12
-
13
- def start
14
- @runner.start
15
- while true do
16
- sleep 1
17
- @runner.execute
18
- end
19
- end
20
-
21
- end
22
-
23
- class Runner
24
-
25
- attr_accessor :file, :run_command
26
-
27
- def initialize shell, session_provider
28
- @filename_formatter = FilenameFormatter.new
29
- @shell = shell
30
- @session_provider = session_provider
31
- end
32
-
33
- def start
34
- init_session
35
- execute
36
- end
37
-
38
- def init_session
39
- @step = 0
40
- @session_id = @session_provider.generate_id
41
- @shell.mkdir_p(@filename_formatter.session_dir @session_id)
42
- end
43
-
44
- def execute
45
- change_time = @shell.ctime @file
46
- if change_time == @previous_change_time then
47
- return
48
- end
49
- result = @shell.execute "#{@run_command} #{@file}"
50
- state_dir = @filename_formatter.state_dir @session_id, @step
51
- @shell.mkdir state_dir
52
- @shell.cp @file, state_dir
53
- @shell.write_file @filename_formatter.result_file(state_dir), result
54
- @step += 1
55
- @previous_change_time = change_time
56
- end
57
-
58
- end
59
-
60
- class Shell
61
-
62
- MAX_STDOUT_LENGTH = 100000
63
-
64
- def cp source, destination
65
- FileUtils.cp source, destination
66
- end
67
-
68
- def mkdir dir
69
- FileUtils.mkdir dir
70
- end
71
-
72
- def mkdir_p dirs
73
- FileUtils.mkdir_p dirs
74
- end
75
-
76
- def execute command
77
- spec_pipe = IO.popen(command, "r")
78
- result = spec_pipe.read MAX_STDOUT_LENGTH
79
- spec_pipe.close
80
- puts result
81
- result
82
- end
83
-
84
- def write_file filename, content
85
- file = File.new filename, "w"
86
- file << content
87
- file.close
88
- end
89
-
90
- def read_file filename
91
- file = File.new filename
92
- content = file.read
93
- file.close
94
- content
95
- end
96
-
97
- def ctime filename
98
- File.new(filename).ctime
99
- end
100
-
101
- def make_os_specific text
102
- text.gsub('%sh%', shell_extension).gsub('%:%', path_separator).gsub('%rm%', remove_command_name)
103
- end
104
-
105
- def remove_command_name
106
- windows? ? 'delete' : 'rm'
107
- end
108
-
109
- def shell_extension
110
- windows? ? 'cmd' : 'sh'
111
- end
112
-
113
- def path_separator
114
- windows? ? ';' : ':'
115
- end
116
-
117
- def windows?
118
- RUBY_PLATFORM.downcase.include? "windows"
119
- end
120
-
121
- end
122
-
123
- class SessionIdGenerator
124
-
125
- def generate_id time=Time.new
126
- year = format_to_length time.year, 4
127
- month = format_to_length time.month, 2
128
- day = format_to_length time.day, 2
129
- hour = format_to_length time.hour, 2
130
- minute = format_to_length time.min, 2
131
- second = format_to_length time.sec, 2
132
- "#{year}-#{month}-#{day}_#{hour}-#{minute}-#{second}"
133
- end
134
-
135
- def format_to_length value, len
136
- value.to_s.rjust len,"0"
137
- end
138
-
139
- end
140
-
141
- class StateReader
142
-
143
- attr_accessor :session_id, :next_step
144
-
145
- def initialize shell
146
- @filename_formatter = FilenameFormatter.new
147
- @shell = shell
148
- @next_step = 0
149
- end
150
-
151
- def state_count
152
- Dir.new(@filename_formatter.session_dir @session_id).count - 2
153
- end
154
-
155
- def enough_states?
156
- state_count >= 2
157
- end
158
-
159
- def get_state_dir
160
- @filename_formatter.state_dir(@session_id, @next_step)
161
- end
162
-
163
- def has_next_state
164
- File.exist?(get_state_dir)
165
- end
166
-
167
- def read_next_state
168
- state = State.new
169
- state_dir = get_state_dir
170
- state.time = @shell.ctime state_dir
171
- state.code = @shell.read_file @filename_formatter.source_code_file(state_dir)
172
- state.result = @shell.read_file @filename_formatter.result_file(state_dir)
173
- @next_step += 1
174
- state
175
- end
176
-
177
- end
178
-
179
- class FilenameFormatter
180
-
181
- CODERSDOJO_WORKSPACE = ".codersdojo"
182
- RESULT_FILE = "result.txt"
183
- STATE_DIR_PREFIX = "state_"
184
-
185
-
186
- def source_code_file state_dir
187
- Dir.entries(state_dir).each { |file|
188
- return state_file state_dir, file unless file =='..' || file == '.' ||file == RESULT_FILE }
189
- end
190
-
191
- def result_file state_dir
192
- state_file state_dir, RESULT_FILE
193
- end
194
-
195
- def state_file state_dir, file
196
- "#{state_dir}/#{file}"
197
- end
198
-
199
- def state_dir session_id, step
200
- session_directory = session_dir session_id
201
- "#{session_directory}/#{STATE_DIR_PREFIX}#{step}"
202
- end
203
-
204
- def session_dir session_id
205
- "#{CODERSDOJO_WORKSPACE}/#{session_id}"
206
- end
207
-
208
- end
209
-
210
- class Progress
211
-
212
- def self.write_empty_progress states
213
- STDOUT.print "#{states} states to upload"
214
- STDOUT.print "["+" "*states+"]"
215
- STDOUT.print "\b"*(states+1)
216
- STDOUT.flush
217
- end
218
-
219
- def self.next
220
- STDOUT.print "."
221
- STDOUT.flush
222
- end
223
-
224
- def self.end
225
- STDOUT.puts
226
- end
227
- end
228
-
229
- class Uploader
230
-
231
- def initialize hostname, framework, session_dir, state_reader = StateReader.new(Shell.new)
232
- @hostname = hostname
233
- @framework = framework
234
- @state_reader = state_reader
235
- @state_reader.session_id = session_dir.gsub('.codersdojo/', '')
236
- end
237
-
238
- def upload_kata
239
- RestClient.post "#{@hostname}#{@@kata_path}", {:framework => @framework}
240
- end
241
-
242
- def upload_state kata_id
243
- state = @state_reader.read_next_state
244
- RestClient.post "#{@hostname}#{@@kata_path}/#{kata_id}#{@@state_path}", {:code => state.code, :result => state.result, :created_at => state.time}
245
- Progress.next
246
- end
247
-
248
- def upload_states kata_id
249
- Progress.write_empty_progress @state_reader.state_count
250
- while @state_reader.has_next_state
251
- upload_state kata_id
252
- end
253
- Progress.end
254
- end
255
-
256
- def upload_kata_and_states
257
- kata = upload_kata
258
- upload_states(XMLElementExtractor.extract('kata/id', kata))
259
- "This is the link to review and comment your kata #{XMLElementExtractor.extract('kata/short-url', kata)}"
260
- end
261
-
262
- def upload
263
- return upload_kata_and_states if @state_reader.enough_states?
264
- return "You need at least two states"
265
- end
266
-
267
- private
268
- @@kata_path = '/katas'
269
- @@state_path = '/states'
270
-
271
- end
272
-
273
- class XMLElementExtractor
274
- def self.extract element, xml_string
275
- doc = REXML::Document.new xml_string
276
- return doc.elements.each(element) do |found|
277
- return found.text
278
- end
279
- end
280
- end
281
-
282
- class State
283
-
284
- attr_accessor :time, :code, :result
285
-
286
- def initialize time=nil, code=nil, result=nil
287
- @time = time
288
- @code = code
289
- @result = result
290
- end
291
-
292
- end
293
-
294
- class ArgumentParser
295
-
296
- def initialize controller
297
- @controller = controller
298
- end
299
-
300
- def parse params
301
- command = params[0] ? params[0] : ""
302
- if command.downcase == "help" then
303
- @controller.help params[1]
304
- elsif command.downcase == "show-examples" then
305
- @controller.show_examples
306
- elsif command.downcase == "setup" then
307
- @controller.generate params[1], params[2] ? params[2] : '<kata_file>'
308
- elsif command.downcase == "upload" then
309
- @controller.upload params[1], params[2]
310
- elsif command.downcase == "start" then
311
- run_command = expand_run_command params[1]
312
- @controller.start run_command, params[2]
313
- elsif command.downcase == "spec" then
314
- # 'spec" is for testing purpose only: do nothing special
315
- else
316
- raise ArgumentError
317
- end
318
- end
319
-
320
- def expand_run_command command
321
- if command.end_with?(".sh") then
322
- "bash #{command}"
323
- elsif command.end_with?(".bat") or command.end_with?(".cmd") then
324
- "start #{command}"
325
- else
326
- command
327
- end
328
- end
329
-
330
- end
331
-
332
- class Controller
333
-
334
- def initialize view, hostname
335
- @view = view
336
- @hostname = hostname
337
- end
338
-
339
- def help command=nil
340
- if command then
341
- @view.show_detailed_help command.downcase
342
- else
343
- @view.show_help
344
- end
345
- end
346
-
347
- def show_examples
348
- @view.show_examples
349
- end
350
-
351
- def generate framework, kata_file
352
- generator = GeneratorFactory.new.create_generator framework
353
- shell = Shell.new
354
- generator_text = generator.generate kata_file
355
- generator_text = shell.make_os_specific generator_text
356
- puts generator_text
357
- end
358
-
359
- def start command, file
360
- if not command or not file then
361
- @view.show_missing_command_argument_error "start"
362
- return
363
- end
364
- @view.show_start_kata command, file
365
- dojo = Runner.new Shell.new, SessionIdGenerator.new
366
- dojo.file = file
367
- dojo.run_command = command
368
- scheduler = Scheduler.new dojo
369
- scheduler.start
370
- end
371
-
372
- def upload framework, session_directory
373
- @view.show_upload_start @hostname
374
- if session_directory then
375
- uploader = Uploader.new @hostname, framework, session_directory
376
- p uploader.upload
377
- else
378
- @view.show_missing_command_argument_error "upload"
379
- end
380
- end
381
-
382
- end
383
-
384
-
385
- class ConsoleView
386
-
387
- @@VERSION = '0.9'
388
-
389
- def show_help
390
- puts <<-helptext
391
- Personal CodersDojo, Version #{@@VERSION}, http://www.codersdojo.com, Copyright by it-agile GmbH (http://www.it-agile.de)
392
- PersonalCodersDojo automatically runs your tests of a code kata.
393
-
394
- helptext
395
- show_usage
396
- end
397
-
398
- def show_usage
399
- puts <<-helptext
400
- Usage: #{$0} command [options]
401
- Commands:
402
- help, -h, --help Print this help text.
403
- help <command> See the details of the command.
404
- setup <framework> <kata_file> Setup the environment for running the kata.
405
- upload <framework> <session_dir> Upload the kata to http://www.codersdojo.org
406
- show-examples Show some example usages.
407
-
408
- Report bugs to <codersdojo@it-agile.de>
409
- helptext
410
- end
411
-
412
- def show_examples
413
- puts <<-helptext
414
- Examples:
415
- :/dojo/my_kata% #{$0} setup ruby.test/unit prime
416
- Show the instructions how to setup the environment for kata execution with ruby.
417
-
418
- :/dojo/my_kata$ #{$0} upload ruby.test/unit .codersdojo/2010-11-02_16-21-53
419
- Upload the kata (written in Ruby with the test/unit framework) located in directory ".codersdojo/2010-11-02_16-21-53" to codersdojo.com.
420
- helptext
421
- end
422
-
423
- def show_detailed_help command
424
- if command == 'setup' then
425
- show_help_setup
426
- elsif command == 'start' then
427
- show_help_start
428
- elsif command == 'upload' then
429
- show_help_upload
430
- else
431
- show_help_unknown command
432
- end
433
- end
434
-
435
- def show_help_setup
436
- puts <<-helptext
437
- setup <framework> <kata_file_no_ext> Setup the environment for the kata for the given framework and kata file.
438
- The kata_file should not have an extension. Use 'prime' and not 'prime.java'.
439
- By now <framework> is one of clojure.is-test, java.junit, javascript.jspec,
440
- python.unittest, ruby.test/unit.
441
- Use ??? as framework if your framework isn't in the list.
442
- helptext
443
- end
444
-
445
- def show_help_start
446
- puts <<-helptext
447
- start <shell_command> <kata_file> Start the continuous test runner, that runs <shell-command> whenever <kata_file>
448
- changes. The <kata_file> has to include the whole source code of the kata.
449
- Whenever the test runner is started, it creates a new session directory in the
450
- directory .codersdojo where it logs the steps of the kata.
451
- helptext
452
- end
453
-
454
- def show_help_upload
455
- puts <<-helptext
456
- upload <framework> <session_directory> Upload the kata written with <framework> in <session_directory> to codersdojo.com.
457
- <session_directory> is relative to the working directory.
458
- Take a look at the generate command for the supported frameworks.
459
- If you used another framework, use ??? and send an email to codersdojo@it-agile.de
460
- helptext
461
- end
462
-
463
- def show_help_unknown command
464
- puts <<-helptext
465
- Command #{command} not known. Try '#{$0} help' to list the supported commands.
466
- helptext
467
- end
468
-
469
- def show_start_kata command, file
470
- puts "Starting PersonalCodersDojo with command #{command} and kata file #{file}. Use Ctrl+C to finish the kata."
471
- end
472
-
473
- def show_missing_command_argument_error command
474
- puts "Command <#{command}> recognized but no argument was provided (at least one argument is required).\n\n"
475
- show_usage
476
- end
477
-
478
- def show_upload_start hostname
479
- puts "Start upload to #{hostname}"
480
- end
481
-
482
- def show_upload_result result
483
- puts result
484
- end
485
-
486
- def show_socket_error command
487
- puts "Encountered network error while <#{command}>. Is http://www.codersdojo.com down?"
488
- end
489
-
490
- end
491
-
492
- class GeneratorFactory
493
-
494
- def initialize
495
- @frameworks = {"clojure.is-test" => ClosureGenerator,
496
- "java.junit" => JavaGenerator,
497
- "javascript.jspec" => JavascriptGenerator,
498
- "python.unittest" => PythonGenerator,
499
- "ruby.test/unit" => RubyGenerator}
500
- end
501
-
502
- def create_generator framework
503
- generator_class = @frameworks[framework]
504
- if generator_class.nil? then
505
- generator_class = AnyGenerator
506
- end
507
- generator_class.new
508
- end
509
-
510
- end
511
-
512
- class AnyGenerator
513
-
514
- def generate kata_file
515
- <<-generate_help
516
- You have to create two shell scripts manually:
517
- Create a shell script run-once.%sh% that runs the tests of your kata once.
518
-
519
- Create a second shell script run-endless.sh with this content:
520
- #{$0} start run-once.%sh% #{kata_file}.<extension>
521
-
522
- Run run-endless.%sh% and start your kata.
523
-
524
- Assumptions:
525
- - The whole kata source code is in the one #{kata_file}.<extension>.
526
- generate_help
527
- end
528
- end
529
-
530
- class ClosureGenerator
531
- def generate kata_file
532
- <<-generate_help
533
- You have to create two shell scripts manually:
534
- Create a shell script run-once.%sh% with this content:
535
- java -cp clojure-contrib.jar%:%clojure.jar clojure.main #{kata_file}.clj
536
-
537
- Create a second shell script run-endless.sh with this content:
538
- #{$0} start run-once.%sh% #{kata_file}.clj
539
-
540
- Run run-endless.%sh% and start your kata.
541
-
542
- Assumptions:
543
- - Java is installed on your system and 'java' is in the path.
544
- - clojure.jar and clojure-contrib.jar are placed in your work directory.
545
- generate_help
546
- end
547
- end
548
-
549
- class JavaGenerator
550
- def generate kata_file
551
- <<-generate_help
552
- You have to create two shell scripts manually:
553
- Create a shell script run-once.%sh% with this content:
554
- %rm% bin/#{kata_file}.class
555
- javac -cp lib/junit.jar -d bin #{kata_file}.java
556
- java -cp lib/junit.jar%:%bin #{kata_file}
557
-
558
- Create a second shell script run-endless.sh with this content:
559
- #{$0} start run-once.%sh% src/#{kata_file}.java
560
-
561
- Run run-endless.%sh% and start your kata.
562
-
563
- Assumptions:
564
- - A Java JDK is installed on your system and 'java' and 'javac' are in the path.
565
- - junit.jar is placed in a directory named 'lib'.
566
- - The kata source file is placed in the 'src' directory.
567
- - The kata class file is generated into the 'class' directory.
568
- - In the source file the classes are placed in the default package.
569
- - The kata source file has a main method that starts the tests.
570
- - If your IDE (like Eclipse) compiles the source file, you should remove the first two lines
571
- in the run-once.%sh% file.
572
- generate_help
573
- end
574
- end
575
-
576
-
577
- class JavascriptGenerator
578
- def generate kata_file
579
- <<-generate_help
580
- You have to create two shell scripts manually:
581
- Create a shell script run-once.%sh% with this content:
582
- jspec --rhino run
583
-
584
- Create a second shell script run-endless.sh with this content:
585
- #{$0} start run-once.%sh% #{kata_file}.js
586
-
587
- Run run-endless.%sh% and start your kata.
588
- generate_help
589
- end
590
- end
591
-
592
-
593
- class PythonGenerator
594
- def generate kata_file
595
- <<-generate_help
596
- You have to create two shell scripts manually:
597
- Create a shell script run-once.%sh% with this content:
598
- python #{kata_file}.py
599
-
600
- Create a second shell script run-endless.sh with this content:
601
- #{$0} start run-once.%sh% #{kata_file}.py
602
-
603
- Run run-endless.%sh% and start your kata.
604
- generate_help
605
- end
606
- end
607
-
608
-
609
- class RubyGenerator
610
- def generate kata_file
611
- <<-generate_help
612
- You have to create one shell script manually:
613
- Create a shell script run-endless.%sh% with this content:
614
- #{$0} start ruby #{kata_file}.rb
615
-
616
- Run run-endless.%sh% and start your kata.
617
- generate_help
618
- end
619
- end
3
+ require 'console_view'
4
+ require 'controller'
5
+ require 'argument_parser'
620
6
 
621
7
  def called_from_spec args
622
8
  args[0] == "spec"
@@ -626,7 +12,7 @@ end
626
12
  if not called_from_spec(ARGV) then
627
13
  view = ConsoleView.new
628
14
  hostname = "http://www.codersdojo.com"
629
- #hostname = "http://localhost:3000"
15
+ # hostname = "http://localhost:3000"
630
16
  controller = Controller.new view, hostname
631
17
  begin
632
18
  arg_parser = ArgumentParser.new controller