equipment 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
1
+ require 'equipment'
2
+
3
+ module Ext
4
+
5
+ # Provides the Base#app method. It's used in extensions to get the
6
+ # application's module.
7
+ #
8
+ # == Dependency
9
+ #
10
+ # * Equipment
11
+ #
12
+ module AppUtil
13
+ extend Equipment
14
+
15
+ module Helpers
16
+ # this is a curious method that's needed because I can't access MyApp::C
17
+ # in extensions. I think this is because of how constants are resolved in ruby.
18
+ # I've seen that Rite will change that. Mabye it will be not needed then.
19
+ #
20
+ # In the meanwhile, you can use app::Controllers, app::Views, ...
21
+ def app
22
+ @__app ||= Kernel.const_get(/^\w+/.match(self.class.name).to_a.first)
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+
@@ -0,0 +1,71 @@
1
+ require 'equipment'
2
+ require 'ext/security'
3
+
4
+ module Ext
5
+
6
+ # Basic Http Authentication.
7
+ #
8
+ # Inspired by Manfred Stienstra (http://www.fngtps.com/2006/05/basic-authentication-for-camping).
9
+ #
10
+ # == Dependencies
11
+ #
12
+ # * Equipment
13
+ # * Security
14
+ #
15
+ # == TODO
16
+ # * More docs
17
+ #
18
+ module BasicAuth
19
+ extend Equipment
20
+ depends_on Security
21
+
22
+ def self.equip(app)
23
+ super
24
+ app::Controllers::Unauthenticated.send :include, app::Base
25
+ end
26
+
27
+ module Base
28
+
29
+ # Gets HTTP_Basic credendials from a request. It consists in an array
30
+ # of user and password.
31
+ def basic_credentials
32
+ puts "Authenticate"
33
+ if d = %w{X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION}.inject([]) \
34
+ { |d,h| env.has_key?(h) ? env[h].to_s.split : [] }
35
+ return Base64.decode64(d[1]).split(':')[0..1] if d[0] == 'Basic'
36
+ end; [nil, nil]
37
+ end
38
+ alias :authenticate :basic_credentials
39
+
40
+ def authorize(user, pass, force=false)
41
+ puts "Authorize"
42
+ return super unless force
43
+ forward(app::Controller::BasicAuth)
44
+ end
45
+
46
+ end
47
+
48
+ module Controllers
49
+
50
+ # Called when an url is not authorized. You can override this is your app.
51
+ class Unauthenticated < Security::Controllers::Unauthenticated
52
+ class << self
53
+ attr_accessor :realm
54
+ end
55
+ self.realm = 'Camping'
56
+
57
+ def get
58
+ @status = 401
59
+ @headers['Status'] = 'Unauthorized'
60
+ @headers['WWW-Authenticate'] = "Basic realm=\"#{self.class.realm}\""
61
+ super
62
+ end
63
+ end
64
+
65
+ class Unauthorized < Unauthenticated
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+
@@ -0,0 +1,26 @@
1
+ require 'equipment'
2
+
3
+ module Ext
4
+ # Not implemented yet
5
+ module Controls
6
+ extend Equipment
7
+
8
+ class Control
9
+ attr_reader :opts
10
+ def initialize(opts={})
11
+ @opts = opts
12
+ end
13
+
14
+ def render
15
+ end
16
+ alias :to_s :render
17
+ end
18
+
19
+ module Helpers
20
+ def control(*args)
21
+ Control.new(*args)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,83 @@
1
+ require 'equipment'
2
+ require 'ext/template_view'
3
+
4
+ begin
5
+ require 'erubis' # faster eruby parser
6
+ ERB = Erubis::Eruby
7
+ rescue LoadError
8
+ require 'erb'
9
+ end
10
+
11
+ module Ext
12
+
13
+ # Gives eruby templating capabilities to your app.
14
+ #
15
+ # == Dependencies
16
+ #
17
+ # * Equipment
18
+ # * TemplateView
19
+ # * `erubis` package. Available as a gem. If not available, it will use `erb`
20
+ #
21
+ # == Usage
22
+ #
23
+ # define the template_root to your views :
24
+ #
25
+ # YourApp::Views.template_root = 'tpl/'
26
+ #
27
+ # Then put your templates as tpl/xy.erb
28
+ #
29
+ # rendering occurs as usual.
30
+ #
31
+ # == Issues
32
+ #
33
+ # Rendering only occurs on regular views. Vars and blocks are not really
34
+ # integrated. You can still use them. Vars as *a and blocks as &b. Like
35
+ # <%= v[1] %> in your template.
36
+ #
37
+ module ErubyView
38
+ extend Equipment
39
+ depends_on TemplateView
40
+
41
+ module Base
42
+
43
+ # Generic wrapper. Forwards to #eruby_view? or ask parent.
44
+ def has_view?(view)
45
+ return true if eruby_view?(view)
46
+ super
47
+ end
48
+
49
+ # Checks if the view exists as an eruby template
50
+ def eruby_view?(view)
51
+ if /text\/html|\*\/\*/ =~ (env.HTTP_ACCEPT || '*/*')
52
+ return true if eruby_template(view)
53
+ end
54
+ end
55
+
56
+ # Returns the path or nil from a view name. Template extensions are .erb.
57
+ def eruby_template(view)
58
+ file = File.join(template_root, "#{view}.erb")
59
+ File.exists?(file) ? file : nil
60
+ end
61
+
62
+ # Generic wrapper. Forwards to #eruby_view or ask parent.
63
+ def view(m, *a, &b)
64
+ if eruby_view?(m)
65
+ return eruby_view(m, *a, &b)
66
+ end
67
+ super
68
+ end
69
+
70
+ # Compiles an eruby template from current instance variables, helpers
71
+ # and the template
72
+ #
73
+ # TODO : Support splat and block
74
+ # TODO : Implement parse caching here
75
+ # NOTE : Override @headers['Content-Type'] if necessary here
76
+ def eruby_view(m, *a, &b)
77
+ e = ::ERB.new(File.read(eruby_template(m)))
78
+ e.result(ivs_send(app::V.new).bind(m,*a,&b)) # instance variables are wrapped in V
79
+ end
80
+ end
81
+ end
82
+ end
83
+
@@ -0,0 +1,81 @@
1
+ require 'equipment'
2
+
3
+ module Ext
4
+ # Hilight some things for a short time. It's very convenient to have some
5
+ # look over things in the night. See Helpers#flash for more options.
6
+ #
7
+ # == Look :
8
+ #
9
+ # in your controller :
10
+ #
11
+ # def get
12
+ # flash.error = 'hey !'
13
+ # redirect R(Index)
14
+ # end
15
+ #
16
+ # in your view :
17
+ #
18
+ # def index
19
+ # div.error(flash.error) if flash.error
20
+ # # ...
21
+ # end
22
+ #
23
+ # or in your layout.. like you want. The flashed data will then disappear
24
+ # on the next request.
25
+ #
26
+ # == Dependency
27
+ #
28
+ # * Equipment
29
+ #
30
+ # == Issues
31
+ #
32
+ # The cookies are not deleted until the end of the session. This is because
33
+ # Camping does not provide this facility and I didn't implement it. I think
34
+ # it is okay altrough.
35
+ #
36
+ module Flash
37
+ extend Equipment
38
+
39
+ module Helpers
40
+ # The flash helper. Use in your controller or view. Retuns
41
+ # a Flasher.
42
+ def flash
43
+ @__flash ||= Flasher.new(cookies)
44
+ end
45
+ end
46
+
47
+ # The flash class. Stores the data temporarily in the cookies.
48
+ # Only store Strings.
49
+ class Flasher
50
+ def initialize(cookies)
51
+ @cookies = cookies
52
+ @data = cookies.dup
53
+ end
54
+
55
+ def [](key)
56
+ key = to_key(key)
57
+ @cookies[key] = '' if @cookies.has_key? key
58
+ return (not @data[key].nil? and not @data[key].empty?) ? @data[key] : nil
59
+ end
60
+
61
+ def []=(key, value)
62
+ unless value.nil? or value.empty?
63
+ key = to_key(key)
64
+ @cookies[key] = value
65
+ return @data[key] = value
66
+ end
67
+ end
68
+
69
+ # Allows fancy flash.xy or flash.xy = 'sdsd'
70
+ def method_missing(m, val='')
71
+ /=$/ =~ m.to_s ? send(:[]=, m.to_s.sub(/=$/,''), val) : send(:[], m)
72
+ end
73
+
74
+ private
75
+ def to_key(key)
76
+ "__flash__#{key}".to_sym
77
+ end
78
+ end
79
+ end
80
+ end
81
+
@@ -0,0 +1,22 @@
1
+ require 'equipment'
2
+ require 'ext/controls'
3
+
4
+ module Ext
5
+ # Not implemented yet
6
+ module Forms
7
+ extend Equipment
8
+ depends_on Controls
9
+
10
+ module Helpers
11
+ def form_for(xy)
12
+ end
13
+ end
14
+
15
+ module Mab
16
+ def form(*a, &b)
17
+ super
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,70 @@
1
+ require 'equipment'
2
+
3
+ module Ext
4
+
5
+ # Forwarding is like redirecting but internally. This won't send another
6
+ # request to the browser. See Base#forward for options.
7
+ #
8
+ # *HINT* : forwarding should be used rarely and only on GET. Use redirect on
9
+ # POST.
10
+ #
11
+ # == Workflows
12
+ #
13
+ # these are simplified workflows that illustrate the difference between
14
+ # redirecting and forwarding.
15
+ #
16
+ # === Workflow with redirect
17
+ #
18
+ # Http client -> YourApp::Controller1 -> Http client -> YourApp::Controller2
19
+ # -> Http client
20
+ #
21
+ # === Workflow with forwarding
22
+ #
23
+ # Http client -> YourApp::Controller1 -> YourApp::Controller2 -> Http client
24
+ #
25
+ # == Dependency
26
+ #
27
+ # * Equipment
28
+ #
29
+ # == TODO
30
+ #
31
+ # * Allow more options in forwarding
32
+ module Forward
33
+ extend Equipment
34
+
35
+ def self.equip(app) # :nodoc:
36
+ super
37
+ app.module_eval do
38
+ def self.run(r=$stdin,e=ENV)
39
+ k, a = self::Controllers.D un("/#{e['PATH_INFO']}".gsub(%r!/+!,'/'))
40
+ k.send :include, self, self::Base, self::Models
41
+ k.new(r,e,(m=e['REQUEST_METHOD']||"GET")).service(*a)
42
+ rescue Forwarder => fw
43
+ fw.controller.new(nil,H['HTTP_HOST','','SCRIPT_NAME','','HTTP_COOKIE',''],m.to_s).service(*fw.args)
44
+ rescue Exception => x
45
+ self::Controllers::ServerError.new(r,e,'get').service(k,m,x)
46
+ end
47
+ end
48
+ end
49
+
50
+ class Forwarder < Exception # :nodoc:
51
+ attr_accessor :controller, :args
52
+
53
+ def initialize(controller, *args)
54
+ @controller, @args = controller, args
55
+ end
56
+ end
57
+
58
+ module Base
59
+ # Forwards to another controller
60
+ #
61
+ # * controller : the other controller
62
+ # * *a : only for ruby hackers ;-)
63
+ #
64
+ # TODO : make *a accessible for non hackers
65
+ def forward(controller, *a)
66
+ raise Forwarder.new(controller, *a)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,50 @@
1
+ require 'equipment'
2
+ require 'ext/app_util'
3
+ require 'ext/use_helper'
4
+
5
+
6
+ module Ext
7
+ module JsHelpers
8
+ extend Equipment
9
+ depends_on AppUtil
10
+ depends_on UseHelper
11
+
12
+ module Helpers
13
+ def join_scripts
14
+ if javascripts.size > 0
15
+ link = R(app::Controllers::JoinScripts, javascripts.map{|js| File.basename(js, '.js')}.join(';'))
16
+ app::Mab.new do
17
+ script :type=>'text/javascript', :src=>link
18
+ end.to_s
19
+ else "" end
20
+ end
21
+ end
22
+
23
+ module Controllers
24
+ class JoinScripts
25
+ class << self
26
+ attr_accessor :root
27
+ def urls; ['/scripts/(.*).js'] end
28
+ end
29
+
30
+ self.root = '.'
31
+
32
+ def get(scripts)
33
+ scripts = scripts.split(';').uniq
34
+ data = ""
35
+ scripts.each do |script|
36
+ path = File.join(self.class.root, "#{script}.js")
37
+ begin
38
+ data << File.open(path).read
39
+ rescue Errno::ENOENT => ex
40
+ data << "// ERROR : #{ex}\n"
41
+ end
42
+ end
43
+
44
+ data
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,152 @@
1
+ require 'equipment'
2
+ require 'ext/sendfile'
3
+ require 'ext/patches'
4
+
5
+ module Ext
6
+ # That equipment allows you to mount whole directories as a camping
7
+ # controller. See ControllersClassMethods#mount for options.
8
+ #
9
+ # == Features
10
+ #
11
+ # * Easy to use
12
+ # * Directory listing (skinnable)
13
+ # * Resolving Content-Type on file extensions
14
+ #
15
+ # == Dependencies
16
+ #
17
+ # * Equipment
18
+ # * Sendfile
19
+ # * Patches
20
+ # * `mime-types` package
21
+ #
22
+ # == Example
23
+ #
24
+ # Camping.goes :YourApp
25
+ #
26
+ # module YourApp::Controllers
27
+ # mount File.dirname(File.expand_path(__FILE__)) #=> YourApp::Controllers::Somefolder
28
+ # end
29
+ #
30
+ # that will be available under the url : "/your_app/somefolder"
31
+ #
32
+ # Check the mount method in ControllersClassMethod for more informations.
33
+ # You can also redefine the dir listing template by redefining `mount_listing`
34
+ # or `yourcontroller_listing`
35
+ #
36
+ # eg.
37
+ #
38
+ # module YourApp::Views
39
+ #
40
+ # def mount_listing
41
+ # ul do
42
+ # @files.each { |f| li f }
43
+ # nil # array bug in markaby
44
+ # end
45
+ # end
46
+ #
47
+ # def somefolder_listing # default
48
+ # end
49
+ #
50
+ # end
51
+ #
52
+ # Controller assignation by hand :
53
+ #
54
+ # module YourApp::Controllers
55
+ # class Stylesheets < M File.dirname('../public/css'), :name => false, :listing => false
56
+ # end
57
+ # end
58
+ #
59
+ # == Known bug
60
+ #
61
+ # There is a known bug where issued controllers appears as NilClass in the
62
+ # view. I really don't know why...
63
+ #
64
+ module Mount
65
+ extend Equipment
66
+ depends_on Patches
67
+ depends_on Sendfile
68
+
69
+ module ControllersClassMethods
70
+ # Mounts the path given by 'path'
71
+ #
72
+ # Options :
73
+ # * urls : list of accessible
74
+ # * name : name of the controller, false sets no controller
75
+ # * listing : set to false if you don't want directory listing.
76
+ #
77
+ # Option defaults for '/var/my_path' :
78
+ # * urls : /my_path(|/.*)
79
+ # * name : MyPath
80
+ #
81
+ # Renders the view Views#mount_listing if dir listing is enabled.
82
+
83
+ def mount(path, opts={})
84
+ defaults = {
85
+ :name => File.basename(path).gsub(/(^|_)(.)/){ $2.upcase },
86
+ :url => "/#{File.basename(path)}",
87
+ :listing => true
88
+ }
89
+ opts = defaults.merge(opts)
90
+
91
+ urls = ["#{opts[:url]}(|/.*)"]
92
+
93
+ klass = Class.new() do
94
+ meta_def(:urls) { urls }
95
+ meta_def(:path) { path }
96
+ meta_def(:listing?) { opts[:listing] }
97
+ end
98
+ klass.class_eval do
99
+ def get(file='') # :nodoc:
100
+ @path = File.join(self.class.path, file)
101
+ if File.directory?(@path) and self.class.listing?
102
+ @files = Dir[File.join(@path, '*')].map{|f| File.basename(f)}
103
+ if has_view?("#{self.class.name.downcase}_listing")
104
+ render("#{self.class.name.downcase}_listing")
105
+ else
106
+ render(:mount_listing)
107
+ end
108
+ else
109
+ sendfile(@path)
110
+ end
111
+ end
112
+ end
113
+ const_set(opts[:name], klass) if opts[:name]
114
+
115
+ if $DBG
116
+ puts "** mounted #{path}"
117
+ puts "Controller : #{klass}"
118
+ puts "Options : #{opts.inspect}"
119
+ end
120
+
121
+ klass
122
+ end
123
+ alias :M :mount
124
+ end
125
+
126
+ module Views
127
+ # Default dir listing. @files contains all the files.
128
+ def mount_listing
129
+ path_info = @env.PATH_INFO.sub /\/$/, ''
130
+ h1 "Dir listing of #{path_info}"
131
+ ul do
132
+ li { a '..', :href => path_info.sub(/\w*$/,'') }
133
+ @files.each do |file|
134
+ li { a file, :href => "#{path_info}/#{file}" }
135
+ end
136
+ nil
137
+ end
138
+ end
139
+ end
140
+
141
+ end
142
+ end
143
+
144
+ ### Mime-types extensions
145
+
146
+ js_type = MIME::Type.from_hash(
147
+ 'Content-Type' => 'text/javascript',
148
+ 'Extensions' => ['js']
149
+ )
150
+
151
+ MIME::Types.add(js_type)
152
+