tanuki 0.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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010 Anatoly Ressin, Dimitry Solovyov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = Tanuki
2
+
3
+ Tanuki is a web application framework inspired by the MVVM pattern with focus
4
+ on DRY code and extensibility. Tanuki tries to keep its looks close to
5
+ idiomatic Ruby, so you would feel at home.
6
+
7
+ == Quick Start
8
+
9
+ Fire up the terminal and type:
10
+
11
+ $ gem install tanuki
12
+ $ tanuki create test
13
+ $ ruby test/main.rb
14
+
15
+ View the result at: http://localhost:3000
16
+
17
+ == Resources
18
+
19
+ {Home Page}[http://bitbucket.org/dimituri/tanuki]
@@ -0,0 +1,3 @@
1
+ class Tanuki_Controller < Tanuki_Object
2
+ include Tanuki::ControllerBehavior
3
+ end
@@ -0,0 +1,5 @@
1
+ % if visual_child
2
+ <%! visual_child.default_view -%>
3
+ % else
4
+ <%! index_view -%>
5
+ % end
@@ -0,0 +1 @@
1
+ <%= self.class %> is under construction
@@ -0,0 +1,7 @@
1
+ % if current?
2
+ <span class="active"><%= self %></span>
3
+ % elsif active?
4
+ <a class="active" href="<%= self.link %>"><%= self %></a>
5
+ % else
6
+ <a href="<%= self.link %>"><%= self %></a>
7
+ % end
@@ -0,0 +1,3 @@
1
+ class Tanuki_Object
2
+ include Tanuki::ObjectBehavior
3
+ end
@@ -0,0 +1,2 @@
1
+ <h1>Oh no!</h1>
2
+ <p>Tanuki broke free at <%= ctx.env['REQUEST_URI'] %>!</p>
@@ -0,0 +1,9 @@
1
+ class Tanuki_Page_Missing < Tanuki_Controller
2
+ def visual_parent
3
+ nil
4
+ end
5
+
6
+ def result_type
7
+ :not_found
8
+ end
9
+ end
@@ -0,0 +1,121 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Welcome to Tanuki</title>
6
+ <style type="text/css" media="screen">
7
+ body {
8
+ margin: 0;
9
+ padding: 0;
10
+ font-family: 'Trebuchet MS', Helvetica, sans-serif;
11
+ font-size: 11pt;
12
+ background-color: #f5f4ef;
13
+ color: #000305;
14
+ }
15
+
16
+ #header {
17
+ margin: 0 auto;
18
+ width: 540px;
19
+ }
20
+
21
+ div.top {
22
+ background-color: #fff;
23
+ margin: 0 auto 20px auto;
24
+ padding: 15px 20px 0 20px;
25
+ width: 540px;
26
+ border-radius: 10px;
27
+ -moz-border-radius: 10px;
28
+ -webkit-border-radius: 10px;
29
+ }
30
+
31
+ h1 {
32
+ margin: 20px 0;
33
+ padding: 0;
34
+ font-size: 22pt;
35
+ }
36
+
37
+ h1 em {
38
+ display: block;
39
+ font-style: normal;
40
+ font-size: 12pt;
41
+ color: #777a7c;
42
+ }
43
+
44
+ h2 {
45
+ margin: 0 0 15px 0;
46
+ padding: 0;
47
+ font-size: 16pt;
48
+ }
49
+
50
+ #next ol {
51
+ margin: 0;
52
+ padding-bottom: 25px;
53
+ }
54
+
55
+ #next li {
56
+ margin: 0;
57
+ padding: 0;
58
+ font-size: 16pt;
59
+ font-weight: bold;
60
+ color: #c74451;
61
+ }
62
+
63
+ #next li span {
64
+ font-size: 11pt;
65
+ font-weight: normal;
66
+ color: #000305;
67
+ }
68
+
69
+ code {
70
+ font-family: 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', Menlo, Monaco, Consolas, 'Courier New', monospace;
71
+ font-size: 10pt;
72
+ background-color: #fffccc;
73
+ padding: 1px 3px;
74
+ }
75
+
76
+ #env div {
77
+ padding-bottom: 25px;
78
+ }
79
+
80
+ #env table {
81
+ margin: 0;
82
+ padding: 0;
83
+ border-collapse: collapse;
84
+ color: #444;
85
+ }
86
+
87
+ #env table th {
88
+ text-align: left;
89
+ }
90
+
91
+ #env table td {
92
+ padding-left: 20px;
93
+ font-size: 10pt;
94
+ }
95
+ </style>
96
+ </head>
97
+ <body>
98
+ <div id="header">
99
+ <h1>It Works! <em>You have been granted one Tanuki.</em></h1>
100
+ </div>
101
+ <div class="top" id="next">
102
+ <h2>What to do next</h2>
103
+ <div>
104
+ <p>Here's a few suggestions to get things going:</p>
105
+ <ol>
106
+ <li><span>To use a database, describe its contents in <code>schema/user</code>.</span></li>
107
+ <li><span>To generate models from your schema, type <code>rake tanuki:models</code>.</span></li>
108
+ <li><span>To add or edit controllers, navigate to <code>app/user</code>.</span></li>
109
+ </ol>
110
+ </div>
111
+ </div>
112
+ <div class="top" id="env">
113
+ <h2>Your setup</h2>
114
+ <div><table>
115
+ <tr><th>Ruby</th><td><%= RUBY_VERSION %> (<%= RUBY_RELEASE_DATE %>) [<%= RUBY_PLATFORM %>]</td></tr>
116
+ <tr><th>Rack</th><td><%= Rack.version %> (on <%= ctx.env['SERVER_SOFTWARE'] %>)</td></tr>
117
+ <tr><th>Tanuki</th><td><%= Tanuki::VERSION %></td></tr>
118
+ </table></div>
119
+ </div>
120
+ </body>
121
+ </html>
@@ -0,0 +1,2 @@
1
+ class User_Page_Index < Tanuki_Controller
2
+ end
data/bin/tanuki ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+ module Tanuki
3
+ class << self
4
+ def create(name)
5
+ require 'fileutils'
6
+ version
7
+ puts "\n creating #{name = name.downcase}"
8
+ FileUtils.mkdir name
9
+ puts " copying app/tanuki"
10
+ FileUtils.mkdir_p File.join(name, 'app', 'tanuki')
11
+ FileUtils.cp_r File.expand_path(File.join('..', '..', 'app', 'tanuki'), __FILE__), File.join(name, 'app')
12
+ puts " copying app/user"
13
+ FileUtils.mkdir_p File.join(name, 'app', 'user')
14
+ FileUtils.cp_r File.expand_path(File.join('..', '..', 'app', 'user'), __FILE__), File.join(name, 'app')
15
+ puts " creating cache"
16
+ FileUtils.mkdir(File.join(name, 'cache'))
17
+ puts " copying schema"
18
+ FileUtils.mkdir(File.join(name, 'schema'))
19
+ FileUtils.cp_r File.expand_path(File.join('..', '..', 'schema'), __FILE__), name
20
+ puts " writing main"
21
+ File.open(File.join(name, 'main.rb'), 'w') {|file| file.write "require 'tanuki'\nTanuki.development_application" }
22
+ end
23
+
24
+ def help
25
+ version
26
+ puts "\nbasic commands:\n"
27
+ {
28
+ 'help' => 'show help (this text)',
29
+ 'create' => 'create a new app with the given name',
30
+ 'version' => 'show framework version'
31
+ }.each_pair {|k, v| puts ' %-7s %s' % [k, v] }
32
+ end
33
+
34
+ def init
35
+ case ARGV[0]
36
+ when /\Ac(r(e(a(te?)?)?)?)?\Z/, '--create', '-c' then create(ARGV[1])
37
+ when /\Ah(e(lp?)?)?\Z/, '--help', '-h', nil then help
38
+ when /\Av(e(r(s(i(on?)?)?)?)?)?\Z/, '--version', '-v' then version
39
+ else create(ARGV[0])
40
+ end
41
+ end
42
+
43
+ def version
44
+ begin
45
+ require 'tanuki/version'
46
+ rescue LoadError
47
+ $:.unshift File.expand_path(File.join('..', '..', 'lib'), __FILE__)
48
+ require 'tanuki/version'
49
+ end
50
+ puts "Tanuki version #{VERSION}"
51
+ end
52
+ end
53
+ end
54
+
55
+ Tanuki.init if File.basename(__FILE__) == File.basename($0)
@@ -0,0 +1,111 @@
1
+ module Tanuki
2
+
3
+ # Tanuki::Application is the starting point for all framework applications.
4
+ # It contains core application functionality like configuration, request handling and view management.
5
+ class Application
6
+
7
+ @context = Loader.context = Context.new
8
+ @rack_middleware = {}
9
+
10
+ class << self
11
+
12
+ # Removes a given middleware from the Rack middleware pipeline
13
+ def discard(middleware)
14
+ @rack_middleware.delete(middleware)
15
+ end
16
+
17
+ # Runs the application with current settings
18
+ def run
19
+ rack_builder = Rack::Builder.new
20
+ @rack_middleware.each {|middleware, params| rack_builder.use(middleware, *params[0], &params[1]) }
21
+ rack_builder.run(rack_app)
22
+ srv = available_server
23
+ puts "A wild Tanuki appears!", "You used #{srv.name.gsub(/.*::/, '')} at #{@context.host}:#{@context.port}."
24
+ srv.run rack_builder.to_app, :Host => @context.host, :Port => @context.port
25
+ end
26
+
27
+ # Sets an option to value in the current context.
28
+ def set(option, value)
29
+ @context.send("#{option}=".to_sym, value)
30
+ end
31
+
32
+ # Adds a given middleware to the Rack middleware pipeline
33
+ def use(middleware, *args, &block)
34
+ @rack_middleware[middleware] = [args, block]
35
+ end
36
+
37
+ # Adds a template visitor block. This block must return a Proc.
38
+ #
39
+ # visitor :escape do
40
+ # s = ''
41
+ # proc {|out| s << CGI.escapeHTML(out.to_s) }
42
+ # end
43
+ # visitor :printf do |format|
44
+ # s = ''
45
+ # proc {|out| s << format % out.to_s }
46
+ # end
47
+ #
48
+ # It can later be used in a template via the visitor syntax.
49
+ #
50
+ # <%_escape escaped_view %>
51
+ # <%_printf('<div>%s</div>') formatted_view %>
52
+ def visitor(sym, &block)
53
+ ObjectBehavior.instance_eval { define_method "#{sym}_visitor".to_sym, &block }
54
+ end
55
+
56
+ private
57
+
58
+ # Returns the first available server from a server list in the current context.
59
+ def available_server
60
+ @context.server.each do |server_name|
61
+ begin
62
+ return Rack::Handler.get(server_name.downcase)
63
+ rescue LoadError
64
+ rescue NameError
65
+ end
66
+ end
67
+ raise "servers #{@context.server.join(', ')} not found"
68
+ end
69
+
70
+ # Returns an array of template outputs for controller ctrl in context ctx.
71
+ def build_body(ctrl, request_ctx)
72
+ arr = []
73
+ Launcher.new(ctrl, request_ctx).each &proc {|out| arr << out.to_s }
74
+ end
75
+
76
+ # Returns a Rack app block for Rack::Builder.
77
+ # This block is passed a request environment and returns and array of three elements:
78
+ # * a response status code,
79
+ # * a hash of headers,
80
+ # * an iterable body object.
81
+ # It is run on each request.
82
+ def rack_app
83
+ ctx = @context
84
+ proc do |env|
85
+ request_ctx = ctx.child
86
+ request_ctx.templates = {}
87
+ if match = env['REQUEST_PATH'].match(/^(.+)(?<!\$)\/$/)
88
+ loc = match[1]
89
+ loc << "?#{env['QUERY_STRING']}" unless env['QUERY_STRING'].empty?
90
+ [301, {'Location' => loc, 'Content-Type' => 'text/html; charset=utf-8'}, []]
91
+ else
92
+ request_ctx.env = env
93
+ result = ::Tanuki::ControllerBehavior.dispatch(request_ctx, ctx.i18n ? ::Tanuki::I18n : ctx.root_page,
94
+ Rack::Utils.unescape(env['REQUEST_PATH']).force_encoding('UTF-8'))
95
+ case result[:type]
96
+ when :redirect then
97
+ [302, {'Location' => result[:location], 'Content-Type' => 'text/html; charset=utf-8'}, []]
98
+ when :page then
99
+ [200, {'Content-Type' => 'text/html; charset=utf-8'}, build_body(result[:controller], request_ctx)]
100
+ else
101
+ [404, {'Content-Type' => 'text/html; charset=utf-8'}, build_body(result[:controller], request_ctx)]
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ end # end class << self
108
+
109
+ end # end Application
110
+
111
+ end # end Tanuki
@@ -0,0 +1,27 @@
1
+ module Tanuki
2
+ module Argument
3
+
4
+ # Tanuki::Argument::Base is the base class for argument types.
5
+ class Base
6
+
7
+ attr_reader :default, :value
8
+
9
+ # Initializes the argument with a default value.
10
+ def initialize(default)
11
+ @value = @default = default
12
+ end
13
+
14
+ # Returns a string representation of the argument value.
15
+ def to_s
16
+ @value.to_s
17
+ end
18
+
19
+ # Sets the value with required rules.
20
+ def value=(obj)
21
+ @value = to_value(obj)
22
+ end
23
+
24
+ end # end Base
25
+
26
+ end # end Argument
27
+ end # end Tanuki
@@ -0,0 +1,15 @@
1
+ module Tanuki
2
+ module Argument
3
+
4
+ # Tanuki::Argument::Integer is a class for Integer arguments.
5
+ class Integer < Base
6
+
7
+ # Returns argument value from a string representation.
8
+ def to_value(obj)
9
+ begin Kernel::Integer obj rescue @default end
10
+ end
11
+
12
+ end # end Integer
13
+
14
+ end # end Argument
15
+ end # end Tanuki
@@ -0,0 +1,22 @@
1
+ module Tanuki
2
+ module Argument
3
+
4
+ # Tanuki::Argument::IntegerRange is a class for Integer arguments with a certain value range.
5
+ class IntegerRange < Integer
6
+
7
+ # Initializes the argument with a default value and allowed value range.
8
+ def initialize(range, default=nil)
9
+ super(default ? default : range.first)
10
+ @range = range
11
+ end
12
+
13
+ # Returns argument value from a string representation.
14
+ def to_value(obj)
15
+ i = super(obj)
16
+ @range.include?(i) ? i : @default
17
+ end
18
+
19
+ end # end IntegerRange
20
+
21
+ end # end Argument
22
+ end # end Tanuki
@@ -0,0 +1,15 @@
1
+ module Tanuki
2
+ module Argument
3
+
4
+ # Tanuki::Argument::String is a class for String arguments.
5
+ class String < Base
6
+
7
+ # Returns argument value from a string representation.
8
+ def to_value(obj)
9
+ obj.to_s
10
+ end
11
+
12
+ end # end String
13
+
14
+ end # end Argument
15
+ end # end Tanuki
@@ -0,0 +1,41 @@
1
+ require 'tanuki/argument/base'
2
+ require 'tanuki/argument/integer'
3
+ require 'tanuki/argument/integer_range'
4
+ require 'tanuki/argument/string'
5
+
6
+ module Tanuki
7
+
8
+ # Tanuki::Argument contains basic classes and methods for controller arguments.
9
+ module Argument
10
+
11
+ @assoc = {}
12
+
13
+ class << self
14
+
15
+ # Remove argument association for a given type class.
16
+ def delete(klass)
17
+ @assoc.delete(klass)
18
+ end
19
+
20
+ # Associate a given type class with an argument class.
21
+ def store(klass, arg_class)
22
+ warn "Tanuki::Argument::Base is not an ancestor of `#{arg_class}'" unless arg_class.ancestors.include? Argument::Base
23
+ @assoc[klass] = arg_class
24
+ end
25
+
26
+ alias_method :[], :store
27
+
28
+ # Convert a given type object to an argument object.
29
+ def to_argument(obj, *args)
30
+ if @assoc.include?(klass = obj.class)
31
+ @assoc[klass].new(obj, *args)
32
+ else
33
+ Argument::String.new(obj.to_s)
34
+ end
35
+ end
36
+
37
+ end # end class << self
38
+
39
+ end # end Argument
40
+
41
+ end # end Tanuki
@@ -0,0 +1,93 @@
1
+ module Tanuki
2
+
3
+ # Tanuki::Configurator is a scope for evaluating a Tanuki application configuration block.
4
+ # Use Tanuki::development_application and Tanuki::production_application to create such a block.
5
+ class Configurator
6
+
7
+ class << self
8
+
9
+ private
10
+
11
+ # Invokes Tanuki::Argument.store.
12
+ def argument(klass, arg_class)
13
+ Argument.store(klass, arg_class)
14
+ end
15
+
16
+ # Invokes Tanuki::Application.set.
17
+ def set(option, value)
18
+ Application.set(option, value)
19
+ end
20
+
21
+ # Invokes Tanuki::Application.use.
22
+ def use(middleware, *args, &block)
23
+ Application.use(middleware, *args, &block)
24
+ end
25
+
26
+ # Invokes Tanuki::Application.discard.
27
+ def discard(middleware)
28
+ Application.discard(middleware)
29
+ end
30
+
31
+ # Invokes Tanuki::Application.visitor.
32
+ def visitor(sym, &block)
33
+ Application.visitor(sym, &block)
34
+ end
35
+
36
+ end # end class << self
37
+
38
+ end # end Configurator
39
+
40
+ # Creates default configuration for development environments.
41
+ def self.development_application(&block)
42
+ Application.set :development, true
43
+ Application.instance_eval do
44
+ use Rack::CommonLogger
45
+ use Rack::Lint
46
+ use Rack::Reloader, 0
47
+ use Rack::ShowExceptions
48
+ end
49
+ common_application(&block)
50
+ end
51
+
52
+ # Creates default configuration for production environments.
53
+ def self.production_application(&block)
54
+ Application.set :development, false
55
+ common_application(&block)
56
+ end
57
+
58
+ private
59
+
60
+ # Creates default configuration for common environments.
61
+ def self.common_application(&block)
62
+ Application.instance_eval do
63
+ use Rack::Head
64
+ use Rack::ShowStatus
65
+ set :server, [:thin, :mongrel, :webrick]
66
+ set :host, '0.0.0.0'
67
+ set :port, 3000
68
+ set :root, File.expand_path('..', $0)
69
+ set :app_root, proc { File.join(root, 'app') }
70
+ set :cache_root, proc { File.join(root, 'cache') }
71
+ set :schema_root, proc { File.join(root, 'schema') }
72
+ set :root_page, ::User_Page_Index
73
+ set :missing_page, ::Tanuki_Page_Missing
74
+ set :i18n, false
75
+ set :language, nil
76
+ set :language_fallback, {}
77
+ set :languages, proc { language_fallback.keys }
78
+ set :best_language, proc {|lngs| language_fallback[language].each {|lng| return lng if lngs.include? lng }; nil }
79
+ set :best_translation, proc {|trn| language_fallback[language].each {|lng| return trn[lng] if trn.include? lng }; nil }
80
+ visitor :string do s = ''; proc {|out| s << out.to_s } end
81
+ end
82
+ Argument.instance_eval do
83
+ store Fixnum, Argument::Integer
84
+ store Bignum, Argument::Integer
85
+ store Range, Argument::IntegerRange
86
+ store String, Argument::String
87
+ end
88
+ Application.instance_eval { @context = @context.child }
89
+ Configurator.instance_eval(&block) if block_given?
90
+ Application.run
91
+ end
92
+
93
+ end # end Tanuki
@@ -0,0 +1,36 @@
1
+ module Tanuki
2
+
3
+ # Tanuki::Context is used to create unique environments for each request.
4
+ # Once a context variable has been set, it cannot be redefined.
5
+ # Child contexts are used to override parent context variables without changing them directly.
6
+ # Use Tanuki::Context#child to create new contexts.
7
+ class Context
8
+
9
+ # Create and return child context.
10
+ def child
11
+ Class.new(self.class).new
12
+ end
13
+
14
+ # Kernel#method_missing hook.
15
+ def method_missing(sym, arg=nil)
16
+ match = sym.to_s.match(/^([^=]+)(=)?$/)
17
+ self.class.instance_eval do
18
+ method_sym = match[1].to_sym
19
+ unless instance_methods(false).include? method_sym
20
+ if arg.is_a? Proc
21
+ define_method(method_sym, &arg)
22
+ else
23
+ define_method(method_sym) { arg }
24
+ end
25
+ return arg
26
+ else
27
+ raise "context entry `#{match[1]}' redefined, use Context#child"
28
+ end
29
+ end if match[2]
30
+ warn "#{__FILE__}:#{__LINE__}: warning: undefined context entry `#{sym}' for #{self}"
31
+ super
32
+ end
33
+
34
+ end # end Context
35
+
36
+ end # end Tanuki