merb 0.3.1 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -8,7 +8,7 @@ Lightweight MVC Ruby app server. For high performance dynamic pages.
8
8
  ** Dependencies **
9
9
  mongrel
10
10
  erubis
11
- json or fjson
11
+ json
12
12
  mime-types
13
13
  archive-tar-minitar
14
14
  rspec
data/Rakefile CHANGED
@@ -15,7 +15,7 @@ include FileUtils
15
15
 
16
16
 
17
17
  NAME = "merb"
18
- VERS = "0.3.1"
18
+ VERS = "0.3.3"
19
19
  CLEAN.include ['**/.*.sw?', '*.gem', '.config']
20
20
 
21
21
  setup_clean [ "pkg", "lib/*.bundle", "*.gem",
@@ -29,13 +29,6 @@ task :merb => [:clean, :rdoc, :package]
29
29
  task :doc => [:rdoc]
30
30
 
31
31
 
32
- #Rake::RDocTask.new do |rdoc|
33
- # rdoc.rdoc_dir = 'doc/rdoc'
34
- # rdoc.options += RDOC_OPTS
35
- # rdoc.main = "README"
36
- # rdoc.title = "Merb Documentation"
37
- # rdoc.rdoc_files.add ['README', 'LICENSE', 'TODO', 'lib/**/*.rb']
38
- #end
39
32
  Rake::RDocTask.new do |rdoc|
40
33
  files =['README', 'LICENSE', 'TODO', 'lib/**/*.rb']
41
34
  rdoc.rdoc_files.add(files)
@@ -66,6 +59,9 @@ spec = Gem::Specification.new do |s|
66
59
  s.add_dependency('mongrel')
67
60
  s.add_dependency('erubis')
68
61
  s.add_dependency('json')
62
+ s.add_dependency('mime-types')
63
+ s.add_dependency('xml-simple')
64
+ s.add_dependency('archive-tar-minitar')
69
65
  s.required_ruby_version = '>= 1.8.4'
70
66
 
71
67
  s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{bin,test,lib,examples}/**/*")
@@ -132,7 +128,7 @@ end
132
128
 
133
129
  desc 'Run all tests, specs and finish with rcov'
134
130
  task :aok do
135
- sh %{rake specs}
131
+ sh %{rake specs;rake rcov}
136
132
  end
137
133
 
138
134
  desc "Run all specs"
@@ -144,11 +140,25 @@ end
144
140
 
145
141
  desc "RCov"
146
142
  Spec::Rake::SpecTask.new('rcov') do |t|
143
+ t.spec_opts = ["--format", "specdoc"]
147
144
  t.spec_files = FileList['specs/**/*_spec.rb']
148
145
  t.libs = ['lib', 'server/lib' ]
149
146
  t.rcov = true
150
147
  end
151
148
 
149
+ STATS_DIRECTORIES = [
150
+ %w(Code lib/),
151
+ %w(Unit\ tests specs),
152
+ ].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
153
+
154
+ desc "Report code statistics (KLOCs, etc) from the application"
155
+ task :stats do
156
+ require __DIR__ + '/tools/code_statistics'
157
+ #require 'extra/stats'
158
+ verbose = true
159
+ CodeStatistics.new(*STATS_DIRECTORIES).to_s
160
+ end
161
+
152
162
  ##############################################################################
153
163
  # SVN
154
164
  ##############################################################################
data/TODO CHANGED
@@ -1,16 +1,3 @@
1
1
  [TODO]
2
- * Irb Console **DONE**
3
- * Migrations **DONE**
4
- * Helpers
5
- * config file **DONE**
6
- * session
7
- ** AR ** DONE **
8
- ** DRb
9
- * Rails integration
10
- * Text backend?
11
- * plugins support(rails plugins too?)
12
- * Layouts **DONE**
13
- * auto template_dir **DONE**
14
- * before/after filters **DONE**
15
- * merbjs **DONE**
16
- * testing
2
+ * more specs for Template engines and Rendering
3
+ *
Binary file
@@ -10,7 +10,7 @@
10
10
 
11
11
 
12
12
  puts "Compiling routes.."
13
- Merb::RouteMatcher.prepare do |r|
13
+ Merb::Router.prepare do |r|
14
14
  # restfull routes
15
15
  # r.resources :posts
16
16
 
@@ -19,4 +19,4 @@ Merb::RouteMatcher.prepare do |r|
19
19
 
20
20
  # change this for your home page to be avaiable at /
21
21
  #r.add '/', :controller => 'whatever', :action =>'index'
22
- end
22
+ end
data/lib/merb.rb CHANGED
@@ -11,16 +11,11 @@ end
11
11
  require 'fileutils'
12
12
  require 'erubis'
13
13
  require 'logger'
14
-
15
- begin
16
- require 'fjson'
17
- rescue LoadError
18
- require 'json'
19
- end
14
+ require 'json'
20
15
 
21
16
 
22
17
  module Merb
23
- VERSION='0.3.1' unless defined?VERSION
18
+ VERSION='0.3.3' unless defined?VERSION
24
19
  class Server
25
20
  class << self
26
21
  def config
@@ -56,6 +51,7 @@ MERB_FRAMEWORK_ROOT = __DIR__
56
51
  MERB_ROOT = Merb::Server.merb_root || Dir.pwd
57
52
  DIST_ROOT = Merb::Server.dist_root || Dir.pwd+'/dist'
58
53
  MERB_ENV = Merb::Server.config[:environment]
54
+ MERB_VIEW_ROOT = MERB_ROOT / "dist/app/views"
59
55
 
60
56
  logpath = $TESTING ? "/tmp/merb_test.log" : "#{MERB_ROOT}/log/merb.#{Merb::Server.port}.log"
61
57
  MERB_LOGGER = Logger.new(logpath)
@@ -78,10 +74,12 @@ MERB_LOGGER.level = case (Merb::Server.log_level.downcase rescue '')
78
74
  end
79
75
 
80
76
  module Mongrel::Const
81
- HTTP_COOKIE = 'HTTP_COOKIE'.freeze
82
- QUERY_STRING = 'QUERY_STRING'.freeze
83
- APPLICATION_JSON = 'application/json'.freeze
84
- TEXT_JSON = 'text/x-json'.freeze
77
+ HTTP_COOKIE = 'HTTP_COOKIE'.freeze
78
+ QUERY_STRING = 'QUERY_STRING'.freeze
79
+ APPLICATION_JSON = 'application/json'.freeze
80
+ TEXT_JSON = 'text/x-json'.freeze
81
+ APPLICATION_XML = 'application/xml'.freeze
82
+ TEXT_XML = 'text/xml'.freeze
85
83
  UPCASE_CONTENT_TYPE = 'CONTENT_TYPE'.freeze
86
84
  end
87
85
 
data/lib/merb/core_ext.rb CHANGED
@@ -10,5 +10,4 @@ corelib = __DIR__+'/merb/core_ext'
10
10
  merb_hash
11
11
  merb_numeric
12
12
  merb_symbol
13
-
14
13
  ].each {|fn| require File.join(corelib, fn)}
@@ -1,8 +1,77 @@
1
1
  class Hash
2
+
3
+ class << self
4
+ def from_xml(xml)
5
+ # TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple
6
+ undasherize_keys(typecast_xml_value(XmlSimple.xml_in(xml,
7
+ 'forcearray' => false,
8
+ 'forcecontent' => true,
9
+ 'keeproot' => true,
10
+ 'contentkey' => '__content__')
11
+ ))
12
+ end
13
+
14
+ private
15
+ def typecast_xml_value(value)
16
+ case value.class.to_s
17
+ when "Hash"
18
+ if value.has_key?("__content__")
19
+ content = translate_xml_entities(value["__content__"])
20
+ case value["type"]
21
+ when "integer" then content.to_i
22
+ when "boolean" then content.strip == "true"
23
+ when "datetime" then ::Time.parse(content).utc
24
+ when "date" then ::Date.parse(content)
25
+ else content
26
+ end
27
+ else
28
+ (value.blank? || value['type'] || value['nil'] == 'true') ? nil : value.inject({}) do |h,(k,v)|
29
+ h[k] = typecast_xml_value(v)
30
+ h
31
+ end
32
+ end
33
+ when "Array"
34
+ value.map! { |i| typecast_xml_value(i) }
35
+ case value.length
36
+ when 0 then nil
37
+ when 1 then value.first
38
+ else value
39
+ end
40
+ when "String"
41
+ value
42
+ else
43
+ raise "can't typecast #{value.inspect}"
44
+ end
45
+ end
46
+
47
+ def translate_xml_entities(value)
48
+ value.gsub(/&lt;/, "<").
49
+ gsub(/&gt;/, ">").
50
+ gsub(/&quot;/, '"').
51
+ gsub(/&apos;/, "'").
52
+ gsub(/&amp;/, "&")
53
+ end
54
+
55
+ def undasherize_keys(params)
56
+ case params.class.to_s
57
+ when "Hash"
58
+ params.inject({}) do |h,(k,v)|
59
+ h[k.to_s.tr("-", "_")] = undasherize_keys(v)
60
+ h
61
+ end
62
+ when "Array"
63
+ params.map { |v| undasherize_keys(v) }
64
+ else
65
+ params
66
+ end
67
+ end
68
+ end
69
+
70
+
2
71
  def with_indifferent_access
3
72
  MerbHash.new(self)
4
73
  end
5
- def to_params()
74
+ def to_params
6
75
  result = ''
7
76
  stack = []
8
77
 
@@ -1,10 +1,4 @@
1
- class Symbol
2
-
3
- # faster Symbol#to_s to speed up routing.
4
- def to_s
5
- @str_rep ||= id2name.freeze
6
- end
7
-
1
+ class Symbol
8
2
  # ["foo", "bar"].map &:reverse #=> ['oof', 'rab']
9
3
  def to_proc
10
4
  Proc.new{|*args| args.shift.__send__(self, *args)}
@@ -21,8 +21,8 @@ module Merb
21
21
  self._layout = :application
22
22
  self._session_id_key = :_session_id
23
23
  self._template_extensions = { }
24
- self._template_root = File.expand_path(MERB_ROOT / "dist/app/views")
25
- self._layout_root = File.expand_path(MERB_ROOT / "dist/app/views/layout")
24
+ self._template_root = File.expand_path(MERB_VIEW_ROOT)
25
+ self._layout_root = File.expand_path(MERB_VIEW_ROOT / "layout")
26
26
 
27
27
  include Merb::ControllerMixin
28
28
  include Merb::RenderMixin
@@ -51,6 +51,8 @@ module Merb
51
51
  json = JSON.parse(request.read || "") || {}
52
52
  json = MerbHash.new(json) if json.is_a? Hash
53
53
  querystring.update(json)
54
+ elsif [Mongrel::Const::APPLICATION_XML, Mongrel::Const::TEXT_XML].include?(@env[Mongrel::Const::UPCASE_CONTENT_TYPE])
55
+ querystring.update(Hash.from_xml(request.read).with_indifferent_access)
54
56
  else
55
57
  querystring.update(query_parse(request.read))
56
58
  end
@@ -64,7 +66,7 @@ module Merb
64
66
  if @params.key?(:_method) && allow.include?(@method)
65
67
  @method = @params.delete(:_method).downcase.intern
66
68
  end
67
- @request = Request.new(@env, @method)
69
+ @request = Request.new(@env, @method, request)
68
70
 
69
71
  MERB_LOGGER.info("Params: #{params.inspect}\nCookies: #{cookies.inspect}")
70
72
  end
@@ -178,12 +180,14 @@ module Merb
178
180
  else
179
181
  ok = true
180
182
  end
181
- case filter
182
- when Symbol, String
183
- send(filter) if ok
184
- when Proc
185
- filter.call(self) if ok
186
- end
183
+ if ok
184
+ case filter
185
+ when Symbol, String
186
+ send(filter)
187
+ when Proc
188
+ filter.call(self)
189
+ end
190
+ end
187
191
  end
188
192
  return :filter_chain_completed
189
193
  end
@@ -49,7 +49,7 @@ module Merb
49
49
  def route_path(path)
50
50
  path = path.sub(/\/+/, '/').sub(/\?.*$/, '')
51
51
  path = path[0..-2] if (path[-1] == ?/) && path.size > 1
52
- Merb::RouteMatcher.route_request(path)
52
+ Merb::Router.match(path)
53
53
  end
54
54
 
55
55
  # take a controller class name string and reload or require
@@ -45,11 +45,7 @@ module Merb
45
45
  end
46
46
  end
47
47
  =begin
48
- m = Merb::Mailer.new :to => 'foo@bar.com',
49
- :from => 'bar@foo.com',
50
- :subject => 'Welcome to whatever!',
51
- :body => partial(:sometemplate)
52
- m.deliver!
48
+
53
49
 
54
50
  Merb::Mailer.config = {
55
51
  :host=>'smtp.yourserver.com',
@@ -58,4 +54,10 @@ Merb::Mailer.config = {
58
54
  :pass=>'pass',
59
55
  :auth=>:plain # :plain, :login, or :cram_md5, default :plain
60
56
  }
57
+ m = Merb::Mailer.new :to => 'foo@bar.com',
58
+ :from => 'bar@foo.com',
59
+ :subject => 'Welcome to whatever!',
60
+ :body => partial(:sometemplate)
61
+ m.deliver!
62
+
61
63
  =end
@@ -2,11 +2,17 @@ module Merb
2
2
 
3
3
  class Request
4
4
  attr_accessor :env
5
- def initialize(env, method)
5
+ def initialize(env, method, request)
6
6
  @env = env
7
7
  @method = method
8
+ @request = request
8
9
  end
9
10
 
11
+ def raw_post
12
+ @request.rewind
13
+ @request.read
14
+ end
15
+
10
16
  # returns true if the request is an ajax request.
11
17
  def xml_http_request?
12
18
  not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil?
@@ -148,9 +154,7 @@ module Merb
148
154
 
149
155
  # create predicate methods for querying the REQUEST_METHOD
150
156
  [:get, :post, :put, :delete, :head].each do |m|
151
- eval %{
152
- def #{m}?; method == :#{m}; end
153
- }
157
+ define_method("#{m}?") { method == m.to_sym }
154
158
  end
155
159
 
156
160
  end
@@ -1,97 +1,248 @@
1
1
  module Merb
2
2
 
3
- # Merb::RouteMatcher is the request routing mapper for the merb framework.
4
- # You can define placeholder parts of the url with the :symbol notation.
5
- # so r.add '/foo/:bar/baz/:id', :class => 'Bar', :method => 'foo'
6
- # will match against a request to /foo/123/baz/456. It will then
7
- # use the class Bar as your merb controller and call the foo method on it.
8
- # the foo method will recieve a hash with {:bar => '123', :id => '456'}
9
- # as the content. So the :placeholders sections of your routes become
10
- # a hash of arguments to your controller methods. This route maps
11
- # the default /controller/action and /controller/action/id urls:
12
- # r.add '/:class/:method/:id'
13
- class RouteMatcher
3
+ class Router
14
4
 
15
- attr_accessor :sections
16
- @@section_regexp = /(?::([a-z*_]+))/.freeze
5
+ SECTION_REGEXP = /(?::([a-z*_]+))/.freeze
17
6
 
18
- # setup the router and yield it out to
19
- # add routes to it. Then compile all routes
20
- def self.prepare
21
- @@routes = Array.new
22
- @@compiled_statement = String.new
23
- @@compiled_regexen = Array.new
24
- yield self
25
- compile_router
26
- end
7
+ class << self
8
+
9
+ def prepare(&block)
10
+ @@matcher = RouteMatcher.new
11
+ @@generator = RouteGenerator.new
12
+
13
+ yield self
14
+
15
+ @@matcher.compile_router
16
+ end
17
+
18
+ def add(*route)
19
+ @@matcher.add(*route)
20
+ end
21
+
22
+ def matcher
23
+ @@matcher
24
+ end
25
+
26
+ def generator
27
+ @@generator
28
+ end
29
+
30
+ def match(path)
31
+ @@matcher.route_request(path)
32
+ end
33
+
34
+ def generate(method, options = {})
35
+ @@generator.generate(method, options)
36
+ end
37
+
38
+ def resources(res, opts={})
39
+ opts[:prefix] ||= ""
40
+ if block_given?
41
+ procs = []
42
+ yield Resource.new(res, procs, opts)
43
+ procs.reverse.each &:call
44
+ else
45
+ generate_resources_routes(res,opts)
46
+ end
47
+ end
48
+
49
+ def resource(res, opts={})
50
+ opts[:prefix] ||= ""
51
+ if block_given?
52
+ procs = []
53
+ yield Resource.new(res, procs, opts)
54
+ procs.reverse.each &:call
55
+ else
56
+ generate_singleton_routes(res,opts)
57
+ end
58
+ end
59
+
60
+ def default_routes
61
+ @@matcher.default_routes
62
+ end
63
+
64
+ def compiled_statement
65
+ @@matcher.compiled_statement
66
+ end
67
+
68
+ def compiled_regexen
69
+ @@matcher.compiled_regexen
70
+ end
71
+
72
+ def generate_resources_routes(res, opts)
73
+ [@@matcher,@@generator].each { |r| r.generate_resources_routes(res, opts) }
74
+ end
75
+
76
+ def generate_singleton_routes(res, opts)
77
+ [@@matcher,@@generator].each { |r| r.generate_singleton_routes(res, opts) }
78
+ end
27
79
 
28
- # the final compiled lambda that gets used
29
- # as the body of the route_request method.
30
- def self.compiled_statement
31
- @@compiled_statement
32
- end
33
-
34
- def self.compiled_regexen
35
- @@compiled_regexen
36
- end
80
+ end # class << self
37
81
 
38
- def compiled_statement
39
- @@compiled_statement
40
- end
41
-
42
- # add a route to be compiled
43
- def self.add(*route)
44
- @@routes << [route[0], (route[1] || {})]
82
+ class RouteMatcher
83
+
84
+ def initialize
85
+ @routes = Array.new
86
+ @compiled_statement = String.new
87
+ @compiled_regexen = Array.new
88
+ end
89
+
90
+ # the final compiled lambda that gets used
91
+ # as the body of the route_request method.
92
+ def compiled_statement
93
+ @compiled_statement
94
+ end
95
+
96
+ def compiled_regexen
97
+ @compiled_regexen
98
+ end
99
+
100
+ # add a route to be compiled
101
+ def add(*route)
102
+ @routes << [route[0], (route[1] || {})]
103
+ end
104
+
105
+ # build up a string that defines a lambda
106
+ # that does a case statement on the PATH_INFO
107
+ # against each of the compiled routes in turn.
108
+ # first route that matches wins.
109
+ def compile_router
110
+ router_lambda = @routes.inject("lambda{|path| \n sections={}\n case path\n") { |m,r|
111
+ m << compile(r)
112
+ } << " else\n return {:controller=>'Noroutefound', :action=>'noroute'}\n end\n}"
113
+ @compiled_statement = router_lambda
114
+ meta_def(:route_request, &eval(router_lambda))
115
+ end
116
+
117
+ # compile each individual route into a when /.../
118
+ # component of the case statement. Takes /:sections
119
+ # of the route def that start with : and turns them
120
+ # into placeholders for whatever urls match against
121
+ # the route in question.
122
+ def compile(route)
123
+ raise ArgumentError unless String === route[0]
124
+ code, count = '', 0
125
+ while route[0] =~ Router::SECTION_REGEXP
126
+ route[0] = route[0].dup
127
+ name = $1
128
+ (name =~ /(\*+)(\w+)/) ? (flag = true; name = $2) : (flag = false)
129
+ count += 1
130
+ if flag
131
+ route[0].sub!(Router::SECTION_REGEXP, "([^,?]+)")
132
+ else
133
+ route[0].sub!(Router::SECTION_REGEXP, "([^\/,?]+)")
134
+ end
135
+ code << " sections[:#{name}] = $#{count}\n"
136
+ end
137
+ @compiled_regexen << Regexp.new(route[0])
138
+ index = @compiled_regexen.size - 1
139
+ condition = " when @compiled_regexen[#{index}] "
140
+ statement = "#{condition}\n#{code}"
141
+ statement << " return #{route[1].inspect}.update(sections)\n"
142
+ statement
143
+ end
144
+
145
+ def generate_resources_routes(res,opt)
146
+ with_options :controller => res.to_s, :rest => true do |r|
147
+ r.add "#{opt[:prefix]}/#{res}/:id[;/]edit", :allowed => {:get => 'edit'}
148
+ r.add "#{opt[:prefix]}/#{res}/new[;/]:action", :allowed => {:get => 'new', :post => 'new', :put => 'new', :delete => 'new'}
149
+ r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'}
150
+ if mem = opt[:member]
151
+ mem.keys.sort_by{|x| "#{x}"}.each {|action|
152
+ allowed = mem[action].injecting({}) {|h, verb| h[verb] = "#{action}"}
153
+ r.add "#{opt[:prefix]}/#{res}/:id[;/]+#{action}", :allowed => allowed
154
+ }
155
+ end
156
+ if coll = opt[:collection]
157
+ coll.keys.sort_by{|x| "#{x}"}.each {|action|
158
+ allowed = coll[action].injecting({}) {|h, verb| h[verb] = "#{action}"}
159
+ r.add "#{opt[:prefix]}/#{res}[;/]#{action}", :allowed => allowed
160
+ }
161
+ end
162
+ r.add "#{opt[:prefix]}/#{res}/:id\\.:format", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'}
163
+ r.add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'index', :post => 'create'}
164
+ r.add "#{opt[:prefix]}/#{res}/:id", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'}
165
+ r.add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'index', :post => 'create'}
166
+ end
167
+ end
168
+
169
+ def generate_singleton_routes(res,opt)
170
+ with_options :controller => res.to_s, :rest => true do |r|
171
+ r.add "#{opt[:prefix]}/#{res}[;/]edit", :allowed => {:get => 'edit'}
172
+ r.add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'show'}
173
+ r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'}
174
+ r.add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'show', :post => 'create', :put => 'update', :delete => 'destroy'}
175
+ end
176
+ end
177
+
178
+ def default_routes
179
+ add "/:controller/:action/:id\\.:format"
180
+ add "/:controller/:action/:id"
181
+ add "/:controller/:action\\.:format"
182
+ add "/:controller/:action"
183
+ add "/:controller\\.:format", :action => 'index'
184
+ add "/:controller", :action => 'index'
185
+ end
45
186
  end
46
-
47
- # build up a string that defines a lambda
48
- # that does a case statement on the PATH_INFO
49
- # against each of the compiled routes in turn.
50
- # first route that matches wins.
51
- def self.compile_router
52
- router_lambda = @@routes.inject("lambda{|path| \n sections={}\n case path\n") { |m,r|
53
- m << compile(r)
54
- } <<" else\n return {:controller=>'Noroutefound', :action=>'noroute'}\n end\n}"
55
- @@compiled_statement = router_lambda
56
- meta_def(:route_request, &eval(router_lambda))
57
- end
58
-
59
- # compile each individual route into a when /.../
60
- # component of the case statement. Takes /:sections
61
- # of the route def that start with : and turns them
62
- # into placeholders for whatever urls match against
63
- # the route in question.
64
- def self.compile(route)
65
- raise ArgumentError unless String === route[0]
66
- code, count = '', 0
67
- while route[0] =~ @@section_regexp
68
- route[0] = route[0].dup
69
- name = $1
70
- (name =~ /(\*+)(\w+)/) ? (flag = true; name = $2) : (flag = false)
71
- count += 1
72
- if flag
73
- route[0].sub!(@@section_regexp, "([^,?]+)")
74
- else
75
- route[0].sub!(@@section_regexp, "([^\/,?]+)")
187
+
188
+ class RouteGenerator
189
+
190
+ attr_accessor :paths
191
+
192
+ def initialize
193
+ @paths = {}
194
+ end
195
+
196
+ def add(name, path)
197
+ name = name.intern unless Symbol === name
198
+ @paths[name] = path
199
+ end
200
+
201
+ def generate(name, options = {})
202
+ path = @paths[name]
203
+ while path =~ Router::SECTION_REGEXP
204
+ path.sub!(Router::SECTION_REGEXP, options[$~[1].intern].to_s)
205
+ end
206
+ if f = options[:format]
207
+ "#{path}.#{f}"
208
+ else
209
+ path
210
+ end
211
+ end
212
+
213
+ def generate_singleton_routes(res,opt)
214
+ add "edit_#{res}", "#{opt[:prefix]}/#{res}/edit"
215
+ add "new_#{res}","#{opt[:prefix]}/#{res}/new"
216
+ add res, "#{opt[:prefix]}/#{res}"
217
+ end
218
+
219
+ def generate_resources_routes(res,opt)
220
+ res_singular = res.to_s.singularize
221
+ add res, "#{opt[:prefix]}/#{res}"
222
+ add res_singular, "#{opt[:prefix]}/#{res}/:id"
223
+ add "new_#{res_singular}", "#{opt[:prefix]}/#{res}/new"
224
+ add "custom_new_#{res_singular}", "#{opt[:prefix]}/#{res}/new/:action"
225
+ add "edit_#{res_singular}", "#{opt[:prefix]}/#{res}/:id/edit"
226
+ if mem = opt[:member]
227
+ mem.keys.sort_by{|x| "#{x}"}.each {|action|
228
+ add "#{action}_#{res_singular}", "#{opt[:prefix]}/#{res}/:id/#{action}"
229
+ }
230
+ end
231
+ if coll = opt[:collection]
232
+ coll.keys.sort_by{|x| "#{x}"}.each {|action|
233
+ add "#{action}_#{res_singular}", "#{opt[:prefix]}/#{res}/#{action}"
234
+ }
76
235
  end
77
- code << " sections[:#{name}] = $#{count}\n"
78
- end
79
- @@compiled_regexen << Regexp.new(route[0])
80
- index = @@compiled_regexen.size - 1
81
- condition = " when @@compiled_regexen[#{index}] "
82
- statement = "#{condition}\n#{code}"
83
- statement << " return #{route[1].inspect}.update(sections)\n"
84
- statement
236
+ end
85
237
  end
86
-
238
+
87
239
  class Resource
88
- # TODO : come up with naming convention and generate helper
89
- # methods for route generation. Route generation
90
- # framework needed but needs to be simple and light
91
240
 
92
241
  def initialize(resource, procs=[], opts={})
93
242
  @resource, @procs, @opts = resource, procs, opts
94
- @procs << proc { ::Merb::RouteMatcher.generate_resources_routes(@resource, @opts) }
243
+ @procs << proc do
244
+ [::Merb::Router.matcher, ::Merb::Router.generator].each {|r| r.generate_resources_routes(@resource, @opts) }
245
+ end
95
246
  end
96
247
 
97
248
  def resources(res, opts={})
@@ -101,7 +252,9 @@ module Merb
101
252
  if block_given?
102
253
  yield self.class.new(res, @procs, opts)
103
254
  else
104
- @procs << proc { ::Merb::RouteMatcher.generate_resources_routes(res, opts) }
255
+ @procs << proc do
256
+ [::Merb::Router.matcher, ::Merb::Router.generator].each {|r| r.generate_resources_routes(res, opts) }
257
+ end
105
258
  end
106
259
  end
107
260
 
@@ -112,76 +265,11 @@ module Merb
112
265
  if block_given?
113
266
  yield self.class.new(res, @procs, opts)
114
267
  else
115
- @procs << proc { ::Merb::RouteMatcher.generate_singleton_routes(res, opts) }
116
- end
117
- end
118
- end
119
-
120
- # add a resource to be compiled for rest style dispatch
121
- def self.resources(res, opts={})
122
- opts[:prefix] ||= ""
123
- if block_given?
124
- procs = []
125
- yield Resource.new(res, procs, opts)
126
- procs.reverse.each &:call
127
- else
128
- generate_resources_routes(res,opts)
129
- end
130
- end
131
-
132
- # add a resource to be compiled for rest style dispatch
133
- def self.resource(res, opts={})
134
- opts[:prefix] ||= ""
135
- if block_given?
136
- procs = []
137
- yield Resource.new(res, procs, opts)
138
- procs.reverse.each &:call
139
- else
140
- generate_singleton_routes(res,opts)
141
- end
142
- end
143
-
144
- def self.generate_resources_routes(res,opt)
145
- with_options :controller => res.to_s, :rest => true do |r|
146
- r.add "#{opt[:prefix]}/#{res}/:id[;/]+edit", :allowed => {:get => 'edit'}
147
- r.add "#{opt[:prefix]}/#{res}/new[;/]+:action", :allowed => {:get => 'new', :post => 'new', :put => 'new', :delete => 'new'}
148
- r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'}
149
- if mem = opt[:member]
150
- mem.keys.sort_by{|x| "#{x}"}.each {|action|
151
- allowed = mem[action].injecting({}) {|h, verb| h[verb] = "#{action}"}
152
- r.add "#{opt[:prefix]}/#{res}/:id[;/]+#{action}", :allowed => allowed
153
- }
268
+ @procs << proc do
269
+ [::Merb::Router.matcher, ::Merb::Router.generator].each { |r| r.generate_singleton_routes(res, opts) }
270
+ end
154
271
  end
155
- if coll = opt[:collection]
156
- coll.keys.sort_by{|x| "#{x}"}.each {|action|
157
- allowed = coll[action].injecting({}) {|h, verb| h[verb] = "#{action}"}
158
- r.add "#{opt[:prefix]}/#{res}[;/]+#{action}", :allowed => allowed
159
- }
160
- end
161
- r.add "#{opt[:prefix]}/#{res}/:id\\.:format", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'}
162
- r.add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'index', :post => 'create'}
163
- r.add "#{opt[:prefix]}/#{res}/:id", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'}
164
- r.add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'index', :post => 'create'}
165
272
  end
166
273
  end
167
-
168
- def self.generate_singleton_routes(res,opt)
169
- with_options :controller => res.to_s, :rest => true do |r|
170
- r.add "#{opt[:prefix]}/#{res}[;/]+edit", :allowed => {:get => 'edit'}
171
- r.add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'show'}
172
- r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'}
173
- r.add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'show', :post => 'create', :put => 'update', :delete => 'destroy'}
174
- end
175
- end
176
-
177
- def self.default_routes
178
- add "/:controller/:action/:id\\.:format"
179
- add "/:controller/:action/:id"
180
- add "/:controller/:action\\.:format"
181
- add "/:controller/:action"
182
- add "/:controller\\.:format", :action => 'index'
183
- add "/:controller", :action => 'index'
184
- end
185
274
  end
186
-
187
275
  end
@@ -91,6 +91,10 @@ module Merb
91
91
  options[:environment] = env
92
92
  end
93
93
 
94
+ opts.on("-r", "--script-runner ['RUBY CODE'| FULL_SCRIPT_PATH]", "Command-line option to run scripts and/or code in the merb app") do |stuff_to_run|
95
+ options[:runner] = stuff_to_run
96
+ end
97
+
94
98
  opts.on("-g", "--generate-app PATH", "Generate a fresh merb app at PATH") do |path|
95
99
  options[:generate] = path || Dir.pwd
96
100
  end
@@ -102,8 +106,8 @@ module Merb
102
106
  opts.on("-M", "--merb-config FILENAME", "This flag is for explicitly declaring the merb app's config file") do |config|
103
107
  options[:merb_config] = config
104
108
  end
105
-
106
- opts.on("-X", "--mutex on/off", "This flag is for turnhing on and off the mutex lock.") do |mutex|
109
+
110
+ opts.on("-X", "--mutex on/off", "This flag is for turning the mutex lock on and off.") do |mutex|
107
111
  if mutex == 'off'
108
112
  options[:use_mutex] = false
109
113
  else
@@ -191,6 +195,15 @@ module Merb
191
195
 
192
196
  if @@merb_opts[:console]
193
197
  initialize_merb
198
+ Object.class_eval do
199
+ def show_routes
200
+ Merb::Router.generator.paths.each {|p| puts p.inspect}
201
+ nil
202
+ end
203
+ def url(path, o={})
204
+ Merb::Router.generator.generate(path,o)
205
+ end
206
+ end
194
207
  ARGV.clear # Avoid passing args to IRB
195
208
  require 'irb'
196
209
  require 'irb/completion'
@@ -203,6 +216,17 @@ module Merb
203
216
  IRB.start
204
217
  exit!
205
218
  end
219
+
220
+ if @@merb_opts[:runner]
221
+ initialize_merb
222
+ code_or_file = @@merb_opts[:runner]
223
+ if File.exists?(code_or_file)
224
+ eval(File.read(code_or_file))
225
+ else
226
+ eval(code_or_file)
227
+ end
228
+ exit!
229
+ end
206
230
 
207
231
  if @@merb_opts[:start_drb]
208
232
  puts "Starting merb drb server on port: #{@@merb_opts[:drb_server_port]}"
@@ -220,7 +244,6 @@ module Merb
220
244
  delete_pidfiles(@@merb_opts[:port])
221
245
  start(@@merb_opts[:port])
222
246
  else
223
- initialize_merb
224
247
  trap('TERM') { exit }
225
248
  mongrel_start(@@merb_opts[:port])
226
249
  end
@@ -3,9 +3,22 @@ require File.dirname(__FILE__)+'/mixins/view_context_mixin'
3
3
  require File.dirname(__FILE__)+'/mixins/form_control_mixin'
4
4
 
5
5
  module Merb
6
- PROTECTED_IVARS = %w[@cookies @session @headers @params
7
- @env @in @status @root @method @request @fingerprint_before
8
- @_new_cookie @tmpl_ext_cache @body]
6
+ PROTECTED_IVARS = %w[@_new_cookie
7
+ @method
8
+ @env
9
+ @body
10
+ @_fingerprint_before
11
+ @pager
12
+ @session
13
+ @headers
14
+ @page
15
+ @cookies
16
+ @request
17
+ @status
18
+ @_view_context_cache
19
+ @response
20
+ @params]
21
+
9
22
  module GlobalHelper
10
23
  end
11
24
  # the ViewContext is really
@@ -19,6 +32,7 @@ module Merb
19
32
  include Merb::GlobalHelper
20
33
 
21
34
  def initialize(controller)
35
+ @_merb_partial_locals = {}
22
36
  @controller = controller
23
37
  (@controller.instance_variables - PROTECTED_IVARS).each do |ivar|
24
38
  self.instance_variable_set(ivar, @controller.instance_variable_get(ivar))
@@ -43,7 +57,7 @@ module Merb
43
57
  alias_method :old_respond_to?, :respond_to?
44
58
 
45
59
  def respond_to?(sym, include_private=false)
46
- old_respond_to?(sym, include_private) || @controller.respond_to?(sym, include_private)
60
+ old_respond_to?(sym, include_private) || @controller.respond_to?(sym, include_private) || @_merb_partial_locals.key?(sym)
47
61
  end
48
62
 
49
63
  # catch any method calls that the controller responds to
@@ -51,6 +65,8 @@ module Merb
51
65
  def method_missing(sym, *args, &blk)
52
66
  if @controller.respond_to? sym
53
67
  @controller.send(sym, *args, &blk)
68
+ elsif @_merb_partial_locals.key? sym
69
+ @_merb_partial_locals[sym]
54
70
  else
55
71
  super
56
72
  end
@@ -18,39 +18,46 @@ module Merb
18
18
  status = input.read(boundary_size)
19
19
  raise EOFError, "bad content body" unless status == boundary + EOL
20
20
  rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
21
-
22
21
  loop {
23
22
  head = nil
24
23
  body = ''
25
24
  filename = content_type = name = nil
26
-
25
+ read_size = 0
27
26
  until head && buf =~ rx
28
- if !head && i = buf.index("\r\n\r\n")
27
+ i = buf.index("\r\n\r\n")
28
+ if( i == nil && read_size == 0 && content_length == 0 )
29
+ content_length = -1
30
+ break
31
+ end
32
+ if !head && i
29
33
  head = buf.slice!(0, i+2) # First \r\n
30
34
  buf.slice!(0, 2) # Second \r\n
31
-
32
35
  filename = head[FILENAME_REGEX, 1]
33
36
  content_type = head[CONTENT_TYPE_REGEX, 1]
34
37
  name = head[NAME_REGEX, 1]
35
38
 
36
- if filename && !filename.empty?
39
+ if filename && !filename.empty?
37
40
  body = Tempfile.new(:Merb)
38
41
  body.binmode if defined? body.binmode
39
42
  end
40
43
  next
41
44
  end
42
-
45
+
46
+
43
47
  # Save the read body part.
44
48
  if head && (boundary_size+4 < buf.size)
45
49
  body << buf.slice!(0, buf.size - (boundary_size+4))
46
50
  end
47
51
 
48
- c = input.read(bufsize < content_length ? bufsize : content_length)
49
- raise EOFError, "bad content body" if c.nil? || c.empty?
50
- buf << c
51
- content_length -= c.size
52
+ read_size = bufsize < content_length ? bufsize : content_length
53
+ if( read_size > 0 )
54
+ c = input.read(read_size)
55
+ raise EOFError, "bad content body" if c.nil? || c.empty?
56
+ buf << c
57
+ content_length -= c.size
58
+ end
52
59
  end
53
-
60
+
54
61
  # Save the rest.
55
62
  if i = buf.index(rx)
56
63
  body << buf.slice!(0, i)
@@ -58,7 +65,7 @@ module Merb
58
65
 
59
66
  content_length = -1 if $1 == "--"
60
67
  end
61
-
68
+
62
69
  if filename && !filename.empty?
63
70
  body.rewind
64
71
  data = {
@@ -92,6 +99,10 @@ module Merb
92
99
  parms
93
100
  end
94
101
 
102
+ def url(path, o={})
103
+ ::Merb::Router.generator.generate(path,o)
104
+ end
105
+
95
106
  # parses a query string or the payload of a POST
96
107
  # request into the params hash. So for example:
97
108
  # /foo?bar=nik&post[title]=heya&post[body]=whatever
@@ -246,4 +257,4 @@ module Merb
246
257
  end
247
258
 
248
259
  end
249
- end
260
+ end
@@ -41,6 +41,11 @@ module Merb
41
41
  text(o)
42
42
  end
43
43
 
44
+ def hidden(o)
45
+ o.value ||= ""
46
+ %{<input type="hidden" name="#{o.name}" value="#{o.value}" #{o.html ? o.html.map{|k,v| "#{k}=\"#{v}\""}.join(' ') : nil}/>}
47
+ end
48
+
44
49
  def text(o)
45
50
  o.value ||= ""
46
51
  tag = ''
@@ -27,6 +27,9 @@ module Merb
27
27
  # render :nothing => 200
28
28
  # renders nothing with a status of 200
29
29
  #
30
+ # render :template => 'shared/message'
31
+ # renders views/shared/message
32
+ #
30
33
  # render :js => "$('some-div').toggle();"
31
34
  # if the right hand side of :js => is a string then the proper
32
35
  # javascript headers will be set and the string will be returned
@@ -57,6 +60,13 @@ module Merb
57
60
  when partial = opts[:partial]
58
61
  template = find_partial(partial, opts)
59
62
  opts[:layout] = :none
63
+
64
+ # Add an instance variable that can be used to create the locals in the
65
+ # partial
66
+ if opts[:locals]
67
+ @_merb_partial_locals = opts[:locals]
68
+ end
69
+ opts[:clean_context] = true
60
70
  when js = opts[:js]
61
71
  headers['Content-Type'] = "text/javascript"
62
72
  opts[:layout] = :none
@@ -71,14 +81,16 @@ module Merb
71
81
  headers['Content-Type'] = 'application/xml'
72
82
  headers['Encoding'] = 'UTF-8'
73
83
  return xml
74
- else
84
+ when template = opts[:template]
85
+ template = find_template(:template => template)
86
+ else
75
87
  template = find_template(:action => action)
76
88
  end
77
89
 
78
90
  engine = engine_for(template)
79
91
  options = {
80
92
  :file => template,
81
- :view_context => (opts[:clean_context] ? clean_view_context : _view_context),
93
+ :view_context => (opts[:clean_context] ? clean_view_context : cached_view_context),
82
94
  :opts => opts
83
95
  }
84
96
  content = engine.transform(options)
@@ -92,7 +104,7 @@ module Merb
92
104
  # this returns a ViewContext object populated with all
93
105
  # the instance variables in your controller. This is used
94
106
  # as the view context object for the Erubis templates.
95
- def _view_context
107
+ def cached_view_context
96
108
  @_view_context_cache ||= ViewContext.new(self)
97
109
  end
98
110
 
@@ -129,8 +141,8 @@ module Merb
129
141
  # you can render partials in other view directories. So
130
142
  # if you create a views/shared directory then you can call
131
143
  # partials that live there like partial('shared/foo')
132
- def partial(template)
133
- render :partial => template
144
+ def partial(template, locals={})
145
+ render :partial => template, :locals => locals
134
146
  end
135
147
 
136
148
  private
@@ -146,11 +158,11 @@ module Merb
146
158
  end
147
159
  end
148
160
 
149
- _view_context.instance_variable_set('@_layout_content', content)
161
+ cached_view_context.instance_variable_set('@_layout_content', content)
150
162
  engine = engine_for(layout_choice)
151
163
  options = {
152
164
  :file => layout_choice,
153
- :view_context => _view_context,
165
+ :view_context => cached_view_context,
154
166
  :opts => opts
155
167
  }
156
168
  engine.transform(options)
@@ -158,14 +170,15 @@ module Merb
158
170
 
159
171
  # OPTIMIZE : combine find_template and find_partial ?
160
172
  def find_template(opts={})
161
- if action = opts[:action]
162
- path =
163
- File.expand_path(MERB_ROOT / "dist/app/views" / self.class.name.snake_case / action)
173
+ if template = opts[:template]
174
+ path = _template_root / template
175
+ elsif action = opts[:action]
176
+ path = _template_root / self.class.name.snake_case / action
164
177
  elsif _layout = opts[:layout]
165
178
  path = _layout_root / _layout
166
179
  else
167
180
  raise "called find_template without an :action or :layout"
168
- end
181
+ end
169
182
  extensions = [_template_extensions.keys].flatten.uniq
170
183
  glob = "#{path}.{#{opts[:ext] || extensions.join(',')}}"
171
184
  Dir[glob].first
@@ -32,7 +32,7 @@ module Merb
32
32
  %{<img src="#{opts.delete(:path) + img}" #{opts.map{|k,v| "#{k}=\"#{v}\""}.join(' ')} />}
33
33
  end
34
34
 
35
- # calls .to_json on data. This will use fjson if installed
35
+ # calls .to_json on data.
36
36
  # so it can be faster than escape_js
37
37
  def js(data)
38
38
  if data.respond_to? :to_json
@@ -27,9 +27,6 @@ module Merb
27
27
 
28
28
  class << self
29
29
 
30
- class_inheritable_accessor :haml_options
31
- self.haml_options = { :locals => {} }
32
-
33
30
  @@hamls ||= {}
34
31
  @@mtimes ||= {}
35
32
 
@@ -39,16 +36,14 @@ module Merb
39
36
 
40
37
  def transform(options = {})
41
38
  opts, file, view_context = options.values_at(:opts, :file, :view_context)
42
- opts = haml_options.merge(opts)
39
+
43
40
  begin
44
- if precompiled = get_precompiled(file)
45
- opts[:precompiled] ||= precompiled
46
- haml = ::Haml::Engine.new("", opts)
47
- else
48
- haml = ::Haml::Engine.new(IO.read(file), opts)
49
- set_precompiled(file, haml.precompiled)
50
- end
51
-
41
+ locals_code = build_locals(opts[:locals])
42
+ opts[:locals] = {}
43
+
44
+ template = load_template(file)
45
+
46
+ haml = ::Haml::Engine.new("#{locals_code}#{template}", opts)
52
47
  haml.to_html(view_context)
53
48
  rescue
54
49
  # ::Haml::Engine often inserts a bogus "(haml):#{line_number}" entry in the backtrace.
@@ -59,26 +54,37 @@ module Merb
59
54
  end
60
55
 
61
56
  private
57
+ def load_template(file)
58
+ template = ""
59
+ if @@hamls[file] && !cache_template?(file)
60
+ template = @@hamls[file]
61
+ else
62
+ template = IO.read(file)
63
+ if cache_template?(file)
64
+ @@hamls[file], @@mtimes[file] = template, Time.now
65
+ end
66
+ return template
67
+ end
68
+ end
69
+
70
+ def build_locals(locals)
71
+ locals_code = ""
72
+ if locals
73
+ locals_code = locals.keys.inject("") do |code, key|
74
+ code << "- #{key} = @_merb_partial_locals[:#{key}]\n"
75
+ end
76
+ end
77
+ end
62
78
 
63
- def get_precompiled(path, opts={})
64
- if @@hamls[path] && !cache_template?(path)
65
- return @@hamls[path]
66
- end
67
- end
68
-
69
- def set_precompiled(path, precompiled)
70
- @@hamls[path], @@mtimes[path] = precompiled, Time.now
71
- end
72
-
73
- def cache_template?(path)
74
- return false unless ::Merb::Server.config[:cache_templates]
75
- return true unless @@hamls[path]
76
- @@mtimes[path] < File.mtime(path) ||
77
- (File.symlink?(path) && (@@mtimes[path] < File.lstat(path).mtime))
78
- end
79
+ def cache_template?(path)
80
+ return false unless ::Merb::Server.config[:cache_templates]
81
+ return true unless @@hamls[path]
82
+ @@mtimes[path] < File.mtime(path) ||
83
+ (File.symlink?(path) && (@@mtimes[path] < File.lstat(path).mtime))
84
+ end
79
85
 
80
86
  end
81
87
 
82
88
  end
83
89
  end
84
- end
90
+ end
@@ -33,8 +33,15 @@ module Merb
33
33
  # mab is just ruby, there's no two phase compile and run
34
34
  def transform(options = {})
35
35
  opts, file, view_context = options.values_at(:opts, :file, :view_context)
36
+
37
+ if opts[:locals]
38
+ locals = opts[:locals].keys.inject("") do |code, key|
39
+ code << "#{key} = @_merb_partial_locals[:#{key}]\n"
40
+ end
41
+ end
42
+
36
43
  mab = ::Markaby::Builder.new({}, view_context) {
37
- instance_eval(File.read(file))
44
+ instance_eval("#{locals}#{File.read(file)}")
38
45
  }
39
46
  mab.to_s
40
47
  end
data/lib/tasks/merb.rake CHANGED
@@ -21,4 +21,12 @@ namespace :merb do
21
21
  puts " - #{MERB_ROOT / 'dist/framework'} (recursive) "
22
22
  puts " - #{MERB_ROOT / 'script/merb'}"
23
23
  end
24
+
25
+ desc "freeze the merb framework from svn"
26
+ task :freeze_from_svn do
27
+ puts "Freezing Merb Framework from svn"
28
+ FileUtils.rm_rf MERB_ROOT / 'dist/framework'
29
+ system "svn co http://svn.devjavu.com/merb/trunk/lib #{MERB_ROOT / 'dist/framework'}"
30
+ end
31
+
24
32
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: merb
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.3.1
7
- date: 2007-04-30 00:00:00 -07:00
6
+ version: 0.3.3
7
+ date: 2007-05-31 00:00:00 -07:00
8
8
  summary: Merb == Mongrel + Erb. Pocket rocket web framework.
9
9
  require_paths:
10
10
  - lib
@@ -175,3 +175,30 @@ dependencies:
175
175
  - !ruby/object:Gem::Version
176
176
  version: 0.0.0
177
177
  version:
178
+ - !ruby/object:Gem::Dependency
179
+ name: mime-types
180
+ version_requirement:
181
+ version_requirements: !ruby/object:Gem::Version::Requirement
182
+ requirements:
183
+ - - ">"
184
+ - !ruby/object:Gem::Version
185
+ version: 0.0.0
186
+ version:
187
+ - !ruby/object:Gem::Dependency
188
+ name: xml-simple
189
+ version_requirement:
190
+ version_requirements: !ruby/object:Gem::Version::Requirement
191
+ requirements:
192
+ - - ">"
193
+ - !ruby/object:Gem::Version
194
+ version: 0.0.0
195
+ version:
196
+ - !ruby/object:Gem::Dependency
197
+ name: archive-tar-minitar
198
+ version_requirement:
199
+ version_requirements: !ruby/object:Gem::Version::Requirement
200
+ requirements:
201
+ - - ">"
202
+ - !ruby/object:Gem::Version
203
+ version: 0.0.0
204
+ version: