root / trunk / examples / campsh.rb

Revision 210, 18.8 kB (checked in by zimbatm, 14 months ago)

Following _why's advise on #122, replacing [206]

Line 
1#!/usr/bin/env ruby
2
3$:.unshift File.dirname(__FILE__) + "/../../lib"
4%w(rubygems redcloth camping camping/db acts_as_versioned).each { |lib| require lib }
5 
6Camping.goes :CampSh
7
8module CampSh
9    NAME = 'CampSh'
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        Models.create_schema :assume => (Models::Command.table_exists? ? 1.0 : 0.0)
26    end
27end
28
29module CampSh::Models
30    class Command < Base
31        validates_uniqueness_of :name
32        validates_presence_of :author
33        acts_as_versioned
34    end
35    class CreateBasics < V 1.0
36        def self.up
37            create_table :campsh_commands do |t|
38                t.column :author,     :string,  :limit => 40
39                t.column :name,       :string,  :limit => 255
40                t.column :created_at, :datetime
41                t.column :doc,        :text
42                t.column :code,       :text
43            end
44            Command.create_versioned_table
45            Command.reset_column_information
46        end
47        def self.down
48            drop_table :campsh_commands
49            Command.drop_versioned_table
50        end
51    end
52end
53
54module CampSh::Controllers
55    class Index < R '/'
56        def get
57            redirect List
58        end
59    end
60
61    class List
62        def get
63            @cmds = Command.find :all, :order => 'name'
64            @title = "Command List"
65            render :list
66        end
67    end
68
69    class Run < R '/run/(\w+)', '/run/(\w+)/(.+)', '/run/(\w+) (.+)'
70        def get(cmd, args=nil)
71            @cmd = Command.find_by_name(cmd)
72            unless @cmd
73                redirect New, name
74                return
75            end
76
77            args = args.to_s.strip.split(/[\/\s]+/)
78            eval(@cmd.code)
79        end
80    end
81
82    class Authors
83        def get
84            @authors =
85                Command.find(:all, :order => "author, name").inject({}) do |hsh, cmd|
86                    (hsh[cmd.author] ||= []) << cmd
87                    hsh
88                end
89            @title = "Author"
90            render :authors
91        end
92    end
93
94    class Recent
95        def get
96            @days =
97                Command.find(:all, :order => "created_at DESC").inject({}) do |hsh, cmd|
98                    (hsh[cmd.created_at.strftime("%B %d, %Y")] ||= []) << cmd
99                    hsh
100                end
101            @title = "Recently Revised"
102            render :recent
103        end
104    end
105
106    class Show < R '/show/(\w+)', '/show/(\w+)/(\d+)', '/cancel_edit/(\w+)'
107        def get(name, version = nil)
108            unless @cmd = Command.find_by_name(name)
109                redirect(Edit, name)
110                return
111            end
112            @version = (version.nil? or version == @cmd.version.to_s) ? @cmd : @cmd.versions.find_by_version(version)
113            render :show
114        end
115    end
116
117    class New < R '/new', '/new/(\w+)'
118        def get(name)
119            @cmd = Command.new(:name => name)
120            @title = "Creating #{name}"
121            render :edit
122        end
123        def post
124            @cmd = Command.new(:name => input.cmd)
125            @title = "Creating #{input.cmd}"
126            render :edit
127        end
128    end
129
130    class Edit < R '/edit/(\w+)', '/edit/(\w+)/(\d+)'
131        def get(name, version = nil)
132            @cmd = Command.find_by_name(name)
133            @cmd = @cmd.versions.find_by_version(version) unless version.nil? or version == @cmd.version.to_s
134            @title = "Editing #{name}"
135            @author = @cookies.cmd_author || CampSh::ANON
136            render :edit
137        end
138        def post(name)
139            @cookies.cmd_author = input.command.author
140            Command.find_or_create_by_name(name).update_attributes(input.command)
141            redirect Show, name
142        end
143    end
144
145    class HowTo
146        def get
147            @title = "How To"
148            render :howto
149        end
150    end
151
152    class Style < R '/styles.css'
153        def get
154            @headers['Content-Type'] = 'text/css'
155            %Q[
156                h1#pageName, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover,
157                #TextileHelp h3 { color: #003B76; }
158
159                #container { width: 720px; }
160
161                #container {
162                    float: none;
163                    margin: 0 auto;
164                    text-align: center;
165                    padding: 2px;
166                    border: solid 2px #999;
167                }
168
169                #content {
170                    margin: 0;
171                    padding: 9px;
172                    text-align: left;
173                    border-top: none;
174                    border: solid 1px #999;
175                    background-color: #eee;
176                }
177
178                body, p, ol, ul, td {
179                    font-family: verdana, arial, helvetica, sans-serif;
180                    font-size:   15px;
181                    line-height: 110%;
182                }
183
184                a { color: #000; }
185
186                .newWikiWord { background-color: #eee; }
187                .newWikiWord a:hover { background-color: white; }
188
189                a:visited { color: #666; }
190                a:hover { color: #fff; background-color:#000; }
191
192                /* a.edit:link, a.edit:visited { color: #DA0006; } */
193
194
195                h1, h2, h3 { color: #333; font-family: georgia, verdana; text-align: center; line-height: 70%; margin-bottom: 0; }
196                h1 { font-size: 28px }
197                h2 { font-size: 22px }
198                h3 { font-size: 19px }
199
200                h1#pageName {
201                    margin: 5px 0px 0px 0px;
202                    padding: 0px 0px 0px 0px;
203                    line-height: 28px;
204                }
205
206                h1#pageName small {
207                    color: grey;
208                    line-height: 10px;
209                    font-size: 10px;
210                    padding: 0px;
211                }
212
213                a.nav, a.nav:link, a.nav:visited { color: #000; }
214                a.nav:hover { color: #fff; background-color:#000; }
215
216                li { margin-bottom: 7px }
217
218                .navigation {
219                    margin-top: 5px;
220                    font-size : 12px;
221                    color: #999;
222                    text-align: center;
223                }
224
225                .navigation a:hover { color: #fff; background-color:#000; }
226
227                .navigation a {
228                    font-size: 11px;
229                    color: black;
230                    font-weight: bold;
231                }
232
233                .navigation small a {
234                    font-weight: normal;
235                    font-size: 11px;
236                }
237
238                .navOn{
239                    font-size: 11px;
240                    color: grey;
241                    font-weight: bold;
242                    text-decoration: none;
243                }
244
245                .help {
246                    font-family: verdana, arial, helvetica, sans-serif;
247                    font-size: 11px;
248                }
249
250                .inputBox {
251                    font-family: verdana, arial, helvetica, sans-serif;
252                    font-size: 11px;
253                    background-color: #eee;
254                    padding: 5px;
255                    margin-bottom: 20px;
256                }
257
258                blockquote {
259                    display: block;
260                    margin: 0px 0px 20px 0px;
261                    padding: 0px 30px;
262                    font-size:11px;
263                    line-height:17px;
264                    font-style: italic; 
265                }
266
267                pre {
268                    background-color: #eee;
269                    padding: 10px;
270                    font-size: 11px;
271                }
272
273                ol.setup {
274                    font-size: 19px;
275                    font-family: georgia, verdana;
276                    padding-left: 25px;
277                }
278
279                ol.setup li {
280                    margin-bottom: 20px
281                }
282
283                .byline {
284                    font-size: 10px;
285                    font-style: italic; 
286                    margin-bottom: 10px;
287                    color: #999;
288                }
289
290                .references {
291                    font-size: 10px;
292                }
293
294                .diffdel {
295                    background: pink;
296                }
297
298                .diffins {
299                    background: lightgreen;
300                }
301
302                #allCommands ul {
303                    list-style: none;
304                }
305
306                #allCommands li {
307                    padding: 1px 4px;
308                }
309
310                #allCommands li:hover {
311                    background-color: white;
312                }
313
314                #allCommands a,
315                #allCommands a:hover,
316                #allCommands a:link,
317                #allCommands a:visited,
318                #allCommands h2 {
319                    color: #369;
320                    background-color: transparent;
321                    font-weight: normal;
322                    font-size: 24px;
323                    text-align: left;
324                }
325
326                #TextileHelp table {
327                    margin-bottom: 0;
328                }
329
330                #TextileHelp table+h3 {
331                    margin-top: 11px;
332                }
333
334                #TextileHelp table td {
335                    font-size: 11px;
336                    padding: 3px;
337                    vertical-align: top;
338                    border-top: 1px dotted #ccc;
339                }
340
341                #TextileHelp table td.arrow {
342                    padding-right: 5px;
343                    padding-left: 10px;
344                    color: #999;
345                }
346
347                #TextileHelp table td.label {
348                    font-weight: bold;
349                    white-space: nowrap;
350                    font-size: 10px;
351                    padding-right: 15px;
352                    color: #000;
353                }
354
355                #TextileHelp h3 {
356                    font-size: 11px;
357                    font-weight: bold;
358                    font-weight: normal;
359                    margin: 0 0 5px 0;
360                    padding: 5px 0 0 0;
361                }
362
363                #TextileHelp p {
364                    font-size: 10px;
365                }
366
367                .rightHandSide {
368                    float: right;
369                    width: 147px;
370                    margin-left: 10px;
371                    padding-left: 20px;
372                    border-left: 1px dotted #ccc;
373                }
374
375                .rightHandSide p {
376                    font-size: 10px;
377                }
378
379                .newsList {
380                margin-top: 20px;
381                }
382
383                .newsList p {
384                margin-bottom:30px
385                }
386
387                .leftHandSide
388                {
389                    float: right;
390                    width: 147px;
391                    margin-left: 10px;
392                    padding-left: 20px;
393                    border-left: 1px dotted #ccc;
394                }
395
396                .leftHandSide p
397                {
398                    font-size: 10px;
399                    margin: 0;
400                    padding: 0;
401                }
402
403                .leftHandSide h2
404                {
405                    font-size: 12px;
406                    margin-bottom: 0;
407                    padding-bottom: 0;
408                }
409
410                .property
411                {
412                    color: grey;
413                    font-size: 9px;
414                    text-align: right;
415                }
416
417                body
418                {
419                    background-color: #ccc;
420                    padding: 0;
421                    margin: 20px;
422                    color: #333;
423                    line-height: 1.5;
424                    font-size: 85%;
425                    /*  hacky hack */
426                    voice-family: "\"}\"";
427                    voice-family: inherit;
428                    font-size: 80%;
429                }
430
431                /* be nice to opera */
432                html>body { font-size: 80%; }
433
434                /* syntax highlighting */
435                .keyword {
436                    font-weight: bold;
437                }
438                .comment {
439                    color: #555;
440                }
441                .string, .number {
442                    color: #396;
443                }
444                .regex {
445                    color: #435;
446                }
447                .ident {
448                    color: #369;
449                }
450                .symbol {
451                    color: #000;
452                }
453                .constant, .class {
454                    color: #630;
455                    font-weight: bold;
456                }
457            ]
458        end
459    end
460
461end
462
463module CampSh::Views
464    def red( str )
465        require 'redcloth'
466        self << RedCloth.new( str ).to_html
467    end
468
469    def layout
470        html do
471            head do
472                title "CampShell -- #{ @title }"
473                style "@import '#{ self / R(Style) }';", :type => 'text/css'
474            end
475            body do
476                div.container! do
477                    div.content! do
478                        h2 "CampShell"
479                        h1 @title
480                        _navigation
481                        self << yield
482                    end
483                end
484            end
485        end
486    end
487
488    def _navigation
489        form :id => "navigationForm", :class => "navigation", :style => "font-size: 10px" do 
490            [["Command List", R(List), "Alphabetical list of commands", "A"],
491             ["Recently Revised", R(Recent), "Pages sorted by when they were last changed", "U"],
492             ["Authors", R(Authors), "Who wrote what", "W"],
493             ["How To", R(HowTo), "How to use CampShell", "H"]
494            ].map do |txt, link, title, key|
495                a txt, :href => link, :title => title, :accesskey => key
496            end.join(" | ")
497        end
498    end
499
500    def list
501        div.allCommands! :style => "width: 500px; margin: 0 100px" do
502            form.editForm! :action => R(New), :method => "post" do
503                ul do
504                    if @cmds.each do |cmd|
505                        li do
506                            h2 { a cmd.name, :href => R(Show, cmd.name) }
507                            red cmd.doc
508                        end
509                    end.empty?
510                        h2 "No commands created yet."
511                    end
512
513                    li do
514                        text "Create command: "
515                        input :type => "text", :name => "cmd", :id=> "newCmd", :value => "shortcut",
516                              :onClick => "this.value == 'shortcut' ? this.value = '' : true"
517                    end
518                end
519            end
520        end
521    end
522
523    def authors
524        ul.authorList! do
525            @authors.each do |author, cmds|
526                li do
527                    strong(author) + " worked on: " +
528                        cmds.map { |cmd| a cmd.name, :href => R(Show, cmd.name) }.join(", ")
529                end
530            end
531        end
532    end
533
534    def recent
535        @days.each do |day, cmds|
536            strong day
537            ul do
538                cmds.each do |cmd|
539                    li do
540                        a cmd.name, :href => R(Show, cmd.name)
541                        div.byline "by #{ cmd.author } at #{ cmd.created_at.strftime("%H:%M") }"
542                    end
543                end
544            end
545        end
546    end
547
548    def show
549        h2 "Instructions"
550        red @version.doc
551
552        h2 "Code"
553        if defined? SYNTAX
554            SYNTAX.convert(@version.code)
555        else
556            pre @version.code
557        end
558
559        div.byline "Revised on #{ @version.created_at } by #{ @version.author }"
560
561        div.navigation do
562            a "Edit Page", :href => R(Edit, @version.name, @version.version),
563                           :class => "navlink", :accesskey => "E"
564            unless @version.version == 1
565                text " | "
566                a 'Back in time', :href => R(Show, @version.name, @version.version-1)
567            end
568            unless @version.version == @cmd.version
569                text " | "
570                a 'Next',    :href => R(Show, @version.name, @version.version+1)
571                text " | "
572                a 'Current', :href => R(Show, @version.name)
573            end
574            if @cmd.versions.size > 1
575                small "(#{ @cmd.versions.size } revisions)"
576            end
577        end
578    end
579
580    def edit
581        form :id => "editForm", :action => R(Edit, @cmd.name), :method => "post", :onSubmit => "cleanAuthorName();" do
582            div :style => "margin: 0 100px;" do
583                h2 "Instructions"
584                textarea @cmd.doc, :name => "command[doc]", :style => "width: 450px; height: 120px"
585
586                h2 "Code"
587                textarea @cmd.code, :name => "command[code]", :style => "width: 450px; height: 380px"
588
589                p do
590                    input :type => "submit", :value => "Save"; text " as "
591                    input :type => "text", :name => "command[author]", :id => "authorName", :value => @author,
592                          :onClick => "this.value == '#{ CampSh::ANON }' ? this.value = '' : true"; text " | "
593                    a "Cancel", :href => "/cancel_edit/#{ @cmd.name }"
594                end
595            end
596
597            script <<-END, :language => "JavaScript1.2"
598                function cleanAuthorName() {
599                    if (document.getElementById('authorName').value == "") {
600                        document.getElementById('authorName').value = '#{ @anon }';
601                    }
602                }
603            END
604        end
605    end
606
607    def howto
608        red %[
609            Here's the rundown on CampShell.  It's wiki-like storage for simple Ruby scripts.  You can then run these scripts from
610            the URL.  So, your job is to fill up CampShell with these commands, some of which can be found
611            "here":http://mousehole.rubyforge.org/wiki/wiki.pl?CampShells.
612
613            h2. Using with Firefox
614
615            For best effect, go to @about:config@ and set @keyword.URL@ to @http://#{env['HTTP_HOST']}#{self./ "/run/"}@. Restart Firefox,
616            you will then be able to run commands directly from the address bar.
617
618            For example:
619
620            * @new google@ will open the page for creating a new @google@ command.
621            * @show google@ will display the documentation for that command once it is saved.
622            * @edit google@ will open the editor for the @google@ command.
623            * @google mousehole commands@ will run the @google@ command with the arguments @mousehole@ and @commands@.
624            * @list all@ or @keyword:list@ displays all commands.
625            * @howto ?@ or @keyword:howto@ shoots you over to this page.
626        ].gsub(/^ +/, '')
627    end
628end
629
Note: See TracBrowser for help on using the browser.