WatersOfOblivion-coltrane 0.0.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.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.0 2009-06-22
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,10 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ lib/coltrane.rb
6
+ script/console
7
+ script/destroy
8
+ script/generate
9
+ spec/coltrane_spec.rb
10
+ spec/spec_helper.rb
data/README.rdoc ADDED
@@ -0,0 +1,48 @@
1
+ = coltrane
2
+
3
+ * http://github.com/WatersOfOblivion/coltrane
4
+
5
+ == DESCRIPTION:
6
+
7
+ Coltrane is a Sinatra[http://sinatrarb.com]-like framework for FTP.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ None, yet.
12
+
13
+ == SYNOPSIS:
14
+
15
+ # TODO
16
+
17
+ == REQUIREMENTS:
18
+
19
+ * None, yet.
20
+
21
+ == INSTALL:
22
+
23
+ * sudo gem install WatersOfOblivion-coltrane -s http://gems.github.com/
24
+
25
+ == LICENSE:
26
+
27
+ (The MIT License)
28
+
29
+ Copyright (c) 2009 Jonathan Bryant
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining
32
+ a copy of this software and associated documentation files (the
33
+ 'Software'), to deal in the Software without restriction, including
34
+ without limitation the rights to use, copy, modify, merge, publish,
35
+ distribute, sublicense, and/or sell copies of the Software, and to
36
+ permit persons to whom the Software is furnished to do so, subject to
37
+ the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be
40
+ included in all copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
43
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
45
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
46
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
47
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
48
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
2
+ %w[rake rake/clean fileutils newgem rubigen].each { |f| require f }
3
+ require File.dirname(__FILE__) + '/lib/coltrane'
4
+
5
+ # Generate all the Rake tasks
6
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
7
+ $hoe = Hoe.new('coltrane', Coltrane::VERSION) do |p|
8
+ p.developer('Jonathan Bryant', 'jonathan@watersofoblivion.com')
9
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
10
+ # p.extra_deps = [
11
+ # ['activesupport','>= 2.0.2'],
12
+ # ]
13
+ p.extra_dev_deps = [
14
+ ['newgem', ">= #{::Newgem::VERSION}"]
15
+ ]
16
+
17
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
18
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
19
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
20
+ p.rsync_args = '-av --delete --ignore-errors'
21
+ end
22
+
23
+ require 'newgem/tasks' # load /tasks/*.rake
24
+ Dir['tasks/**/*.rake'].each { |t| load t }
25
+
26
+ # TODO - want other tests/tasks run by default? Add them to the list
27
+ task :default => :spec
data/lib/coltrane.rb ADDED
@@ -0,0 +1,306 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'rubygems'
5
+ require 'eventmachine'
6
+
7
+ module Coltrane
8
+ VERSION = '0.0.0'
9
+
10
+ class ColtraneError < Exception; end
11
+
12
+ class SyntaxError < ColtraneError
13
+ def initialize command, params
14
+ super "Invalid syntax for #{command}: #{params}"
15
+ end
16
+ end
17
+
18
+ class UnknownCommand < ColtraneError
19
+ def initialize cmd
20
+ super "Unknown command: #{cmd}"
21
+ end
22
+ end
23
+
24
+ class RoutingError < ColtraneError
25
+ def initialize cmd, path
26
+ super "Unknown route: #{cmd.upcase} #{path}"
27
+ end
28
+ end
29
+
30
+ class ProtocolError < ColtraneError
31
+ def initialize actual, *expected
32
+ super "Protocol error: expected one of #{expected.join(", ")}, received #{actual}. Goodbye"
33
+ end
34
+ end
35
+
36
+ class Request
37
+ class << self
38
+ def command *args, &block
39
+ name = args.shift
40
+ regexp = args.first.is_a?(Regexp) ? args.shift : /(.*)/
41
+ variables = args
42
+ block ||= Proc.new { |x| x }
43
+
44
+ variables.each do |var|
45
+ attr_accessor var unless var.nil? || (respond_to?(var) && respond_to?("#{var}=".to_sym))
46
+ end
47
+
48
+ define_method name do |params|
49
+ @command = name
50
+ match_data = regexp.match params
51
+ if match_data
52
+ variables.zip match_data.captures do |var, cap|
53
+ instance_variable_set "@#{var}", cap.strip if var
54
+ end
55
+ instance_eval &block if block_given?
56
+ else
57
+ raise SyntaxError.new @command, params
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ attr_accessor :command
64
+
65
+ def initialize req
66
+ req.strip =~ /^(\w+)(.*)$/
67
+ begin
68
+ send $1.downcase.to_sym, $2.strip
69
+ rescue NameError
70
+ raise UnknownCommand.new req
71
+ end
72
+ end
73
+
74
+ command :abor
75
+ command :acct, :account_info
76
+ command :allo, /(\d+)(\s+R\s+(\d+))?/, :size, nil, :max_record_size do
77
+ @size = @size.to_i
78
+ @max_record_size = @max_record_size.to_i if @max_record_size
79
+ end
80
+ command :appe, :path
81
+ command :cdup
82
+ command :cwd, :path
83
+ command :dele, :path
84
+ command :help, :command
85
+ command :list, :path
86
+ command :mtdm, :path
87
+ command :mkd, :path
88
+ command :mode, /([SBC])/i, :mode do
89
+ @mode.upcase!
90
+ end
91
+ command :nlst, :path
92
+ command :noop
93
+ command :pass, :password
94
+ command :pasv
95
+ command :port, /(\d{1,3}),(\d{1,3}),(\d{1,3}),(\d{1,3}),(\d{1,3}),(\d{1,3})/, :oct1, :oct2, :oct3, :oct4, :port1, :port2 do
96
+ self.class.attr_accessor :ip_address, :port_num
97
+ @ip_address = "#{@oct1}.#{@oct2}.#{@oct3}.#{@oct4}"
98
+ @port_num = @port1.to_i * 256 + @port2.to_i
99
+ end
100
+ command :pwd
101
+ command :quit
102
+ command :rein
103
+ command :rest, /(\d+)/, :position do
104
+ @position = @position.to_i
105
+ end
106
+ command :retr, :path
107
+ command :rmd, :path
108
+ command :rnfr, :path
109
+ command :rnto, :path
110
+ command :site, :site_cmd
111
+ command :size, :path
112
+ command :stat, :path
113
+ command :stor, :path
114
+ command :stou
115
+ command :stru, /([FRP])/i, :structure do
116
+ @structure.upcase!
117
+ end
118
+ command :syst
119
+ command :type, /([ILAE])\s*(N|T|C|\d+)?/i, :type, :second_type do
120
+ (@second_type ||= "").strip!
121
+ @type.upcase!
122
+ if ["A", "E"].include? @type
123
+ @second_type ||= "N"
124
+ @second_type.upcase!
125
+ elsif @type == "L"
126
+ @second_type = @second_type.to_i
127
+ end
128
+ end
129
+ command :user, :username
130
+ end
131
+
132
+ class Response
133
+ attr_accessor :code, :status
134
+
135
+ def initialize code = 0, status = ""
136
+ @code, @status = code, status
137
+ end
138
+
139
+ def to_s
140
+ status = @status.split(/\r?\n/m)
141
+ final = status.pop
142
+ status.map! { |s| "#{@code}-#{s}\r\n" }
143
+ status.push "#{@code} #{final}\r\n"
144
+ status.join
145
+ end
146
+ end
147
+
148
+ class Application < EventMachine::Connection
149
+ class << self
150
+ def list path, &block
151
+ route :list, path, &block
152
+ end
153
+
154
+ def retr path, &block
155
+ route :retr, path, &block
156
+ end
157
+
158
+ def stor path, &block
159
+ route :stor, path, &block
160
+ end
161
+
162
+ def dele path, &block
163
+ route :dele, path, &block
164
+ end
165
+
166
+ def route cmd, path, &block
167
+ keys = []
168
+ special_chars = %w{. + ( )}
169
+ path = path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
170
+ case match
171
+ when "*"
172
+ keys << 'splat'
173
+ "(.*?)"
174
+ when *special_chars
175
+ Regexp.escape match
176
+ else
177
+ keys << $2[1..-1]
178
+ "([^/?&#]+)"
179
+ end
180
+ end
181
+ routes[cmd].push(:path => /^#{path}$/, :keys => keys, :block => block)
182
+ end
183
+
184
+ def routes
185
+ @routes ||= { :list => [], :retr => [], :stor => [], :dele => [] }
186
+ end
187
+
188
+ def launch!
189
+ EventMachine.run do
190
+ EventMachine.start_server 'localhost', 2100, self
191
+ end
192
+ end
193
+
194
+ private
195
+
196
+ def state name, &block
197
+ define_method name, &block
198
+ private name
199
+ end
200
+ end
201
+
202
+ def post_init
203
+ @routes = self.class.routes
204
+ @pwd = '/'
205
+ transition :welcome
206
+ send_data send(@state)
207
+ end
208
+
209
+ def receive_data data
210
+ @request = Request.new data
211
+ @response =
212
+ begin
213
+ send(@state)
214
+ rescue SyntaxError => ex
215
+ Response.new 501, ex.message
216
+ rescue UnknownCommand => ex
217
+ Response.new 502, ex.message
218
+ rescue RoutingError => ex
219
+ Response.new 550, ex.message
220
+ rescue ProtocolError => ex
221
+ Response.new 421, ex.message
222
+ rescue Exception => ex
223
+ Response.new 599, "Unknown error: #{ex.message}"
224
+ end
225
+ send_data @response
226
+
227
+ if @response && @response.code == 421
228
+ close_connection
229
+ end
230
+ end
231
+
232
+ state :welcome do
233
+ on do
234
+ transition :user
235
+ Response.new 220, "Welcome!"
236
+ end
237
+ end
238
+
239
+ state :user do
240
+ on :user do
241
+ if @request.username.downcase == "anonymous"
242
+ transition :anon
243
+ Response.new 331, "Please use your email address as a password"
244
+ else
245
+ @user = @request.username
246
+ transition :pass
247
+ Response.new 331, "Password needed"
248
+ end
249
+ end
250
+ end
251
+
252
+ state :anon do
253
+ on :pass do
254
+ transition :logged_in
255
+ Response.new 230, "Thanks"
256
+ end
257
+ end
258
+
259
+ state :pass do
260
+ on :pass do
261
+ if @user == "jbryant" && @request.password == "password"
262
+ transition :type
263
+ Response.new 230, "Authenticated"
264
+ else
265
+ Response.new 421, "Authentication failed. Goodbye"
266
+ end
267
+ end
268
+ end
269
+
270
+ state :type do
271
+ on :type do
272
+ @type = @request.type
273
+ transition :mode
274
+ Response.new 250, "Okay"
275
+ end
276
+ end
277
+
278
+ state :mode do
279
+ on :port do
280
+ puts "Active"
281
+ transition :ready
282
+ Response.new 227, "=1,1,1,1,1,1"
283
+ end
284
+
285
+ on :pasv do
286
+ puts "Passive"
287
+ transition :ready
288
+ Response.new 227, "=1,1,1,1,1,1"
289
+ end
290
+ end
291
+
292
+ state :ready do
293
+ Response.new 200, "Foo"
294
+ end
295
+
296
+ private
297
+
298
+ def transition state
299
+ @state = state
300
+ end
301
+
302
+ def on cmd = nil, &block
303
+ instance_eval &block if cmd.nil? || @request.command == cmd
304
+ end
305
+ end
306
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/coltrane.rb'}"
9
+ puts "Loading coltrane gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,115 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ module Coltrane
4
+ describe Request do
5
+ before do
6
+ io = mock "IO"
7
+ io.stub! :gets => "list /path"
8
+ @request = Request.new(io)
9
+ end
10
+
11
+ describe :new do
12
+ end
13
+
14
+ it "should have a :command attribute" do
15
+ @request.command.should == :list
16
+ @request.command = :foo
17
+ @request.command.should == :foo
18
+ end
19
+
20
+ it "should have a :path attribute" do
21
+ @request.path.should == "/path"
22
+ @request.path = "/htap"
23
+ @request.path.should == "/htap"
24
+ end
25
+ end
26
+
27
+ describe Response do
28
+ before do
29
+ @io = mock "IO"
30
+ @response = Response.new(@io)
31
+ end
32
+
33
+ it "should have a :code attribute" do
34
+ @response.code = 220
35
+ @response.code.should == 220
36
+ end
37
+
38
+ it "should have a :message attribute" do
39
+ @response.status = "Ready"
40
+ @response.status.should == "Ready"
41
+ end
42
+
43
+ describe :finalize! do
44
+ it "should output the response to the stream" do
45
+ @response.code = 220
46
+ @response.status = "Ready"
47
+ @io.should_receive(:puts).with "220 Ready\r\n"
48
+ @io.should_receive(:flush)
49
+ @response.finalize!
50
+ end
51
+ end
52
+ end
53
+
54
+ describe Application do
55
+ describe "class method" do
56
+ before do
57
+ @app = Class.new Application
58
+ @block = Proc.new {}
59
+ end
60
+
61
+ describe :list do
62
+ it "should delegate to route" do
63
+ @app.should_receive(:route).with(:list, '/path', &@block)
64
+ @app.list '/path', &@block
65
+ end
66
+ end
67
+
68
+ describe :retr do
69
+ it "should delegate to route" do
70
+ @app.should_receive(:route).with(:retr, '/path', &@block)
71
+ @app.retr '/path', &@block
72
+ end
73
+ end
74
+
75
+ describe :stor do
76
+ it "should delegate to route" do
77
+ @app.should_receive(:route).with(:stor, '/path', &@block)
78
+ @app.stor '/path', &@block
79
+ end
80
+ end
81
+
82
+ describe :dele do
83
+ it "should delegate to route" do
84
+ @app.should_receive(:route).with(:dele, '/path', &@block)
85
+ @app.dele '/path', &@block
86
+ end
87
+ end
88
+
89
+ describe :route do
90
+ it "should accept a command, a path, and a block" do
91
+ @app.route :list, '/path' do end
92
+ end
93
+
94
+ it "should store the route by command" do
95
+ @app.routes
96
+ end
97
+ end
98
+
99
+ describe :launch! do
100
+ it "should start the application" do
101
+ @app.launch!
102
+ end
103
+ end
104
+ end
105
+
106
+ describe :new do
107
+ end
108
+
109
+ describe :main_loop do
110
+ end
111
+
112
+ describe :process do
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,10 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
5
+ gem 'rspec'
6
+ require 'spec'
7
+ end
8
+
9
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
10
+ require 'coltrane'
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: WatersOfOblivion-coltrane
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Bryant
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-23 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: newgem
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.4.1
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.0
34
+ version:
35
+ description: Coltrane is a Sinatra[http://sinatrarb.com]-like framework for FTP.
36
+ email:
37
+ - jonathan@watersofoblivion.com
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - History.txt
44
+ - Manifest.txt
45
+ - README.rdoc
46
+ files:
47
+ - History.txt
48
+ - Manifest.txt
49
+ - README.rdoc
50
+ - Rakefile
51
+ - lib/coltrane.rb
52
+ - script/console
53
+ - script/destroy
54
+ - script/generate
55
+ - spec/coltrane_spec.rb
56
+ - spec/spec_helper.rb
57
+ has_rdoc: true
58
+ homepage: http://github.com/WatersOfOblivion/coltrane
59
+ post_install_message:
60
+ rdoc_options:
61
+ - --main
62
+ - README.rdoc
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ requirements: []
78
+
79
+ rubyforge_project: coltrane
80
+ rubygems_version: 1.2.0
81
+ signing_key:
82
+ specification_version: 2
83
+ summary: Coltrane is a Sinatra[http://sinatrarb.com]-like framework for FTP.
84
+ test_files: []
85
+