equipment 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+