camping 1.2 → 1.3
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/CHANGELOG +33 -0
- data/README +64 -7
- data/bin/camping +63 -0
- data/examples/blog/blog.rb +44 -14
- data/examples/campsh/campsh.rb +631 -0
- data/examples/serve +77 -26
- data/examples/tepee/tepee.rb +11 -6
- data/extras/Camping.gif +0 -0
- data/extras/flipbook_rdoc.rb +480 -0
- data/lib/camping-unabridged.rb +190 -55
- data/lib/camping.rb +36 -32
- metadata +24 -13
- data/examples/blog/styles.css +0 -10
data/CHANGELOG
ADDED
|
@@ -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
|
|
11
|
+
== A Camping Skeleton
|
|
12
|
+
|
|
13
|
+
A skeletal Camping blog could look like this:
|
|
12
14
|
|
|
13
15
|
require 'camping'
|
|
14
16
|
|
|
15
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
+
|
data/bin/camping
ADDED
|
@@ -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
|
data/examples/blog/blog.rb
CHANGED
|
@@ -18,10 +18,24 @@ module Blog::Models
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
Blog::Models.schema do
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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'
|
|
127
|
+
class Style < R '/styles.css'
|
|
113
128
|
def get
|
|
114
129
|
@headers["Content-Type"] = "text/css; charset=utf-8"
|
|
115
|
-
@body =
|
|
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
|
-
|
|
236
|
-
Blog::Models::
|
|
237
|
-
Blog::Models
|
|
238
|
-
|
|
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.
|
|
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
|