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

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.
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