gopher2000 0.5.3 → 0.7.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/README.markdown CHANGED
@@ -25,11 +25,10 @@ Features
25
25
  * built on Event Machine.
26
26
  * Easy to mount directories and serve up files.
27
27
  * built in logging and stats.
28
- * Runs on Ruby 1.9.2 with all the modern conveniences.
29
28
 
30
29
  Requirements
31
30
  ------------
32
- * Ruby 1.9.2 or greater
31
+ * Ruby 2 or greater
33
32
  * Nerves of steel
34
33
 
35
34
  Examples
@@ -59,7 +58,7 @@ menu :index do
59
58
  br(2)
60
59
 
61
60
  # link somewhere
62
- link 'current time', '/time'
61
+ text_link 'current time', '/time'
63
62
  br
64
63
  end
65
64
 
@@ -108,6 +107,34 @@ Command line options will override defaults specified in your script
108
107
  -- so you can try out things on a different port/address if needed.
109
108
 
110
109
 
110
+ Docker
111
+ ------
112
+
113
+ There's a pretty simple docker script which you can use to run an
114
+ app. To run one of the included examples, you could do something like:
115
+
116
+
117
+ ```
118
+ docker build -t gopher2000 .
119
+ docker run -p 7070:7070 --rm -it gopher2000 examples/simple.rb
120
+ ```
121
+
122
+ This will run the `simple` example on port 7070. You can view it
123
+ locally by running something like:
124
+
125
+ ```
126
+ lynx gopher://0.0.0.0:7070
127
+ ```
128
+
129
+ The Dockerfile is also published to Docker Hub, so you could run
130
+ something like this:
131
+
132
+ ```
133
+ docker run -p 7070:7070 --rm -v $PWD:/opt muffinista/gopher2000 /opt/gopher-script.rb
134
+ ```
135
+
136
+
137
+
111
138
  Developing Gopher Sites
112
139
  -----------------------
113
140
 
data/bin/gopher2000 CHANGED
@@ -31,7 +31,7 @@ params = {
31
31
 
32
32
  opts.on('-d', '--debug', "run in debug mode") { params[:debug] = true }
33
33
  opts.on('-p port', 'set the port (default is 70)') { |val| params[:port] = Integer(val) }
34
- opts.on('-o addr', 'set the host (default is 0.0.0.0)') { |val| params[host] = val }
34
+ opts.on('-o addr', 'set the host (default is 0.0.0.0)') { |val| params[:host] = val }
35
35
  opts.on('-e env', 'set the environment (default is development)') { |val| params[:env] = val.to_sym }
36
36
 
37
37
  opts.on_tail("-h", "--help", "Show this message") do
data/examples/simple.rb CHANGED
@@ -5,6 +5,8 @@
5
5
  # Simple gopher example
6
6
  #
7
7
 
8
+ require "rubygems"
9
+ require "bundler/setup"
8
10
  require 'gopher2000'
9
11
 
10
12
  set :host, '0.0.0.0'
@@ -57,11 +59,11 @@ menu :index do
57
59
  br(2)
58
60
 
59
61
  # link somewhere
60
- link 'current time', '/time'
62
+ text_link 'current time', '/time'
61
63
  br
62
64
 
63
65
  # another link
64
- link 'about', '/about'
66
+ text_link 'about', '/about'
65
67
  br
66
68
 
67
69
  # ask for some input
@@ -54,6 +54,13 @@ module Gopher
54
54
  config[:port] ||= 70
55
55
  end
56
56
 
57
+ #
58
+ # return the application environment
59
+ #
60
+ def env
61
+ config[:env] ||= 'development'
62
+ end
63
+
57
64
  #
58
65
  # are we in debugging mode?
59
66
  #
@@ -162,12 +169,11 @@ module Gopher
162
169
  #
163
170
  def lookup(selector)
164
171
  unless routes.nil?
165
- routes.each do |pattern, keys, block|
166
-
172
+ routes.each do |pattern, keys, block|
167
173
  if match = pattern.match(selector)
168
174
  match = match.to_a
169
175
  url = match.shift
170
-
176
+
171
177
  params = to_params_hash(keys, match)
172
178
 
173
179
  #
@@ -201,6 +207,9 @@ module Gopher
201
207
  if ! @request.valid?
202
208
  response.body = handle_invalid_request
203
209
  response.code = :error
210
+ elsif @request.url?
211
+ response.body = handle_url(@request)
212
+ response.code = :success
204
213
  else
205
214
  begin
206
215
  debug_log("do lookup for #{@request.selector}")
@@ -347,6 +356,15 @@ module Gopher
347
356
  menus.include?(:error) ? :error : :'internal/error'
348
357
  end
349
358
 
359
+
360
+ #
361
+ # get the id of the template that will be used when rendering an html page
362
+ # @return name of error template
363
+ #
364
+ def url_template
365
+ menus.include?(:html) ? :html : :'internal/url'
366
+ end
367
+
350
368
  #
351
369
  # get the id of the template that will be used when rendering an
352
370
  # invalid request
@@ -431,20 +449,18 @@ module Gopher
431
449
 
432
450
  class << self
433
451
 
434
- #
435
- # Sanitizes a gopher selector
436
- #
437
- def sanitize_selector(raw)
438
- "/#{raw}".dup.
439
- strip. # Strip whitespace
440
- sub(/\/$/, ''). # Strip last rslash
441
- sub(/^\/*/, '/'). # Strip extra lslashes
442
- gsub(/\.+/, '.') # Don't want consecutive dots!
443
-
444
- #raw = "/#{raw}" if ! raw.match(/^\//)
445
- end
446
-
447
- #
452
+ #
453
+ # Sanitizes a gopher selector
454
+ #
455
+ def sanitize_selector(raw)
456
+ "/#{raw}".dup.
457
+ strip. # Strip whitespace
458
+ sub(/\/$/, ''). # Strip last rslash
459
+ sub(/^\/*/, '/'). # Strip extra lslashes
460
+ gsub(/\.+/, '.') # Don't want consecutive dots!
461
+ end
462
+
463
+ #
448
464
  # generate a method which we will use to run routes. this is
449
465
  # based on #generate_method as used by sinatra.
450
466
  # @see https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb
@@ -457,7 +473,7 @@ module Gopher
457
473
  method
458
474
  end
459
475
  end
460
-
476
+
461
477
  #
462
478
  # output a debugging message
463
479
  #
@@ -474,15 +490,37 @@ module Gopher
474
490
  #
475
491
  def register_defaults
476
492
  menu :'internal/not_found' do
477
- text "Sorry, #{@request.selector} was not found"
493
+ error "Sorry, #{@request.selector} was not found"
478
494
  end
479
495
 
480
496
  menu :'internal/error' do |details|
481
- text "Sorry, there was an error #{details}"
497
+ error "Sorry, there was an error #{details}"
482
498
  end
483
499
 
484
500
  menu :'internal/invalid_request' do
485
- text "invalid request"
501
+ error "invalid request"
502
+ end
503
+
504
+ menu :'internal/url' do
505
+ output = <<-EOHTML
506
+ <html>
507
+ <head>
508
+ <meta http-equiv="refresh" content="5;URL=#{@request.url}">
509
+ </head>
510
+ <body>
511
+ <p>
512
+ You are following a link from gopher to a website. If your browser supports it, you will be
513
+ automatically taken to the web site shortly. If you do not get
514
+ sent there, please click <a href="#{@request.url}">here</a>.
515
+ </p>
516
+ <p>
517
+ The URL linked is: <a href="#{@request.url}">#{@request.url}</a>.
518
+ </p>
519
+ <p>Have a nice day!</p>
520
+ </body>
521
+ </html>
522
+ EOHTML
523
+ output
486
524
  end
487
525
  end
488
526
 
@@ -490,6 +528,10 @@ module Gopher
490
528
  render not_found_template
491
529
  end
492
530
 
531
+ def handle_url(request)
532
+ render url_template, request
533
+ end
534
+
493
535
  def handle_error(e)
494
536
  render error_template, e
495
537
  end
@@ -27,15 +27,21 @@ module Gopher
27
27
  #
28
28
  def receive_data data
29
29
  (@buf ||= '') << data
30
-
30
+ first_line = true
31
+
32
+ ip_address = remote_ip
31
33
  while line = @buf.slice!(/(.*)\r?\n/)
32
- receive_line(line)
34
+ is_proxy = first_line && line.match?(/^PROXY TCP[4,6] /)
35
+ receive_line(line, ip_address) unless is_proxy
36
+ ip_address = line.split(/ /)[2] if is_proxy
37
+
38
+ first_line = false
33
39
  end
34
40
  end
35
41
 
36
42
  # Invoked with lines received over the network
37
- def receive_line(line)
38
- call! Request.new(line, remote_ip)
43
+ def receive_line(line, ip_address)
44
+ call! Request.new(line, ip_address)
39
45
  end
40
46
 
41
47
  #
@@ -35,7 +35,7 @@ module Gopher
35
35
  # @param [String] string text to add to the output
36
36
  #
37
37
  def <<(string)
38
- @result << string.to_s
38
+ @result << clean_line(string.to_s)
39
39
  end
40
40
 
41
41
  #
@@ -89,7 +89,9 @@ module Gopher
89
89
  # this is a hack - recombine lines, then re-split on newlines
90
90
  # doing this because word_wrap is returning an array of lines, but
91
91
  # those lines have newlines in them where we should wrap
92
- lines = word_wrap(text, width).join("\n").split("\n")
92
+ #
93
+ lines = word_wrap(text, width)
94
+ .join("\n").split("\n")
93
95
 
94
96
  lines.each do |line|
95
97
  text line.lstrip.rstrip
@@ -206,6 +208,19 @@ module Gopher
206
208
  end
207
209
  end
208
210
 
211
+ #
212
+ # handle lines of a single period not at the end of the
213
+ # transmission
214
+ #
215
+ # RFC 1436 states: Note: Lines beginning with
216
+ # periods must be prepended with an extra period to
217
+ # ensure that the transmission is not terminated
218
+ # early. The client should strip extra periods at
219
+ # the beginning of the line.
220
+ def clean_line(line)
221
+ line.match?(/^\./) ? ['.', line].join('') : line
222
+ end
223
+
209
224
  private
210
225
  def add_spacing
211
226
  br(@spacing)
@@ -7,12 +7,21 @@ module Gopher
7
7
  attr_accessor :selector, :input, :ip_address
8
8
 
9
9
  def initialize(raw, ip_addr=nil)
10
- @selector, @input = raw.chomp.split("\t")
10
+ @raw = raw
11
+ @selector, @input = @raw.chomp.split("\t")
12
+
13
+ @selector = Gopher::Application.sanitize_selector(@selector)
14
+ @ip_address = ip_addr
15
+ end
11
16
 
12
- @selector = Gopher::Application.sanitize_selector(@selector)
13
- @ip_address = ip_addr
17
+ def url?
18
+ @raw =~ /^URL\:/
14
19
  end
15
20
 
21
+ def url
22
+ @raw.chomp.split("\t").first.gsub(/^URL\:/, '')
23
+ end
24
+
16
25
  # confirm that this is actually a valid gopher request
17
26
  # @return [Boolean] true if the request is valid, false otherwise
18
27
  def valid?
@@ -13,7 +13,7 @@ module Gopher
13
13
  def initialize(a)
14
14
  @app = a
15
15
  end
16
-
16
+
17
17
  #
18
18
  # @return [String] name of the host specified in our config
19
19
  #
@@ -28,6 +28,13 @@ module Gopher
28
28
  @app.config[:port] ||= 70
29
29
  end
30
30
 
31
+ #
32
+ # @return [String] environment specified in config
33
+ #
34
+ def env
35
+ @app.config[:env] || 'development'
36
+ end
37
+
31
38
  #
32
39
  # main app loop. called via at_exit block defined in DSL
33
40
  #
@@ -77,8 +84,8 @@ module Gopher
77
84
  require 'optparse'
78
85
  OptionParser.new { |op|
79
86
  op.on('-p port', 'set the port (default is 70)') { |val| set :port, Integer(val) }
80
- op.on('-o addr', 'set the host (default is 0.0.0.0)') { |val| set :bind, val }
81
- op.on('-e env', 'set the environment (default is development)') { |val| set :environment, val.to_sym }
87
+ op.on('-o addr', 'set the host (default is 0.0.0.0)') { |val| set :host, val }
88
+ op.on('-e env', 'set the environment (default is development)') { |val| set :env, val.to_sym }
82
89
  }.parse!(ARGV.dup)
83
90
  end
84
91
  end
@@ -1,4 +1,4 @@
1
1
  module Gopher
2
2
  # current version of the app
3
- VERSION = "0.5.3"
3
+ VERSION = "0.7.0"
4
4
  end
@@ -51,6 +51,7 @@ describe Gopher::Application do
51
51
 
52
52
  @response = @server.dispatch(@request)
53
53
  expect(@response.code).to eq(:missing)
54
+ expect(@response.body).to contain_any_error
54
55
  end
55
56
 
56
57
  it "should respond with error if invalid request" do
@@ -59,6 +60,7 @@ describe Gopher::Application do
59
60
 
60
61
  @response = @server.dispatch(@request)
61
62
  expect(@response.code).to eq(:error)
63
+ expect(@response.body).to contain_any_error
62
64
  end
63
65
 
64
66
  it "should respond with error if there's an exception" do
@@ -67,6 +69,7 @@ describe Gopher::Application do
67
69
 
68
70
  @response = @server.dispatch(@request)
69
71
  expect(@response.code).to eq(:error)
72
+ expect(@response.body).to contain_any_error
70
73
  end
71
74
  end
72
75
 
@@ -127,7 +130,17 @@ describe Gopher::Application do
127
130
  end
128
131
  end
129
132
 
133
+ describe 'dispatch for URL requests' do
134
+ let(:target) { 'URL:http://github.com/muffinista/gopher2000' }
135
+ let(:request) { Gopher::Request.new(target) }
136
+ let(:response) { @server.dispatch(request) }
130
137
 
138
+ it "should return web page" do
139
+ expect(response.body).to include('<meta http-equiv="refresh" content="5;URL=http://github.com/muffinista/gopher2000">')
140
+ end
141
+ end
142
+
143
+
131
144
  describe "globs" do
132
145
  before(:each) do
133
146
  @server.route '/about/*' do
@@ -142,3 +155,11 @@ describe Gopher::Application do
142
155
  end
143
156
  end
144
157
  end
158
+
159
+ RSpec::Matchers.define :contain_any_error do
160
+ match do |actual|
161
+ actual.split(/\r?\n/).any? do |line|
162
+ line.match(/^3.*?\tnull\t\(FALSE\)\t0$/)
163
+ end
164
+ end
165
+ end
data/spec/dsl_spec.rb CHANGED
@@ -14,103 +14,112 @@ describe Gopher::DSL do
14
14
 
15
15
  @server = FakeServer.new(@app)
16
16
  @server.send :require, 'gopher2000/dsl'
17
- allow(@server).to receive(:application).and_return(@app)
18
- @app.reset!
19
17
  end
20
18
 
21
-
22
- describe "set" do
23
- it "should set a config var" do
24
- @server.set :foo, 'bar'
25
- expect(@app.config[:foo]).to eq('bar')
19
+ describe "application" do
20
+ it "should return a Gopher::Application" do
21
+ expect(@server.application).to be_a(Gopher::Application)
26
22
  end
27
23
  end
28
24
 
29
- describe "route" do
30
- it "should pass a lookup and block to the app" do
31
- expect(@app).to receive(:route).with('/foo')
32
- @server.route '/foo' do
33
- "hi"
25
+ context "with application" do
26
+ before do
27
+ allow(@server).to receive(:application).and_return(@app)
28
+ @app.reset!
29
+ end
30
+
31
+ describe "set" do
32
+ it "should set a config var" do
33
+ @server.set :foo, 'bar'
34
+ expect(@app.config[:foo]).to eq('bar')
34
35
  end
35
36
  end
36
- end
37
37
 
38
- describe "default_route" do
39
- it "should pass a default block to the app" do
40
- expect(@app).to receive(:default_route)
41
- @server.default_route do
42
- "hi"
38
+ describe "route" do
39
+ it "should pass a lookup and block to the app" do
40
+ expect(@app).to receive(:route).with('/foo')
41
+ @server.route '/foo' do
42
+ "hi"
43
+ end
43
44
  end
44
45
  end
45
- end
46
46
 
47
- describe "mount" do
48
- it "should pass a route, path, and some opts to the app" do
49
- expect(@app).to receive(:mount).with('/foo', {:path => "/bar"})
50
- @server.mount "/foo" => "/bar"
47
+ describe "default_route" do
48
+ it "should pass a default block to the app" do
49
+ expect(@app).to receive(:default_route)
50
+ @server.default_route do
51
+ "hi"
52
+ end
53
+ end
51
54
  end
52
55
 
53
- it "should pass a route, path, filter, and some opts to the app" do
54
- expect(@app).to receive(:mount).with('/foo', {:path => "/bar", :filter => "*.jpg"})
55
- @server.mount "/foo" => "/bar", :filter => "*.jpg"
56
+ describe "mount" do
57
+ it "should pass a route, path, and some opts to the app" do
58
+ expect(@app).to receive(:mount).with('/foo', {:path => "/bar"})
59
+ @server.mount "/foo" => "/bar"
60
+ end
61
+
62
+ it "should pass a route, path, filter, and some opts to the app" do
63
+ expect(@app).to receive(:mount).with('/foo', {:path => "/bar", :filter => "*.jpg"})
64
+ @server.mount "/foo" => "/bar", :filter => "*.jpg"
65
+ end
56
66
  end
57
- end
58
67
 
59
- describe "menu" do
60
- it "should pass a menu key and block to the app" do
61
- expect(@app).to receive(:menu).with('/foo')
62
- @server.menu '/foo' do
63
- "hi"
68
+ describe "menu" do
69
+ it "should pass a menu key and block to the app" do
70
+ expect(@app).to receive(:menu).with('/foo')
71
+ @server.menu '/foo' do
72
+ "hi"
73
+ end
64
74
  end
65
75
  end
66
- end
67
76
 
68
- describe "text" do
69
- it "should pass a text_template key and block to the app" do
70
- expect(@app).to receive(:text).with('/foo')
71
- @server.text '/foo' do
72
- "hi"
77
+ describe "text" do
78
+ it "should pass a text_template key and block to the app" do
79
+ expect(@app).to receive(:text).with('/foo')
80
+ @server.text '/foo' do
81
+ "hi"
82
+ end
73
83
  end
74
84
  end
75
- end
76
85
 
77
- describe "helpers" do
78
- it "should pass a block to the app" do
79
- expect(@app).to receive(:helpers)
80
- @server.helpers do
81
- "hi"
86
+ describe "helpers" do
87
+ it "should pass a block to the app" do
88
+ expect(@app).to receive(:helpers)
89
+ @server.helpers do
90
+ "hi"
91
+ end
82
92
  end
83
93
  end
84
- end
85
94
 
86
- describe "watch" do
87
- it "should pass a script app for watching" do
88
- expect(@app.scripts).to receive(:<<).with("foo")
89
- @server.watch("foo")
95
+ describe "watch" do
96
+ it "should pass a script app for watching" do
97
+ expect(@app.scripts).to receive(:<<).with("foo")
98
+ @server.watch("foo")
99
+ end
90
100
  end
91
- end
92
101
 
93
- describe "run" do
94
- it "should set any incoming opts" do
95
- expect(@server).to receive(:set).with(:x, 1)
96
- expect(@server).to receive(:set).with(:y, 2)
97
- allow(@server).to receive(:load)
102
+ describe "run" do
103
+ it "should set any incoming opts" do
104
+ expect(@server).to receive(:set).with(:x, 1)
105
+ expect(@server).to receive(:set).with(:y, 2)
106
+ allow(@server).to receive(:load)
98
107
 
99
- @server.run("foo", {:x => 1, :y => 2})
100
- end
108
+ @server.run("foo", {:x => 1, :y => 2})
109
+ end
101
110
 
102
- it "should turn on script watching if in debug mode" do
103
- @app.config[:debug] = true
104
- expect(@server).to receive(:watch).with("foo.rb")
105
- expect(@server).to receive(:load).with("foo.rb")
111
+ it "should turn on script watching if in debug mode" do
112
+ @app.config[:debug] = true
113
+ expect(@server).to receive(:watch).with("foo.rb")
114
+ expect(@server).to receive(:load).with("foo.rb")
106
115
 
107
- @server.run("foo.rb")
108
- end
116
+ @server.run("foo.rb")
117
+ end
109
118
 
110
- it "should load the script" do
111
- expect(@server).to receive(:load).with("foo.rb")
112
- @server.run("foo.rb")
119
+ it "should load the script" do
120
+ expect(@server).to receive(:load).with("foo.rb")
121
+ @server.run("foo.rb")
122
+ end
113
123
  end
114
124
  end
115
-
116
125
  end
@@ -10,7 +10,7 @@ describe Gopher::Rendering::Base do
10
10
  @ctx.text("line 2")
11
11
  expect(@ctx.result).to eq("line 1\r\nline 2\r\n")
12
12
  end
13
-
13
+
14
14
  it "should add breaks correctly" do
15
15
  @ctx.spacing 2
16
16
  @ctx.text("line 1")
@@ -18,6 +18,13 @@ describe Gopher::Rendering::Base do
18
18
  expect(@ctx.result).to eq("line 1\r\n\r\nline 2\r\n\r\n")
19
19
  end
20
20
 
21
+ it "should add extra period to lines beginning with period" do
22
+ @ctx.text("line 1")
23
+ @ctx.text(".")
24
+ @ctx.text("line 2")
25
+ expect(@ctx.result).to eq("line 1\r\n..\r\nline 2\r\n")
26
+ end
27
+
21
28
  it "br outputs a bunch of newlines" do
22
29
  expect(@ctx.br(2)).to eq("\r\n\r\n")
23
30
  end
@@ -60,7 +67,12 @@ describe Gopher::Rendering::Base do
60
67
  describe "block" do
61
68
  it "wraps text" do
62
69
  expect(@ctx).to receive(:text).twice.with "a"
63
- @ctx.block("a a",1)
70
+ @ctx.block("a a", 1)
71
+ end
72
+
73
+ it "handles stray period" do
74
+ @ctx.block("a a .", 1)
75
+ expect(@ctx.result).to eq("a\r\na\r\n..\r\n")
64
76
  end
65
77
  end
66
78
  end
@@ -54,9 +54,25 @@ describe Gopher::Rendering::Menu do
54
54
  end
55
55
 
56
56
  describe "link" do
57
- it "should get type with determine_type" do
58
- expect(@ctx).to receive(:determine_type).with("foo.txt").and_return("A")
59
- expect(@ctx.link("FILE", "foo.txt")).to eq("AFILE\tfoo.txt\thost\t1234\r\n")
57
+ context "with filepath" do
58
+ it "should get type with determine_type" do
59
+ expect(@ctx).to receive(:determine_type).with("foo.txt").and_return("A")
60
+ expect(@ctx.link("FILE", "thing", "external-server.com", 70, "foo.txt")).to eq("AFILE\tthing\texternal-server.com\t70\r\n")
61
+ end
62
+ end
63
+
64
+ context "without filepath" do
65
+ it "should get type with determine_type" do
66
+ expect(@ctx).to receive(:determine_type).with("foo.txt").and_return("A")
67
+ expect(@ctx.link("FILE", "foo.txt")).to eq("AFILE\tfoo.txt\thost\t1234\r\n")
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ describe "text_link" do
74
+ it "should work" do
75
+ expect(@ctx.text_link("text link", "/files/foo.txt")).to eq("0text link\t/files/foo.txt\thost\t1234\r\n")
60
76
  end
61
77
  end
62
78
 
data/spec/request_spec.rb CHANGED
@@ -32,4 +32,10 @@ describe Gopher::Request do
32
32
  request = Gopher::Request.new("x" * 255, "bar")
33
33
  expect(request.valid?).to eq(false)
34
34
  end
35
+
36
+ it 'detects urls' do
37
+ request = Gopher::Request.new("URL:http://github.com/muffinista/gopher2000")
38
+ expect(request).to be_url
39
+ expect(request.url).to eql('http://github.com/muffinista/gopher2000')
40
+ end
35
41
  end