WatersOfOblivion-coltrane 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+