nyara 0.1.pre.0 → 0.1.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/bin/nyara +2 -14
  3. data/changes +3 -0
  4. data/example/factorial.rb +19 -0
  5. data/example/hello.rb +5 -0
  6. data/example/project.rb +11 -0
  7. data/example/stream.rb +14 -0
  8. data/ext/extconf.rb +19 -0
  9. data/ext/hashes.c +160 -57
  10. data/ext/inc/ary_intern.h +36 -0
  11. data/ext/route.cc +2 -1
  12. data/ext/url_encoded.c +1 -0
  13. data/lib/nyara.rb +1 -0
  14. data/lib/nyara/command.rb +60 -79
  15. data/lib/nyara/config.rb +19 -1
  16. data/lib/nyara/controller.rb +64 -7
  17. data/lib/nyara/hashes/config_hash.rb +3 -20
  18. data/lib/nyara/hashes/header_hash.rb +2 -2
  19. data/lib/nyara/hashes/param_hash.rb +1 -0
  20. data/lib/nyara/nyara.rb +45 -37
  21. data/lib/nyara/part.rb +2 -2
  22. data/lib/nyara/reload.rb +63 -64
  23. data/lib/nyara/route.rb +7 -6
  24. data/lib/nyara/session.rb +4 -0
  25. data/lib/nyara/templates/{Gemfile → Gemfile.tt} +4 -0
  26. data/lib/nyara/templates/Linnerfile +28 -0
  27. data/lib/nyara/templates/Rakefile +16 -2
  28. data/lib/nyara/templates/app/assets/files/favicon.ico +1 -0
  29. data/lib/nyara/templates/app/assets/files/robots.txt +5 -0
  30. data/lib/nyara/templates/app/assets/scripts/app.coffee +4 -0
  31. data/lib/nyara/templates/app/assets/scripts/module-example.coffee +4 -0
  32. data/lib/nyara/templates/app/assets/styles/app.scss +2 -0
  33. data/lib/nyara/templates/app/controllers/application_controller.rb +8 -0
  34. data/lib/nyara/templates/app/views/layouts/application.erb.tt +12 -0
  35. data/lib/nyara/templates/config/application.rb +4 -0
  36. data/lib/nyara/templates/config/{database.yml → database.yml.tt} +3 -3
  37. data/lib/nyara/templates/config/production.rb +2 -0
  38. data/lib/nyara/view.rb +6 -2
  39. data/nyara.gemspec +3 -1
  40. data/rakefile +7 -26
  41. data/spec/apps/reload.rb +35 -0
  42. data/spec/command_spec.rb +24 -10
  43. data/spec/config_spec.rb +19 -8
  44. data/spec/controller_spec.rb +14 -0
  45. data/spec/evented_io_spec.rb +3 -1
  46. data/spec/ext_route_spec.rb +25 -3
  47. data/spec/hashes_spec.rb +45 -21
  48. data/spec/integration_spec.rb +28 -2
  49. data/spec/path_helper_spec.rb +7 -0
  50. data/spec/performance_spec.rb +1 -1
  51. data/spec/public/test.css +1 -0
  52. data/{lib/nyara/templates/public/robot.txt → spec/public/test.jpg} +0 -0
  53. data/spec/public/test.js +1 -0
  54. data/spec/reload_spec.rb +50 -0
  55. data/spec/route_spec.rb +4 -4
  56. data/spec/spec_helper.rb +15 -9
  57. data/spec/url_encoded_spec.rb +5 -0
  58. data/spec/view_spec.rb +7 -0
  59. data/spec/views/_partial.slim +1 -1
  60. data/tools/bug.rb +53 -0
  61. data/tools/hello.rb +49 -0
  62. data/tools/memcheck.rb +33 -0
  63. metadata +73 -41
  64. data/ext/inc/status_codes.inc +0 -64
  65. data/lib/nyara/templates/app/views/layouts/application.erb +0 -12
  66. data/lib/nyara/templates/public/css/app.css +0 -1
  67. data/lib/nyara/templates/public/js/app.js +0 -1
@@ -0,0 +1,36 @@
1
+ /* array internals, from ruby source code array.c */
2
+
3
+ #pragma once
4
+
5
+ # define ARY_SHARED_P(ary) \
6
+ (assert(!FL_TEST((ary), ELTS_SHARED) || !FL_TEST((ary), RARRAY_EMBED_FLAG)), \
7
+ FL_TEST((ary),ELTS_SHARED)!=0)
8
+ # define ARY_EMBED_P(ary) \
9
+ (assert(!FL_TEST((ary), ELTS_SHARED) || !FL_TEST((ary), RARRAY_EMBED_FLAG)), \
10
+ FL_TEST((ary), RARRAY_EMBED_FLAG)!=0)
11
+
12
+ #define ARY_SET_PTR(ary, p) do { \
13
+ assert(!ARY_EMBED_P(ary)); \
14
+ assert(!OBJ_FROZEN(ary)); \
15
+ RARRAY(ary)->as.heap.ptr = (p); \
16
+ } while (0)
17
+ #define ARY_SET_EMBED_LEN(ary, n) do { \
18
+ long tmp_n = (n); \
19
+ assert(ARY_EMBED_P(ary)); \
20
+ assert(!OBJ_FROZEN(ary)); \
21
+ RBASIC(ary)->flags &= ~RARRAY_EMBED_LEN_MASK; \
22
+ RBASIC(ary)->flags |= (tmp_n) << RARRAY_EMBED_LEN_SHIFT; \
23
+ } while (0)
24
+ #define ARY_SET_HEAP_LEN(ary, n) do { \
25
+ assert(!ARY_EMBED_P(ary)); \
26
+ RARRAY(ary)->as.heap.len = (n); \
27
+ } while (0)
28
+ #define ARY_SET_LEN(ary, n) do { \
29
+ if (ARY_EMBED_P(ary)) { \
30
+ ARY_SET_EMBED_LEN((ary), (n)); \
31
+ } \
32
+ else { \
33
+ ARY_SET_HEAP_LEN((ary), (n)); \
34
+ } \
35
+ assert(RARRAY_LEN(ary) == (n)); \
36
+ } while (0)
data/ext/route.cc CHANGED
@@ -254,7 +254,8 @@ RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath, VALUE a
254
254
  if (suffix_len) {
255
255
  r.format = extract_ext(suffix, suffix_len);
256
256
  if (r.format == Qnil) {
257
- break;
257
+ // suffix not match
258
+ continue;
258
259
  }
259
260
  }
260
261
  r.args = rb_ary_new3(1, i->id);
data/ext/url_encoded.c CHANGED
@@ -57,6 +57,7 @@ static long _decode_url_seg(VALUE output, const char*s, long len, char stop_char
57
57
 
58
58
  } else if (s[i] == '+') {
59
59
  FLUSH_UNESCAPED;
60
+ last_s++;
60
61
  rb_str_cat(output, " ", 1);
61
62
 
62
63
  } else {
data/lib/nyara.rb CHANGED
@@ -4,6 +4,7 @@ require_relative "nyara/nyara"
4
4
  END {
5
5
  if $!.nil? and !Nyara.config.test?
6
6
  Nyara.setup
7
+ Nyara.load_app
7
8
  Nyara.start_server
8
9
  end
9
10
  }
data/lib/nyara/command.rb CHANGED
@@ -1,105 +1,86 @@
1
- require 'optparse'
2
- module Nyara
3
- module Command
4
- extend self
1
+ require "thor"
2
+ require "shellwords"
5
3
 
6
- def help
7
- puts %Q(Usage:
8
- nyara new APP_NAME [options]
4
+ module Nyara
5
+ class Command < Thor
6
+ include Thor::Actions
7
+ map '-v' => :version
9
8
 
10
- commands:
11
- nyara help\t\t\tShow this message
12
- nyara new APP_NAME\t\tTo initialize a new project with default template in current directory.
13
- nyara version\t\t\tDisplay current version
14
- )
9
+ def self.source_root
10
+ __dir__
15
11
  end
16
12
 
13
+ desc "version", "Show version"
17
14
  def version
18
15
  puts "Nyara #{Nyara::VERSION}"
19
16
  end
20
17
 
21
- def new_project(*args)
22
- args ||= []
23
- opts = {
24
- force: false
25
- }
26
- OptionParser.new do |opt|
27
- opt.banner = 'Usage: nyara new APP_NAME [options]'
28
- opt.on('-f', 'Force override old') do
29
- opts[:force] = true
30
- end
31
- end.parse(args)
32
-
18
+ desc "new APP_NAME", "Create a project"
19
+ method_option :force, aliases: '-f', desc: 'Force override old', type: :boolean, default: false
20
+ def new name
33
21
  require 'fileutils'
34
- require "erb"
35
- require 'ostruct'
36
- require_relative "view_handlers/erb"
37
22
 
38
- name = args.shift
39
- if name.blank?
40
- puts "Need project name: \n\tnyara new xxx"
41
- return
42
- end
43
-
44
- app_dir = File.join(Dir.pwd, name)
23
+ app_dir = File.expand_path File.join(Dir.pwd, name)
24
+ @rel_dir = name
25
+ @app_name = File.basename app_dir
45
26
  templte_dir = File.join(File.dirname(__FILE__), "templates")
46
27
 
47
- FileUtils.rm_rf(app_dir) if opts[:force]
48
-
49
- if Dir.exist?(app_dir)
50
- puts "This has same dir name's '#{name}' existed, Nyara can not override it."
51
- return
52
- end
53
-
54
- Dir.mkdir(app_dir)
55
-
56
- puts "Generate Nyara project..."
57
- source_templates = Dir.glob("#{templte_dir}/*")
58
- puts source_templates.map{|f| File.basename f }
59
- FileUtils.cp_r(source_templates, app_dir)
28
+ FileUtils.rm_rf(app_dir) if options[:force]
29
+ directory 'templates', name
60
30
 
61
- # render template
62
- files = Dir.glob("#{app_dir}/**/*")
63
- render_opts = {
64
- app_name: name
65
- }
66
- files.each do |fname|
67
- if not File.directory?(fname)
68
- render_template(fname, render_opts)
69
- end
31
+ create_file app_dir + '/.gitignore' do
32
+ %w".DS_Store config/session.key config/session_cipher.key".join "\n"
70
33
  end
34
+ generate 'session.key'
35
+ puts ' \\ 👻 /'
36
+ ensure
37
+ @app_name = nil
38
+ @rel_dir = nil
39
+ end
71
40
 
72
- Dir.chdir app_dir do
73
- puts "config/session.key"
74
- File.open 'config/session.key', 'wb' do |f|
75
- f << Session.generate_key
41
+ desc "generate THING", "(PROJECT) Generate things, THING can be:
42
+ session.key # config/session.key
43
+ session_cipher.key # config/session_cipher.key"
44
+ def generate thing, app_dir=nil
45
+ case thing
46
+ when 'session.key'
47
+ file = "config/session.key"
48
+ file = File.join @rel_dir, file if @rel_dir
49
+ create_file file do
50
+ Session.generate_key
76
51
  end
77
-
78
- puts ".gitignore"
79
- File.open '.gitignore', 'w' do |f|
80
- f.puts ".DS_Store"
81
- f.puts "config/session.key"
52
+ when 'session_cipher.key'
53
+ file = 'config/session_cipher.key'
54
+ file = File.join @rel_dir, file if @rel_dir
55
+ create_file file do
56
+ Session.generate_cipher_key
82
57
  end
83
58
  end
84
-
85
- puts "Enjoy!"
86
59
  end
87
60
 
88
- def run_server(*args)
89
- args ||= []
90
- system("bundle exec ruby config/boot.rb")
61
+ desc "server", "(PROJECT) Start server"
62
+ method_option :environment, aliases: %w'-e -E', default: 'development'
63
+ def server
64
+ env = options[:environment].shellescape
65
+ exec "NYARA_ENV=#{env} ruby config/boot.rb"
91
66
  end
92
67
 
93
- private
94
- def render_template(fname, opts = {})
95
- renderer = ERB.new(File.read(fname))
96
- locals = {
97
- app_name: opts[:app_name],
98
- nyara_version: Nyara::VERSION
99
- }
100
- File.open(fname, 'w+') do |f|
101
- f.write renderer.result(OpenStruct.new(locals).instance_eval { binding })
68
+ desc "console", "(PROJECT) Start console"
69
+ method_option :environment, aliases: %w'-e -E', default: 'development'
70
+ method_option :shell, aliases: '-s', desc: "tell me which shell you want to use, pry or irb?"
71
+ def console
72
+ env = options[:environment].shellescape
73
+ cmd = options[:shell]
74
+ unless cmd
75
+ if File.read('Gemfile') =~ /\bpry\b/
76
+ cmd = 'pry'
77
+ end
78
+ end
79
+ cmd ||= 'irb'
80
+ if cmd != 'irb'
81
+ cmd = "bundle exec #{cmd}"
102
82
  end
83
+ exec "NYARA_ENV=#{env} #{cmd} -r./config/application.rb"
103
84
  end
104
85
 
105
86
  end
data/lib/nyara/config.rb CHANGED
@@ -7,11 +7,14 @@ module Nyara
7
7
  # * `host` - host name used in `url_to` helper
8
8
  # * `root` - root path, default is `Dir.pwd`
9
9
  # * `views` - views (templates) directory, relative to root, default is `"views"`
10
+ # * `assets` - assets directory, relative to root, default is `"assets"`
10
11
  # * `public` - static files directory, relative to root, default is `"public"`
11
12
  # * `x_send_file` - header field name for `X-Sendfile` or `X-Accel-Redirect`, see [Nyara::Controller#send_file](Controller#send_file.html-instance_method) for details
12
13
  # * `session` - see [Nyara::Session](Session.html) for sub options
13
14
  # * `prefer_erb` - use ERB instead of ERubis for `.erb` templates
14
15
  # * `logger` - if set, every request is logged, and you can use `Nyara.logger` to do your own logging.
16
+ # * `app_files` - application source file glob patterns, they will be required automatically.
17
+ # In developemnt mode, this option enables automatic reloading for views and app.
15
18
  # * `before_fork` - a proc to run before forking
16
19
  # * `after_fork` - a proc to run after forking
17
20
  #
@@ -51,13 +54,18 @@ module Nyara
51
54
  unless self['root']
52
55
  set :root, Dir.pwd
53
56
  end
54
- self['root'] = File.expand_path self['root']
57
+ self['root'] = File.realpath File.expand_path self['root']
55
58
 
59
+ # todo warn paths not under project?
56
60
  self['views'] = project_path(self['views'] || 'views')
57
61
  if self['public']
58
62
  self['public'] = project_path(self['public'])
59
63
  end
60
64
 
65
+ if self['assets']
66
+ self['assets'] = project_path(self['assets'])
67
+ end
68
+
61
69
  self.logger = create_logger
62
70
 
63
71
  assert !self['before_fork'] || self['before_fork'].respond_to?('call')
@@ -111,6 +119,16 @@ module Nyara
111
119
  path_under 'views', path, strict
112
120
  end
113
121
 
122
+ # Get absoute path under assets path
123
+ #
124
+ # #### Options
125
+ #
126
+ # * `strict` - return `nil` if path is not under the dir
127
+ #
128
+ def assets_path path, strict=true
129
+ path_under "assets", path, strict
130
+ end
131
+
114
132
  # Get path under the dir configured `Nyara.config[key]`
115
133
  #
116
134
  # #### Options
@@ -9,6 +9,11 @@ module Nyara
9
9
  # end
10
10
  #
11
11
  def http method, path, &blk
12
+ # special treatment: '/' also maps ''
13
+ if path == '/'
14
+ http method, '', &blk
15
+ end
16
+
12
17
  @routes ||= []
13
18
  @used_ids = {}
14
19
  method = method.to_s.upcase
@@ -164,6 +169,40 @@ module Nyara
164
169
  end
165
170
  end
166
171
 
172
+ def self.process_reload request, l
173
+ if request.http_method == 'POST' and request.path =~ /\A\/reload:([\w-]+)\z/
174
+ ty = $1
175
+ files = request.param['files']
176
+ case ty
177
+ when 'views-modified'
178
+ files.each do |f|
179
+ if l
180
+ l.info "modified: #{f}"
181
+ end
182
+ View.on_removed f
183
+ View.on_modified f
184
+ end
185
+ when 'views-removed'
186
+ files.each do |f|
187
+ if l
188
+ l.info "removed: #{f}"
189
+ end
190
+ View.on_removed f
191
+ end
192
+ when 'app-modified'
193
+ files.each do |f|
194
+ if l
195
+ l.info "modified: #{f}"
196
+ end
197
+ Reload.load_file f
198
+ end
199
+ else
200
+ return false
201
+ end
202
+ true
203
+ end
204
+ end
205
+
167
206
  def self.dispatch request, instance, args
168
207
  if cookie_str = request.header._aref('Cookie')
169
208
  ParamHash.parse_cookie request.cookie, cookie_str
@@ -172,25 +211,35 @@ module Nyara
172
211
  request.session = Session.decode(request.cookie)
173
212
  )
174
213
 
214
+ l = Nyara.logger
215
+
175
216
  if instance
176
- if l = Nyara.logger
217
+ if l
177
218
  l.info "#{request.http_method} #{request.path} => #{instance.class}"
219
+ if %W"POST PUT PATCH".include?(request.http_method)
220
+ l.info " params: #{instance.params.inspect}"
221
+ end
178
222
  end
179
223
  instance.send *args
180
224
  return
181
225
  elsif request.http_method == 'GET' and Config['public']
182
226
  path = Config.public_path request.path
183
227
  if File.file?(path)
184
- if l = Nyara.logger
185
- l.info "GET #{path} => public 200"
228
+ if l
229
+ l.info "GET #{request.path} => public 200"
186
230
  end
187
231
  instance = Controller.new request
188
232
  instance.send_file path
189
233
  return
190
234
  end
235
+ elsif Config.development?
236
+ if process_reload(request, l)
237
+ Ext.request_send_data request, "HTTP/1.1 200 OK\r\n\r\n"
238
+ return
239
+ end
191
240
  end
192
241
 
193
- if l = Nyara.logger
242
+ if l
194
243
  l.info "#{request.http_method} #{request.path} => 404"
195
244
  end
196
245
  Ext.request_send_data request, "HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"
@@ -207,6 +256,9 @@ module Nyara
207
256
  end
208
257
 
209
258
  template, meth = self.class.path_templates[id.to_s]
259
+ if template.blank? && meth.blank?
260
+ raise ArgumentError, "#{id} route not found."
261
+ end
210
262
  r = template % args
211
263
 
212
264
  if opts
@@ -273,8 +325,11 @@ module Nyara
273
325
  end
274
326
 
275
327
  # Shortcut for `redirect url_to *xs`
276
- def redirect_to *xs
277
- redirect url_to(*xs)
328
+ def redirect_to identifier, *xs
329
+ if identifier !~ /\A\w*#\w++(?:\-\w++)*\z/
330
+ raise ArgumentError, "not action identifier: #{identifier.inspect}, did you mean `redirect`?"
331
+ end
332
+ redirect url_to(identifier, *xs)
278
333
  end
279
334
 
280
335
  # Stop processing and close connection<br>
@@ -479,6 +534,8 @@ module Nyara
479
534
  unless content_type
480
535
  extname = File.extname(file)
481
536
  extname = File.extname(filename) if extname.blank? and filename
537
+ extname.gsub!(".","")
538
+
482
539
  content_type = MIME_TYPES[extname] || 'application/octet-stream'
483
540
  end
484
541
  header['Content-Type'] = content_type
@@ -526,7 +583,7 @@ module Nyara
526
583
 
527
584
  # Render a template as string
528
585
  def partial view_path, locals: nil
529
- view = View.new self, view_path, nil, nil, {}
586
+ view = View.new self, view_path, nil, locals, {}
530
587
  view.partial
531
588
  end
532
589
 
@@ -1,33 +1,16 @@
1
1
  module Nyara
2
2
  # Extended hash class for the use in configuration.
3
3
  class ConfigHash
4
- # @private
5
- alias _aref [] # :nodoc:
6
- # @private
7
- alias _aset []= # :nodoc:
8
-
9
4
  # #### Call-seq
10
5
  #
11
- # config['a', 'very', 'deep', 'key']
6
+ # config['a', 'very', 'deep', '', 'key']
12
7
  #
13
8
  # Equivalent to
14
9
  #
15
- # config['a']['very']['deep']['key'] rescue nil
10
+ # config['a']['very']['deep'].last['key'] rescue nil
16
11
  #
17
12
  def [] *keys
18
- h = self
19
- keys.each do |key|
20
- if h.has_key?(key)
21
- if h.is_a?(ConfigHash)
22
- h = h._aref key
23
- else
24
- h = h[key]
25
- end
26
- else
27
- return nil # todo default value?
28
- end
29
- end
30
- h
13
+ nested_aref keys.map(&:to_s)
31
14
  end
32
15
 
33
16
  # #### Call-seq