camping 1.2 → 1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ = 1.3
2
+ === 28th January, 2006
3
+
4
+ * bin/camping: an application launcher.
5
+ * <tt>Camping.run(request, response)</tt> now changed to <tt>controller = Camping.run(request, env)</tt>
6
+ * This means outputting the response is the wrapper/server's job. See bin/camping, you can do a controller.to_s at the least.
7
+ * <tt>Controllers::Base.env</tt> is the new thread-safe home for <tt>ENV</tt>.
8
+ * The input hash now works more like Rails params. You can call keys
9
+ like methods or with symbols or strings.
10
+ * Queries are now parsed more like PHP/Rails, in that you can denote
11
+ structure with brackets: post[user]=_why;post[id]=2
12
+ * Auto-prefix table names, to help prevent name clash.
13
+ * Helpers.errors_for simple validation.
14
+ * Lots of empty :href and :action attributes, a bug.
15
+ * New single-page flipbook RDoc template.
16
+
17
+ = 1.2
18
+ === 23rd January, 2006
19
+
20
+ * Camping.goes allows fresh modules build from all Camping parts.
21
+ * File uploads now supported (multipart/form-data).
22
+ * Helpers.R can rebuild routes.
23
+ * Helpers./ for tracing paths from the root.
24
+
25
+ = 1.1
26
+ === 19th January, 2006
27
+
28
+ * Allowed request and response streams to be passed in, to allow WEBrick and FastCGI support.
29
+
30
+ = 1.0
31
+ === 17th January, 2006
32
+
33
+ * Initial checkin, see announcement at http://redhanded.hobix.com/bits/campingAMicroframework.html.
data/README CHANGED
@@ -8,17 +8,21 @@ The idea here is to store a complete fledgling web application in a single file
8
8
  like many small CGIs. But to organize it as a Model-View-Controller application
9
9
  like Rails does. You can then easily move it to Rails once you've got it going.
10
10
 
11
- A skeleton might be:
11
+ == A Camping Skeleton
12
+
13
+ A skeletal Camping blog could look like this:
12
14
 
13
15
  require 'camping'
14
16
 
15
- module Camping::Models
17
+ Camping.goes :Blog
18
+
19
+ module Blog::Models
16
20
  class Post < Base; belongs_to :user; end
17
21
  class Comment < Base; belongs_to :user; end
18
22
  class User < Base; end
19
23
  end
20
24
 
21
- module Camping::Controllers
25
+ module Blog::Controllers
22
26
  class Index < R '/'
23
27
  def get
24
28
  @posts = Post.find :all
@@ -27,7 +31,7 @@ A skeleton might be:
27
31
  end
28
32
  end
29
33
 
30
- module Camping::Views
34
+ module Blog::Views
31
35
  def layout
32
36
  html do
33
37
  body do
@@ -44,9 +48,10 @@ A skeleton might be:
44
48
  end
45
49
 
46
50
  if __FILE__ == $0
47
- Camping::Models::Base.establish_connection :adapter => 'sqlite3', :database => 'blog3.db'
48
- Camping::Models::Base.logger = Logger.new('camping.log')
49
- Camping.run
51
+ Blog::Models::Base.establish_connection :adapter => 'sqlite3',
52
+ :database => 'blog3.db'
53
+ Blog::Models::Base.logger = Logger.new('camping.log')
54
+ puts Blog.run
50
55
  end
51
56
 
52
57
  Some things you might have noticed:
@@ -55,6 +60,16 @@ Some things you might have noticed:
55
60
  * Camping::Controllers can be assigned URLs in the class definition. Neat?
56
61
  * Camping::Views describes HTML using pure Ruby. Markup as Ruby, which we
57
62
  call Markaby.
63
+ * You use Camping::goes to make a copy of the Camping framework under your
64
+ own module name (in this case: <tt>Blog</tt>.)
65
+
66
+ <b>NOTE:</b> Camping auto-prefixes table names. If your class is named
67
+ <tt>Blog::Models::Post</tt>, your table will be called <b>blog_posts</b>.
68
+ Since many Camping apps can be attached to a database at once, this helps
69
+ prevent name clash.
70
+
71
+ (If you want to see the full blog example, check out <tt>examples/blog/blog.rb</tt>
72
+ for the complete code.)
58
73
 
59
74
  If you want to write larger applications with Camping, you are encouraged to
60
75
  split the application into distinct parts which can be mounted at URLs on your
@@ -67,3 +82,45 @@ Interested yet? Okay, okay, one step at a time.
67
82
  == Installation
68
83
 
69
84
  * <tt>gem install camping</tt>
85
+
86
+ Or for the bleeding edge:
87
+
88
+ * <tt>gem install camping --source code.whytheluckystiff.net</tt>
89
+
90
+ You are encourage to install Camping and SQLite3, since it is a small database
91
+ which fits perfectly with our compact bylaws, works well with the examples.
92
+
93
+ * See http://code.whytheluckystiff.net/camping/wiki/BeAlertWhenOnSqlite3 for instructions.
94
+
95
+ == Running Camping Apps
96
+
97
+ The blog example above and most Camping applications look a lot like CGI scripts.
98
+ If you run them from the commandline, you'll probably just see a pile of HTML.
99
+
100
+ Camping comes with an tool for launching apps from the commandline:
101
+
102
+ * Run: <tt>camping blog.rb</tt>
103
+ * Visit http://localhost:3301/ to use the app.
104
+
105
+ == Debugging Camping Apps
106
+
107
+ If your application isn't working with the <tt>camping</tt> tool, keep in mind
108
+ that the tool expects the following conventions to be used:
109
+
110
+ 1. You must have SQLite3 and SQLite3-ruby installed. (Once again, please see
111
+ http://code.whytheluckystiff.net/camping/wiki/BeAlertWhenOnSqlite3 for instructions.)
112
+ 2. If your script is called <tt>test.rb</tt>, Camping expects your application to
113
+ be stored in a module called <tt>Test</tt>. Case is not imporant, though. The
114
+ module can be called <tt>TeSt</tt> or any other permuation.
115
+ 3. Your script's postamble (anything enclosed in <tt>if __FILE__ == $0</tt>) will be
116
+ ignored by the tool, since the tool will create an SQLite3 database at
117
+ <tt>~/.camping.db</tt>. Or, on Windows, <tt>$USER/Application Data/Camping.db</tt>.
118
+ 4. If your application's module has a <tt>create</tt> method, it will be executed before
119
+ the web server starts up.
120
+
121
+ == The Rules of Thumb
122
+
123
+ Once you've started writing your own Camping app, I'd highly recommend becoming familiar
124
+ with the Camping Rules of Thumb which are listed on the wiki:
125
+ http://code.whytheluckystiff.net/camping/wiki/CampingRulesOfThumb
126
+
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # this line prevents other db adapters from being loaded (oci8 was
4
+ # causing some pain.)
5
+ RAILS_CONNECTION_ADAPTERS = %w[sqlite]
6
+
7
+ require 'stringio'
8
+ require 'webrick/httpserver'
9
+ require 'camping'
10
+
11
+ (puts <<USAGE; exit) if ARGV.length == 0
12
+ #{File.basename($0)}, the microframework ON-button for ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]
13
+ Usage: #{File.basename($0)} your.camping.rb [your.camping.db]
14
+ USAGE
15
+
16
+ script, db = ARGV[0..-1]
17
+
18
+ unless db
19
+ homes = []
20
+ homes << File.join( ENV['HOME'], '.camping.db' ) if ENV['HOME']
21
+ homes << File.join( ENV['APPDATA'], 'Camping.db' ) if ENV['APPDATA']
22
+ homes.each do |db|
23
+ break if File.exists?( db )
24
+ end
25
+ end
26
+
27
+ Camping::Models::Base.establish_connection :adapter => 'sqlite3', :database => db
28
+
29
+ # Load the script, locate the module
30
+ def camp_load(script, klass = nil)
31
+ Object.instance_eval { remove_const klass.name } if klass
32
+ mtime = File.mtime(script)
33
+ load script
34
+ klass = Object.const_get(Object.constants.grep(/^#{File.basename(script)[/^(\w+)/,1]}$/i)[0]) rescue nil
35
+ klass ||= Camping
36
+ klass.create if klass.respond_to? :create
37
+ [klass, mtime]
38
+ end
39
+
40
+ # Mount the root
41
+ klass, mtime = camp_load( script )
42
+ s = WEBrick::HTTPServer.new(:BindAddress => '0.0.0.0', :Port => 3301)
43
+ s.mount_proc("/") do |req, resp|
44
+ newtime = File.mtime( script )
45
+ if newtime > mtime
46
+ klass, mtime = camp_load( script, klass )
47
+ end
48
+ controller = klass.run((req.body and StringIO.new(req.body)), req.meta_vars)
49
+ resp.status = controller.status
50
+ controller.headers.each do |k, v|
51
+ [*v].each do |vi|
52
+ resp[k] = vi
53
+ end
54
+ end
55
+ resp.body = controller.body
56
+ nil
57
+ end
58
+
59
+ # Server up
60
+ trap(:INT) do
61
+ s.shutdown
62
+ end
63
+ s.start
@@ -18,10 +18,24 @@ module Blog::Models
18
18
  end
19
19
 
20
20
  Blog::Models.schema do
21
- # create_table :posts, :force => true do |t|
22
- # t.column :title, :string, :limit => 255
23
- # t.column :body, :text
24
- # end
21
+ create_table :blog_posts, :force => true do |t|
22
+ t.column :id, :integer, :null => false
23
+ t.column :user_id, :integer, :null => false
24
+ t.column :title, :string, :limit => 255
25
+ t.column :body, :text
26
+ end
27
+ create_table :blog_users, :force => true do |t|
28
+ t.column :id, :integer, :null => false
29
+ t.column :username, :string
30
+ t.column :password, :string
31
+ end
32
+ create_table :blog_comments, :force => true do |t|
33
+ t.column :id, :integer, :null => false
34
+ t.column :post_id, :integer, :null => false
35
+ t.column :username, :string
36
+ t.column :body, :text
37
+ end
38
+ execute "INSERT INTO blog_users (username, password) VALUES ('admin', 'camping')"
25
39
  end
26
40
 
27
41
  module Blog::Controllers
@@ -34,14 +48,15 @@ module Blog::Controllers
34
48
 
35
49
  class Add
36
50
  def get
37
- if cookies.user_id
51
+ unless cookies.user_id.blank?
38
52
  @session = User.find cookies.user_id
39
53
  @post = Post.new
40
54
  end
41
55
  render :add
42
56
  end
43
57
  def post
44
- post = Post.create :title => input.post_title, :body => input.post_body
58
+ post = Post.create :title => input.post_title, :body => input.post_body,
59
+ :user_id => @cookies.user_id
45
60
  redirect View, post
46
61
  end
47
62
  end
@@ -66,7 +81,7 @@ module Blog::Controllers
66
81
 
67
82
  class Edit < R '/edit/(\d+)', '/edit'
68
83
  def get post_id
69
- if cookies.user_id
84
+ unless cookies.user_id.blank?
70
85
  @session = User.find cookies.user_id
71
86
  end
72
87
  @post = Post.find post_id
@@ -109,10 +124,21 @@ module Blog::Controllers
109
124
  end
110
125
  end
111
126
 
112
- class Style < R '/styles.css', '/view/styles.css'
127
+ class Style < R '/styles.css'
113
128
  def get
114
129
  @headers["Content-Type"] = "text/css; charset=utf-8"
115
- @body = File.read('styles.css')
130
+ @body = %{
131
+ body {
132
+ font-family: Utopia, Georga, serif;
133
+ }
134
+ h1.header {
135
+ background-color: #fef;
136
+ margin: 0; padding: 10px;
137
+ }
138
+ div.content {
139
+ padding: 10px;
140
+ }
141
+ }
116
142
  end
117
143
  end
118
144
  end
@@ -232,11 +258,15 @@ module Blog::Views
232
258
  end
233
259
  end
234
260
 
235
- db_exists = File.exists?('blog3.db')
236
- Blog::Models::Base.establish_connection :adapter => 'sqlite3', :database => 'blog3.db'
237
- Blog::Models::Base.logger = Logger.new('camping.log')
238
- ActiveRecord::Schema.define(&Blog::Models.schema) unless db_exists
261
+ def Blog.create
262
+ unless Blog::Models::Post.table_exists?
263
+ ActiveRecord::Schema.define(&Blog::Models.schema)
264
+ end
265
+ end
239
266
 
240
267
  if __FILE__ == $0
241
- Blog.run
268
+ Blog::Models::Base.establish_connection :adapter => 'sqlite3', :database => 'blog.db'
269
+ Blog::Models::Base.logger = Logger.new('camping.log')
270
+ Blog.create
271
+ puts Blog.run
242
272
  end
@@ -0,0 +1,631 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + "/../../lib"
4
+ %w(rubygems redcloth camping acts_as_versioned).each { |lib| require lib }
5
+
6
+ Camping.goes :CampSh
7
+
8
+ module CampSh
9
+ NAME = 'CampCampSh'
10
+ DESCRIPTION = %{
11
+ Script your own URL commands, then run these commands through
12
+ the proxy with "cmd/CommandName". All scripts are versioned
13
+ and attributed.
14
+ }
15
+ VERSION = '0.1'
16
+ ANON = 'AnonymousCoward'
17
+
18
+ begin
19
+ require 'syntax/convertors/html'
20
+ SYNTAX = ::Syntax::Convertors::HTML.for_syntax "ruby"
21
+ rescue LoadError
22
+ end
23
+
24
+ def self.create
25
+ unless CampSh::Models::Command.table_exists?
26
+ ActiveRecord::Schema.define do
27
+ create_table :campsh_commands, :force => true do |t|
28
+ t.column :id, :integer, :null => false
29
+ t.column :author, :string, :limit => 40
30
+ t.column :name, :string, :limit => 255
31
+ t.column :created_at, :datetime
32
+ t.column :doc, :text
33
+ t.column :code, :text
34
+ end
35
+ CampSh::Models::Command.create_versioned_table
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ module CampSh::Models
42
+ class Command < Base
43
+ validates_uniqueness_of :name
44
+ validates_presence_of :author
45
+ acts_as_versioned
46
+ end
47
+ end
48
+
49
+ module CampSh::Controllers
50
+ class Index < R '/'
51
+ def get
52
+ redirect List
53
+ end
54
+ end
55
+
56
+ class List
57
+ def get
58
+ @cmds = Command.find :all, :order => 'name'
59
+ @title = "Command List"
60
+ render :list
61
+ end
62
+ end
63
+
64
+ class Run < R '/run/(\w+)', '/run/(\w+)/(.+)', '/run/(\w+) (.+)'
65
+ def get(cmd, args=nil)
66
+ @cmd = Command.find_by_name(cmd)
67
+ unless @cmd
68
+ redirect New, name
69
+ return
70
+ end
71
+
72
+ args = args.to_s.strip.split(/[\/\s]+/)
73
+ eval(@cmd.code)
74
+ end
75
+ end
76
+
77
+ class Authors
78
+ def get
79
+ @authors =
80
+ Command.find(:all, :order => "author, name").inject({}) do |hsh, cmd|
81
+ (hsh[cmd.author] ||= []) << cmd
82
+ hsh
83
+ end
84
+ @title = "Author"
85
+ render :authors
86
+ end
87
+ end
88
+
89
+ class Recent
90
+ def get
91
+ @days =
92
+ Command.find(:all, :order => "created_at DESC").inject({}) do |hsh, cmd|
93
+ (hsh[cmd.created_at.strftime("%B %d, %Y")] ||= []) << cmd
94
+ hsh
95
+ end
96
+ @title = "Recently Revised"
97
+ render :recent
98
+ end
99
+ end
100
+
101
+ class Show < R '/show/(\w+)', '/show/(\w+)/(\d+)', '/cancel_edit/(\w+)'
102
+ def get(name, version = nil)
103
+ unless @cmd = Command.find_by_name(name)
104
+ redirect(Edit, page_name)
105
+ return
106
+ end
107
+ @version = (version.nil? or version == @cmd.version.to_s) ? @cmd : @cmd.versions.find_by_version(version)
108
+ render :show
109
+ end
110
+ end
111
+
112
+ class New < R '/new', '/new/(\w+)'
113
+ def get(name)
114
+ @cmd = Command.new(:name => name)
115
+ @title = "Creating #{name}"
116
+ render :edit
117
+ end
118
+ def post
119
+ @cmd = Command.new(:name => input.cmd)
120
+ @title = "Creating #{input.cmd}"
121
+ render :edit
122
+ end
123
+ end
124
+
125
+ class Edit < R '/edit/(\w+)', '/edit/(\w+)/(\d+)'
126
+ def get(name, version = nil)
127
+ @cmd = Command.find_by_name(name)
128
+ @cmd = @cmd.versions.find_by_version(version) unless version.nil? or version == @cmd.version.to_s
129
+ @title = "Editing #{name}"
130
+ @author = cookies.cmd_author or CampSh::ANON
131
+ render :edit
132
+ end
133
+ def post(name)
134
+ if input.author != CampSh::ANON
135
+ cookies.cmd_author = input.author
136
+ end
137
+ Command.find_or_create_by_name(name).update_attributes(input.command)
138
+ redirect Show, name
139
+ end
140
+ end
141
+
142
+ class HowTo
143
+ def get
144
+ @title = "How To"
145
+ render :howto
146
+ end
147
+ end
148
+
149
+ class Style < R '/styles.css'
150
+ def get
151
+ @headers['Content-Type'] = 'text/css'
152
+ %Q[
153
+ h1#pageName, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover,
154
+ #TextileHelp h3 { color: #003B76; }
155
+
156
+ #container { width: 720px; }
157
+
158
+ #container {
159
+ float: none;
160
+ margin: 0 auto;
161
+ text-align: center;
162
+ padding: 2px;
163
+ border: solid 2px #999;
164
+ }
165
+
166
+ #content {
167
+ margin: 0;
168
+ padding: 9px;
169
+ text-align: left;
170
+ border-top: none;
171
+ border: solid 1px #999;
172
+ background-color: #eee;
173
+ }
174
+
175
+ body, p, ol, ul, td {
176
+ font-family: verdana, arial, helvetica, sans-serif;
177
+ font-size: 15px;
178
+ line-height: 110%;
179
+ }
180
+
181
+ a { color: #000; }
182
+
183
+ .newWikiWord { background-color: #eee; }
184
+ .newWikiWord a:hover { background-color: white; }
185
+
186
+ a:visited { color: #666; }
187
+ a:hover { color: #fff; background-color:#000; }
188
+
189
+ /* a.edit:link, a.edit:visited { color: #DA0006; } */
190
+
191
+
192
+ h1, h2, h3 { color: #333; font-family: georgia, verdana; text-align: center; line-height: 70%; margin-bottom: 0; }
193
+ h1 { font-size: 28px }
194
+ h2 { font-size: 22px }
195
+ h3 { font-size: 19px }
196
+
197
+ h1#pageName {
198
+ margin: 5px 0px 0px 0px;
199
+ padding: 0px 0px 0px 0px;
200
+ line-height: 28px;
201
+ }
202
+
203
+ h1#pageName small {
204
+ color: grey;
205
+ line-height: 10px;
206
+ font-size: 10px;
207
+ padding: 0px;
208
+ }
209
+
210
+ a.nav, a.nav:link, a.nav:visited { color: #000; }
211
+ a.nav:hover { color: #fff; background-color:#000; }
212
+
213
+ li { margin-bottom: 7px }
214
+
215
+ .navigation {
216
+ margin-top: 5px;
217
+ font-size : 12px;
218
+ color: #999;
219
+ text-align: center;
220
+ }
221
+
222
+ .navigation a:hover { color: #fff; background-color:#000; }
223
+
224
+ .navigation a {
225
+ font-size: 11px;
226
+ color: black;
227
+ font-weight: bold;
228
+ }
229
+
230
+ .navigation small a {
231
+ font-weight: normal;
232
+ font-size: 11px;
233
+ }
234
+
235
+ .navOn{
236
+ font-size: 11px;
237
+ color: grey;
238
+ font-weight: bold;
239
+ text-decoration: none;
240
+ }
241
+
242
+ .help {
243
+ font-family: verdana, arial, helvetica, sans-serif;
244
+ font-size: 11px;
245
+ }
246
+
247
+ .inputBox {
248
+ font-family: verdana, arial, helvetica, sans-serif;
249
+ font-size: 11px;
250
+ background-color: #eee;
251
+ padding: 5px;
252
+ margin-bottom: 20px;
253
+ }
254
+
255
+ blockquote {
256
+ display: block;
257
+ margin: 0px 0px 20px 0px;
258
+ padding: 0px 30px;
259
+ font-size:11px;
260
+ line-height:17px;
261
+ font-style: italic;
262
+ }
263
+
264
+ pre {
265
+ background-color: #eee;
266
+ padding: 10px;
267
+ font-size: 11px;
268
+ }
269
+
270
+ ol.setup {
271
+ font-size: 19px;
272
+ font-family: georgia, verdana;
273
+ padding-left: 25px;
274
+ }
275
+
276
+ ol.setup li {
277
+ margin-bottom: 20px
278
+ }
279
+
280
+ .byline {
281
+ font-size: 10px;
282
+ font-style: italic;
283
+ margin-bottom: 10px;
284
+ color: #999;
285
+ }
286
+
287
+ .references {
288
+ font-size: 10px;
289
+ }
290
+
291
+ .diffdel {
292
+ background: pink;
293
+ }
294
+
295
+ .diffins {
296
+ background: lightgreen;
297
+ }
298
+
299
+ #allCommands ul {
300
+ list-style: none;
301
+ }
302
+
303
+ #allCommands li {
304
+ padding: 1px 4px;
305
+ }
306
+
307
+ #allCommands li:hover {
308
+ background-color: white;
309
+ }
310
+
311
+ #allCommands a,
312
+ #allCommands a:hover,
313
+ #allCommands a:link,
314
+ #allCommands a:visited,
315
+ #allCommands h2 {
316
+ color: #369;
317
+ background-color: transparent;
318
+ font-weight: normal;
319
+ font-size: 24px;
320
+ text-align: left;
321
+ }
322
+
323
+ #TextileHelp table {
324
+ margin-bottom: 0;
325
+ }
326
+
327
+ #TextileHelp table+h3 {
328
+ margin-top: 11px;
329
+ }
330
+
331
+ #TextileHelp table td {
332
+ font-size: 11px;
333
+ padding: 3px;
334
+ vertical-align: top;
335
+ border-top: 1px dotted #ccc;
336
+ }
337
+
338
+ #TextileHelp table td.arrow {
339
+ padding-right: 5px;
340
+ padding-left: 10px;
341
+ color: #999;
342
+ }
343
+
344
+ #TextileHelp table td.label {
345
+ font-weight: bold;
346
+ white-space: nowrap;
347
+ font-size: 10px;
348
+ padding-right: 15px;
349
+ color: #000;
350
+ }
351
+
352
+ #TextileHelp h3 {
353
+ font-size: 11px;
354
+ font-weight: bold;
355
+ font-weight: normal;
356
+ margin: 0 0 5px 0;
357
+ padding: 5px 0 0 0;
358
+ }
359
+
360
+ #TextileHelp p {
361
+ font-size: 10px;
362
+ }
363
+
364
+ .rightHandSide {
365
+ float: right;
366
+ width: 147px;
367
+ margin-left: 10px;
368
+ padding-left: 20px;
369
+ border-left: 1px dotted #ccc;
370
+ }
371
+
372
+ .rightHandSide p {
373
+ font-size: 10px;
374
+ }
375
+
376
+ .newsList {
377
+ margin-top: 20px;
378
+ }
379
+
380
+ .newsList p {
381
+ margin-bottom:30px
382
+ }
383
+
384
+ .leftHandSide
385
+ {
386
+ float: right;
387
+ width: 147px;
388
+ margin-left: 10px;
389
+ padding-left: 20px;
390
+ border-left: 1px dotted #ccc;
391
+ }
392
+
393
+ .leftHandSide p
394
+ {
395
+ font-size: 10px;
396
+ margin: 0;
397
+ padding: 0;
398
+ }
399
+
400
+ .leftHandSide h2
401
+ {
402
+ font-size: 12px;
403
+ margin-bottom: 0;
404
+ padding-bottom: 0;
405
+ }
406
+
407
+ .property
408
+ {
409
+ color: grey;
410
+ font-size: 9px;
411
+ text-align: right;
412
+ }
413
+
414
+ body
415
+ {
416
+ background-color: #ccc;
417
+ padding: 0;
418
+ margin: 20px;
419
+ color: #333;
420
+ line-height: 1.5;
421
+ font-size: 85%;
422
+ /* hacky hack */
423
+ voice-family: "\"}\"";
424
+ voice-family: inherit;
425
+ font-size: 80%;
426
+ }
427
+
428
+ /* be nice to opera */
429
+ html>body { font-size: 80%; }
430
+
431
+ /* syntax highlighting */
432
+ .keyword {
433
+ font-weight: bold;
434
+ }
435
+ .comment {
436
+ color: #555;
437
+ }
438
+ .string, .number {
439
+ color: #396;
440
+ }
441
+ .regex {
442
+ color: #435;
443
+ }
444
+ .ident {
445
+ color: #369;
446
+ }
447
+ .symbol {
448
+ color: #000;
449
+ }
450
+ .constant, .class {
451
+ color: #630;
452
+ font-weight: bold;
453
+ }
454
+ ]
455
+ end
456
+ end
457
+
458
+ end
459
+
460
+ module CampSh::Views
461
+ def red( str )
462
+ require 'redcloth'
463
+ self << RedCloth.new( str ).to_html
464
+ end
465
+
466
+ def layout
467
+ html do
468
+ head do
469
+ title "CampShell -- #{ @title }"
470
+ style "@import '#{ self / R(Style) }';", :type => 'text/css'
471
+ end
472
+ body do
473
+ div.container! do
474
+ div.content! do
475
+ h2 "CampShell"
476
+ h1 @title
477
+ _navigation
478
+ self << yield
479
+ end
480
+ end
481
+ end
482
+ end
483
+ end
484
+
485
+ def _navigation
486
+ form :id => "navigationForm", :class => "navigation", :style => "font-size: 10px" do
487
+ a "Command List", :href => R(List), :title => "Alphabetical list of commands", :accesskey => "A"
488
+ text " | "
489
+ a "Recently Revised", :href => R(Recent), :title => "Pages sorted by when they were last changed",
490
+ :accesskey => "U"
491
+ text " | "
492
+ a "Authors", :href => R(Authors), :title => "Who wrote what"
493
+ text " | "
494
+ a "How To", :href => R(HowTo), :title => "How to use CampShell", :accesskey => "H"
495
+ end
496
+ end
497
+
498
+ def list
499
+ div.allCommands! :style => "width: 500px; margin: 0 100px" do
500
+ form.editForm! :action => R(New), :method => "post" do
501
+ ul do
502
+ if @cmds.each do |cmd|
503
+ li do
504
+ h2 { a cmd.name, :href => R(Show, cmd.name) }
505
+ red cmd.doc
506
+ end
507
+ end.empty?
508
+ h2 "No commands created yet."
509
+ end
510
+
511
+ li do
512
+ text "Create command: "
513
+ input :type => "text", :name => "cmd", :id=> "newCmd", :value => "shortcut",
514
+ :onClick => "this.value == 'shortcut' ? this.value = '' : true"
515
+ end
516
+ end
517
+ end
518
+ end
519
+ end
520
+
521
+ def authors
522
+ ul.authorList! do
523
+ @authors.each do |author, cmds|
524
+ li do
525
+ strong author
526
+ text " worked on: " +
527
+ cmds.map { |cmd|
528
+ capture { a cmd.name, :href => R(Show, cmd.name) }
529
+ }.join(", ")
530
+ end
531
+ end
532
+ end
533
+ end
534
+
535
+ def recent
536
+ @days.each do |day, cmds|
537
+ strong day
538
+ ul do
539
+ cmds.each do |cmd|
540
+ li do
541
+ a cmd.name, :href => R(Show, cmd.name)
542
+ div.byline "by #{ cmd.author } at #{ cmd.created_at.strftime("%H:%M") }"
543
+ end
544
+ end
545
+ end
546
+ end
547
+ end
548
+
549
+ def show
550
+ h2 "Instructions"
551
+ red @version.doc
552
+
553
+ h2 "Code"
554
+ if defined? SYNTAX
555
+ SYNTAX.convert(@version.code)
556
+ else
557
+ pre @version.code
558
+ end
559
+
560
+ div.byline "Revised on #{ @version.created_at } by #{ @version.author }"
561
+
562
+ div.navigation do
563
+ a "Edit Page", :href => R(Edit, @version.name, @version.version),
564
+ :class => "navlink", :accesskey => "E"
565
+ unless @version.version == 1
566
+ a 'Back in time', :href => R(Show, @version.name, @version.version-1)
567
+ end
568
+ unless @version.version == @cmd.version
569
+ a 'Next', :href => R(Show, @version.name, @version.version+1)
570
+ a 'Current', :href => R(Show, @version.name)
571
+ end
572
+ small "(#{ @cmd.version.size } revisions)"
573
+ end
574
+ end
575
+
576
+ def edit
577
+ form :id => "editForm", :action => R(Edit, @cmd.name), :method => "post", :onSubmit => "cleanAuthorName();" do
578
+ div :style => "margin: 0 100px;" do
579
+ h2 "Instructions"
580
+ textarea @cmd.doc, :name => "command[doc]", :style => "width: 450px; height: 120px"
581
+
582
+ h2 "Code"
583
+ textarea @cmd.code, :name => "command[code]", :style => "width: 450px; height: 380px"
584
+
585
+ p do
586
+ input :type => "submit", :value => "Save"; text " as "
587
+ input :type => "text", :name => "command[author]", :id => "authorName", :value => @author,
588
+ :onClick => "this.value == '#{ CampSh::ANON }' ? this.value = '' : true"; text " | "
589
+ a "Cancel", :href => "/cancel_edit/#{ @cmd.name }"
590
+ end
591
+ end
592
+
593
+ script <<-END, :language => "JavaScript1.2"
594
+ function cleanAuthorName() {
595
+ if (document.getElementById('authorName').value == "") {
596
+ document.getElementById('authorName').value = '#{ @anon }';
597
+ }
598
+ }
599
+ END
600
+ end
601
+ end
602
+
603
+ def howto
604
+ red %[
605
+ Here's the rundown on CampShell. It's wiki-like storage for simple Ruby scripts. You can then run these scripts from
606
+ the URL. So, your job is to fill up CampShell with these commands, some of which can be found
607
+ "here":http://mousehole.rubyforge.org/wiki/wiki.pl?CampShells.
608
+
609
+ h2. Using with Firefox
610
+
611
+ For best effect, go to @about:config@ and set @keyword.URL@ to @http://#{env['HTTP_HOST']}#{self./ "/run/"}@. Restart Firefox,
612
+ you will then be able to run commands directly from the address bar.
613
+
614
+ For example:
615
+
616
+ * @new google@ will open the page for creating a new @google@ command.
617
+ * @show google@ will display the documentation for that command once it is saved.
618
+ * @edit google@ will open the editor for the @google@ command.
619
+ * @google mousehole commands@ will run the @google@ command with the arguments @mousehole@ and @commands@.
620
+ * @list all@ or @keyword:list@ displays all commands.
621
+ * @howto ?@ or @keyword:howto@ shoots you over to this page.
622
+ ].gsub(/^ +/, '')
623
+ end
624
+ end
625
+
626
+ if __FILE__ == $0
627
+ CampSh::Models::Base.establish_connection :adapter => 'sqlite3', :database => 'campsh.db'
628
+ CampSh::Models::Base.logger = Logger.new('camping.log')
629
+ CampSh.create
630
+ puts CampSh.run
631
+ end