waxx 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54a4e66466f3cfd8f9a3ff3b6cf240ac7388b69a19701d967230bdf269cae647
4
- data.tar.gz: 1192518c45703b44733d4b8bd641b6c1d1c1d48cbe3c6e47eaad5f2f92465d51
3
+ metadata.gz: d48f18168120d265fbad00f5c097e897fc2100475c2fc5c16ca2e0ac0fdd065a
4
+ data.tar.gz: 49cb992829fadcf39605e9052db7ff1d0ec10b06b0a76c1f84d7f9d6b5f85c5e
5
5
  SHA512:
6
- metadata.gz: 9b77c420784a8d95a6c1aa3a478b87dfb45808accd86d40cb43738b5d06ed91d5dec0540349060b5399df6a25f31a3d27b6b9b9d4cdba5e1396f4a37073115be
7
- data.tar.gz: c500352f171956dcec85d5b32aedfec96f35f6b98d0eb3b4dd883c23ccbf28946ec0ff720c4ef89f1287702865598988377d2029b2053d0ab5c9025fa10edc45
6
+ metadata.gz: 84f8791ac7b156387e1a4780ad3bf05e5f5ddb7ee1d4735f47ddd75c714452a1ccb0f0994becd324a80ded687b080e2ed01bf6d17eaf21de6ec39f97ae9cf00a
7
+ data.tar.gz: 1163ef8ad6da886481ea0df6d02c32357ef49c380105f487e320a33822ac4e9f460b0a801707e386f5d127f15a560819c270f5047f96c04a8206bd2a3cb966d3
data/README.md CHANGED
@@ -80,11 +80,11 @@ You specify the number of threads in the config file.
80
80
  Each thread is prespawned and each thread makes it's own database connection.
81
81
  Requests are received and put into a FIFO request queue.
82
82
  The threads work through the queue.
83
- Each request, including session management, a database query, access control, and rendering in HTML or JSON is approximately 1-2ms (on a modern Xeon server).
83
+ Each request, including session management, a database query, access control, and rendering in HTML or JSON is approximately 1 ms (on a modern laptop or server).
84
84
  With additional libraries, Waxx also easily generates XML, XLSX, CSV, PDF, etc.
85
85
 
86
86
  ### Easy to Grok
87
- Waxx has no Classes.
87
+ Waxx has no classes.
88
88
  It is Module-based and the Modules have methods (functions).
89
89
  Each method within a Module is given parameters and the method runs in isolation.
90
90
  There are no instance variables and no global variables.
@@ -92,7 +92,7 @@ Consequently, it is very easy to understand what any method does and it is very
92
92
  You can call any method in the whole system from the console using `waxx console`.
93
93
  Passing in the same variables to a function will always return the same result.
94
94
  Waxx does have `x.res.out` variable, which is appended to with `x << "text"`, that is passed into each method and any method can append to the response body or set response headers.
95
- So it is not truly functional because this is considered a side effect.
95
+ So it is not truly functional because this is a side effect.
96
96
  My opinion is that when you are building a response, then copying the response on every method is a waste of resources.
97
97
  So it does have this side effect by design.
98
98
 
@@ -112,11 +112,11 @@ So it does have this side effect by design.
112
112
  - `x.req.get` is a hash of vars passed in the query string
113
113
  - `x.req.post` is a hash of vars passed in the body of the request
114
114
  - `x.req.env` is a hash of the environment
115
- - `x.req['Header-Name']` is a shortcut to incoming headers / environment vars
115
+ - `x.req['header-name']` is a shortcut to incoming headers / environment vars - always lower-case
116
116
  - `x['param_name']` and `x/:param_name` are shortcuts to get and post vars (post vars override get vars)
117
117
  - `x.res` contains the status, response cookies, headers, and content body
118
118
  - `x << "some text"` appends to the body
119
- - `x.res['Header-Name'] = "value"` to set a response header
119
+ - `x.res['header-name'] = "value"` to set a response header
120
120
  - `x.status = 404` set the status. Defaults to 200.
121
121
  - `x.usr` is the session cookie hash (set expiration params in opt/{env}/config.yaml)
122
122
  - `x.usr['name'] = 'Joe'` will set the name variable in the `x.usr` variable accross requests.
@@ -130,10 +130,10 @@ See [Waxx Docs](https://www.waxx.io/doc/code) for more info.
130
130
  1. HTTP request is received by Waxx (Use a reverse proxy/load balancer/https server like NGINX first for production)
131
131
  2. The request is placed in a queue: `Waxx::Server.queue`
132
132
  3. The request is popped off the queue by a Ruby green thread and parsed
133
- 4. The variable `x` is created with the request `x.req` and response `x.res`.
133
+ 4. The variable `x` is created with the request `x.req` and response `x.res` and DB connections in `x.db`.
134
134
  5. The run method is called for the appropriate app (a namespaced RPC). All routes are: /app/act/[arg1/args2/arg3/...] => app is the module and act is the method to call with the args.
135
- 6. You output to the response using `x << "output"` or using helper methods: `App::Html.page(...)`
136
- 7. The response is returned to the client. Partial, chunked, and streamed responses are supported as well as you have direct access to the IO.
135
+ 6. You output to the response using `x << "output"` or using helper methods: `App::Html.render(...)`
136
+ 7. The response is returned to the client. Partial, chunked, and streamed responses are supported as well. You have direct access to the IO.
137
137
 
138
138
  ## Fast to Develop
139
139
 
data/bin/waxx CHANGED
@@ -3,7 +3,7 @@
3
3
  require 'optparse'
4
4
 
5
5
  command = ARGV[0]
6
- commands = %w(on off buff start stop restart console get put post delete patch config migrate migration deploy test init help)
6
+ commands = %w(on off buff start stop restart console get put post delete patch config migrate migration deploy test init gen generate help)
7
7
  if not commands.include? command
8
8
  puts "Enter a command: #{commands.join(", ")}"
9
9
  puts "waxx --help for all options"
@@ -110,7 +110,9 @@ end
110
110
  if Waxx::Console::Command.respond_to? command
111
111
  if %w(migration).include? command
112
112
  Waxx::Console::Command.send(command, ARGV[1], ARGV[2], opts)
113
- elsif %w(get post put delete patch test deploy migration migrate).include? command
113
+ elsif %w(gen generate).include? command
114
+ Waxx::Console::Command.send(command, *ARGV.slice(1..))
115
+ elsif %w(get post put delete patch test deploy migrate).include? command
114
116
  Waxx::Console::Command.send(command, ARGV[1], opts)
115
117
  else
116
118
  Waxx::Console::Command.send(command, opts)
@@ -0,0 +1,30 @@
1
+ waxx/app.rb 44dff36d6d504ad141ef0db05796356117533858
2
+ waxx/conf.rb ffecdb880e8a57bc1aeb1f6806cee120803fc3e3
3
+ waxx/console.rb 6d2072ee1c426ade3d7f69aa1c7a7c34f56115d4
4
+ waxx/csrf.rb 5ed5af0e892a25421bcb604b3bb527b3ecb4f016
5
+ waxx/database.rb 65c27b3b5819498e400b9d76996bf4d0c814b42e
6
+ waxx/encrypt.rb 13ba4c1eeedae72cc7b84cda96f93281f8bca51a
7
+ waxx/error.rb 86e9fa4c33e381b88234393d01f88882e609a164
8
+ waxx/html.rb 57cbca81c7e3175b090e1ec4159f3c5dd13624b8
9
+ waxx/http.rb 7ead01576646c232dadba35858612d46aa2a1762
10
+ waxx/init.rb ab6cff9000bb848f173d8bb9fd7a9ccc5337a678
11
+ waxx/irb.rb 3a68cbfffefcbd7f9bc1377f3e6778aa62ea48d5
12
+ waxx/irb_env.rb 403613b5b1b269df90ae02049c3584dfa9c253f8
13
+ waxx/json.rb ca90d0f0f8179a6c30d58c69024af6316e2c9e1b
14
+ waxx/mongodb.rb e08957a8d225418541265e6a278fa8667ce79b30
15
+ waxx/mysql2.rb 0bd1d416d7a91cd1e20fc3d785907ee86e18eb69
16
+ waxx/object.rb 2c2aa5f86fd66c24a0522a6734aa69a2285a3a58
17
+ waxx/patch.rb 6795d921bf481dbe72269aa3f3d8156ef27c00cc
18
+ waxx/pdf.rb eb7b7d5c06dd65930303dd003aee2c8ba28fab94
19
+ waxx/pg.rb 74ed59fb57ce4e3a1adfdf43429d7be3bbd1725f
20
+ waxx/process.rb a6d97846c5c2b55181a67e88a112e885b941eede
21
+ waxx/req.rb 512961f28140e2f984943141d49ba6a1b3bb8368
22
+ waxx/res.rb c35d377fe31e0af1de8f3edc833d4e90e6d88c7d
23
+ waxx/server.rb 3142a3cf6d974c2f2ffe7bfa0e40b2899c4afd14
24
+ waxx/sqlite3.rb f3a9a566ffbeb82625f8aaaf96f375c6b49cef75
25
+ waxx/supervisor.rb dbe5fd12f2308c765b72f88e620a06deed7b06ba
26
+ waxx/test.rb fd7b41fcca24e09fd58270c4fd56745d9d346f38
27
+ waxx/util.rb aca719489717efb31e7c5d219a04f95457b30534
28
+ waxx/view.rb 0a9d4e4d4154dea45217bc093801b98c3571be68
29
+ waxx/waxx.rb 6df4053942c329088b71e6c33e63cc4012ee80fb
30
+ waxx/x.rb 9309f97e707660bd91c840b7c43af3a17b0e2577
File without changes
Binary file
Binary file
Binary file
data/lib/waxx/app.rb CHANGED
@@ -100,9 +100,13 @@ module Waxx::App
100
100
  # Layouts in app/app/error/*
101
101
  def error(x, status:200, type:"request", title:"An error occurred", message:"", args: [])
102
102
  x.res.status = status
103
- if App[:app_error][type.to_sym]
104
- App[:app_error][type.to_sym][:get][x, title, message, *args]
105
- else
103
+ begin
104
+ if App[:app_error][type.to_sym]
105
+ App[:app_error][type.to_sym][:get][x, title, message, *args]
106
+ else
107
+ x << "ERROR: #{title} - #{message}"
108
+ end
109
+ rescue
106
110
  x << "ERROR: #{title} - #{message}"
107
111
  end
108
112
  end
@@ -141,7 +145,7 @@ module Waxx::App
141
145
  end
142
146
  return false if g.nil?
143
147
  return true if %w(* all any public).include? g.to_s
144
- return access?(x, g)
148
+ return access?(x, acl: g)
145
149
  when Proc
146
150
  return acl.call(x)
147
151
  else
@@ -163,7 +167,7 @@ module Waxx::App
163
167
  def login_needed(x)
164
168
  if x.ext == "json"
165
169
  x.res.status = 400
166
- x << {ok: false, msg: 'Login needed: Session did not pass ACL'}
170
+ x << {ok: false, msg: 'Login needed: Session did not pass ACL'}.to_json
167
171
  else
168
172
  App::Html.render(x,
169
173
  title: "Please Login",
data/lib/waxx/conf.rb CHANGED
@@ -35,19 +35,19 @@ module Waxx::Conf
35
35
  ##
36
36
  # Get a Waxx variable
37
37
  def [](n)
38
- @data[n]
38
+ @data[n.to_s]
39
39
  end
40
40
 
41
41
  ##
42
42
  # Set a conf variable
43
43
  def []=(n, v)
44
- @data[n] = v
44
+ @data[n.to_s] = v
45
45
  end
46
46
 
47
47
  ##
48
48
  # Get a Waxx variable
49
49
  def /(n)
50
- @data[n.to_s] || @data[n.to_sym]
50
+ @data[n.to_s]
51
51
  end
52
52
 
53
53
  end
data/lib/waxx/console.rb CHANGED
@@ -103,7 +103,7 @@ module Waxx::Console
103
103
  App.put(ARGV[1], opts)
104
104
  end
105
105
 
106
- def delete(opts)
106
+ def del(opts)
107
107
  App.delete(ARGV[1], opts)
108
108
  end
109
109
 
@@ -114,7 +114,7 @@ module Waxx::Console
114
114
  #help = "Use the source, Luke"
115
115
  begin
116
116
  x = Waxx::Console.x
117
- binding.irb
117
+ binding.irb(show_code: false)
118
118
  rescue
119
119
  IRB.setup(nil)
120
120
  workspace = IRB::WorkSpace.new(self)
@@ -175,7 +175,7 @@ module Waxx::Console
175
175
  if target == "waxx"
176
176
  tests = []
177
177
  Dir.entries(opts[:base] + '/test').each{|f|
178
- next if f =~ /^\./
178
+ next if f =~ /^\./ or not f =~ /\.rb$/
179
179
  tests << f.sub(/\.rb$/,"")
180
180
  path = opts[:base] + '/test/' + f
181
181
  puts path
@@ -203,6 +203,16 @@ module Waxx::Console
203
203
  puts re.send("to_#{opts[:format]}")
204
204
  end
205
205
 
206
+ def generate(scope, app, act=nil, type='record')
207
+ unless File.exist? 'app/app.rb'
208
+ puts "Error: You need to call 'waxx generate' from the root of a waxx installation."
209
+ return
210
+ end
211
+ x = Waxx::Console.x
212
+ Waxx::Generate.gen(x, scope, app, act, type)
213
+ end
214
+ alias gen generate
215
+
206
216
  end
207
217
 
208
218
  end
data/lib/waxx/database.rb CHANGED
@@ -65,14 +65,18 @@ module Waxx::Database
65
65
  next if db_only and db_only.to_sym != name
66
66
  puts "Migrating: db.#{name}"
67
67
  # get the latest version
68
- latest = db.exec("SELECT value FROM waxx WHERE name = 'db.#{name}.migration.last'").first['value']
69
- Dir.entries("#{opts[:base]}/db/#{name}/").sort.each{|f|
70
- if f =~ /\.sql$/ and f > latest
71
- puts " #{f}"
72
- db.exec(File.read("#{opts[:base]}/db/#{name}/#{f}"))
73
- db.exec("UPDATE waxx SET value = $1 WHERE name = 'db.#{name}.migration.last'",[f])
74
- end
75
- }
68
+ latest = db.exec("SELECT value FROM waxx WHERE name = 'db.migration.last'").first['value'] rescue nil
69
+ if latest.nil?
70
+ puts "WARNING: No db.migration.last key in waxx table for #{name}"
71
+ else
72
+ Dir.entries("#{opts[:base]}/db/#{name}/").sort.each{|f|
73
+ if f =~ /\.sql$/ and f > latest
74
+ puts " #{f}"
75
+ db.exec(File.read("#{opts[:base]}/db/#{name}/#{f}"))
76
+ db.exec("UPDATE waxx SET value = $1 WHERE name = 'db.migration.last'",[f])
77
+ end
78
+ }
79
+ end
76
80
  }
77
81
  puts "Migration complete"
78
82
  end
@@ -0,0 +1,189 @@
1
+ module Waxx::Generate
2
+ extend self
3
+
4
+ def gen(x, scope, app, act=nil, type='record')
5
+ folder = app_folder(app)
6
+ columns = Waxx::Pg.columns_for(x, app)
7
+ if %w(app object obj).include? scope.to_s
8
+ obj(folder, app, columns)
9
+ end
10
+ if %w(view).include? scope.to_s
11
+ view(folder, app, act, type, columns)
12
+ end
13
+ if %w(app).include? scope.to_s
14
+ view(folder, app, 'list', 'list', columns)
15
+ view(folder, app, 'record', 'record', columns)
16
+ end
17
+ end
18
+
19
+ def app_folder(app)
20
+ folder = "app/#{app.to_s.strip.gsub("_","/")}"
21
+ begin
22
+ FileUtils.mkdir_p folder
23
+ puts "Created folder #{folder}"
24
+ rescue => e
25
+ puts "Could not make app folder. Skipping."
26
+ end
27
+ folder
28
+ end
29
+
30
+ def obj(folder, app, columns)
31
+ file = "#{folder}/#{app.strip}.rb"
32
+ if File.exist? file
33
+ puts "#{file} already exists. Skipping."
34
+ return
35
+ end
36
+ # Build the object file
37
+ re = [
38
+ "module App::#{Waxx::Util.camel_case(app.strip)}",
39
+ " extend Waxx::Pg",
40
+ " extend self",
41
+ "",
42
+ " has(",
43
+ ]
44
+ columns.each{|rec|
45
+ primary = rec['pkey'] ? ", pkey: true" : ""
46
+ re << %( #{rec['column_name']}: {type: '#{rec['data_type'].split(' ').first }'#{primary}},)
47
+ }
48
+ re << [
49
+ " )",
50
+ "",
51
+ " runs(",
52
+ " default: 'list',",
53
+ " list: {",
54
+ " desc: 'List #{app} records',",
55
+ " acl: %w(user),",
56
+ " get: -> (x) {",
57
+ " #{app}s = List.get(x)",
58
+ " List.render(x, #{app}s)",
59
+ " }",
60
+ " },",
61
+ " record: {",
62
+ " desc: 'CRUD #{app} records',",
63
+ " acl: %w(user),",
64
+ " get: -> (x, id=0) {",
65
+ " #{app} = id.to_i == 0 ? {id: 0} : Record.by_id(x, id)",
66
+ " Record.render(x, #{app})",
67
+ " },",
68
+ " post: -> (x, id=0) {",
69
+ " #{app} = id.to_i == 0 ? Record.post(x, x.req.post) : Record.put(x, id, x.req.post)",
70
+ " return x << #{app}.to_json if x.ext == 'json'",
71
+ " x.res.redirect('/#{app}/list')",
72
+ " },",
73
+ " put: -> (x, id) {",
74
+ " #{app} = Record.put(x, id, x.req.post)",
75
+ " return x << #{app}.to_json if x.ext == 'json'",
76
+ " x.res.redirect('/#{app}/list')",
77
+ " },",
78
+ " delete: -> (x, id) {",
79
+ " Record.delete(x, id)",
80
+ " return x << {ok: true, id: id}.to_json if x.ext == 'json'",
81
+ " x.res.redirect('/#{app}/list')",
82
+ " },",
83
+ " },",
84
+ " )",
85
+ "",
86
+ "end",
87
+ "",
88
+ ]
89
+ File.open(file, 'w'){|f| f.puts re.join("\n") }
90
+ puts "Created object file: #{file}"
91
+ end
92
+
93
+ def view(folder, app, act, type, columns)
94
+ file = "#{folder}/#{act.strip}.rb"
95
+ if File.exist? file
96
+ puts "#{file} already exists. Skipping."
97
+ return
98
+ end
99
+ # Build the object file
100
+ re = [
101
+ "module App::#{Waxx::Util.camel_case(app.strip)}::#{Waxx::Util.camel_case(act.strip)}",
102
+ " extend Waxx::View",
103
+ " extend self",
104
+ "",
105
+ " # Specify what formats this view will render automatically",
106
+ " as :json",
107
+ "",
108
+ " # Specify what fields are on this view (only these fields will be rendered and updated)",
109
+ " has(",
110
+ columns.map{|rec| %( :#{rec['column_name']},) },
111
+ " )",
112
+ "",
113
+ ]
114
+ if type.to_s == 'list'
115
+ re << [
116
+ " # Add the fields that can be matched with get(x, args: x.req.get)",
117
+ " match_in(:#{columns[0]['column_name']})",
118
+ " # Add the fields that can be searched using the 'q' parameter in args",
119
+ " search_in(:#{columns[1]['column_name']})",
120
+ "",
121
+ ]
122
+ end
123
+ re << [
124
+ " # This is the HTML view. Delete this module if you are not rendering HTML.",
125
+ " module Html",
126
+ " extend Waxx::Html",
127
+ " extend self",
128
+ "",
129
+ " def get(x, #{type == 'record' ? app : 'data'}, message: {})",
130
+ " title = #{type == 'record' ? "#{app}/:id == 0 ? 'New #{app.capitalize}' : #{app}/:name" : "'#{app.capitalize}s'"}",
131
+ " App::Html.page(x, title: title, content: #{type}(x, #{type == 'record' ? app: 'data'}), message: message)",
132
+ " end",
133
+ "",
134
+ ]
135
+ if type.to_s == 'record'
136
+ re << [
137
+ " def record(x, #{app})",
138
+ " %(",
139
+ %( <form action="/#{app}/#{act}/#{'#{'}#{app}/:id}" method="post">),
140
+ columns.map{|c|
141
+ next if c['pkey']
142
+ %(
143
+ <label for="#{c/:column_name}" class="fssr mt-m">#{c['column_name'].title_case}</label>
144
+ <input id="#{c['column_name']}" name="#{c['column_name']}" class="form" value="#{'#{h '}#{app}/:#{c['column_name']}}">)
145
+ },
146
+ %( <div class="df g-s">),
147
+ %( <button type="submit" class="btn btn-primary">Save</button>),
148
+ %( <a href="/#{app}/list" class="btn">Cancel</a>),
149
+ %( </div>),
150
+ %( </form>),
151
+ " )",
152
+ " end",
153
+ ]
154
+ else
155
+ re << [
156
+ " def list(x, data)",
157
+ " %(",
158
+ %( <a href="/#{app}/record/0" class="fr btn btn-primary">New #{app.title_case}</a>),
159
+ %( <table class="mt-m data">),
160
+ %( <thead>),
161
+ %( <tr>),
162
+ columns.map{|c| %( <th>#{c['column_name'].title_case}</th>) },
163
+ %( </tr>),
164
+ %( </thead>),
165
+ %( <tbody>),
166
+ ' #{data.map{|rec|',
167
+ ' %(',
168
+ ' <tr>',
169
+ columns.map{|c| %( <td>#{'#{h rec/:'}#{c['column_name']}}</td>) },
170
+ %( <td><a href="/#{app}/record/#{'#{'}rec/:id}">edit</a></td>),
171
+ ' </tr>',
172
+ ' )',
173
+ ' }.join("\n")}',
174
+ %( </tbody>),
175
+ %( </table>),
176
+ " )",
177
+ " end",
178
+ ]
179
+ end
180
+ re << " end"
181
+ re << "end"
182
+ # Write the view file
183
+ File.open(file, 'w'){|f| f.puts re.join("\n") }
184
+ # Require the view file from the object file
185
+ File.open("#{folder}/#{app}.rb", 'a'){|f| f.puts %(require_relative "#{act}") }
186
+ puts "Created view file: #{file}"
187
+ end
188
+
189
+ end
data/lib/waxx/http.rb CHANGED
@@ -25,9 +25,10 @@ module Waxx::Http
25
25
  alias qs escape
26
26
 
27
27
  def unescape(str)
28
- str.to_s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) do |m|
28
+ s = str.to_s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) do |m|
29
29
  [m.delete('%')].pack('H*')
30
- end
30
+ end.force_encoding('UTF-8')
31
+ s.valid_encoding? ? s : s.force_encoding(str.encoding)
31
32
  end
32
33
 
33
34
  def parse_head(io)
@@ -38,7 +39,7 @@ module Waxx::Http
38
39
  break if e.strip == ""
39
40
  head << e
40
41
  n, v = e.split(":", 2)
41
- env[n] = v.strip
42
+ env[n.downcase] = v.strip
42
43
  end
43
44
  Waxx.debug "env.size: #{env.size}", 9
44
45
  [env, head]
@@ -46,7 +47,7 @@ module Waxx::Http
46
47
 
47
48
  def query_string_to_hash(str, base={})
48
49
  return base if str.nil? or str.strip == ""
49
- str.strip.split(/[;&]/).each{|nv|
50
+ str.force_encoding('UTF-8').strip.split(/[;&]/).each{|nv|
50
51
  n, v = nv.split("=",2).map{|s| unescape(s)}
51
52
  if n =~ /\[\]$/
52
53
  n = n.sub(/\[\]$/,"")
@@ -59,25 +60,55 @@ module Waxx::Http
59
60
  base
60
61
  end
61
62
 
63
+ def file_object(boundary, filename, body, headers)
64
+ {
65
+ filename: filename,
66
+ data: body.sub(/\r\n--#{boundary}--\r\n$/,"").sub(/\r\n$/,""),
67
+ content_type: headers['content-type'] || headers['Content-Type'],
68
+ headers: headers
69
+ }
70
+ end
71
+
62
72
  def parse_multipart(env, data)
63
- boundary = env['Content-Type'].match(/boundary=(.*)$/)[1]
73
+ boundary = env['content-type'].match(/boundary=(.*)$/)[1]
64
74
  parts = data.split("--"+boundary+"\r\n")
65
75
  post = {}
66
76
  parts.each{|part|
67
77
  next if part.strip == ""
68
78
  begin
69
79
  head, body = part.split("\r\n\r\n",2)
70
- headers = Hash[*(head.split("\r\n").map{|hp| hp.split(":",2).map{|i| i.strip}}.flatten)]
71
- cd = Hash[*("_=#{headers['Content-Disposition']}".split(";").map{|da| da.strip.gsub('"',"").split("=",2)}.flatten)]
80
+ headers = Hash[*(head.split("\r\n").map{|hp|
81
+ hp.split(":",2).map{|i| i.strip}
82
+ }.flatten)]
83
+ cd = Hash[*("_=#{headers['Content-Disposition'] || headers['content-disposition']}".split(";").map{|da| da.strip.gsub('"',"").split("=",2)}.flatten)]
84
+ name = cd['name']
85
+ # If field name ends with [], make result an array
86
+ if name.include?('[]')
87
+ # Strip the square backets off the name for the post key
88
+ name = name.sub(/\[\]$/,'')
89
+ post[name] ||= []
90
+ end
72
91
  if cd['filename']
73
- post[cd['name']] = {
74
- filename: cd['filename'],
75
- data: body.sub(/\r\n--#{boundary}--\r\n$/,"").sub(/\r\n$/,""),
76
- content_type: headers['Content-Type'],
77
- headers: headers
78
- }
92
+ # Handle multiple files
93
+ if post.has_key?(name)
94
+ if Array === post[name]
95
+ post[name].push file_object(boundary, cd['filename'], body, headers)
96
+ else
97
+ post[name] = [post[name], file_object(boundary, cd['filename'], body, headers)]
98
+ end
99
+ else
100
+ post[name] = file_object(boundary, cd['filename'], body, headers)
101
+ end
79
102
  else
80
- post[cd['name']] = body.sub(/\r\n--#{boundary}--\r\n$/,"").sub(/\r\n$/,"")
103
+ if post.has_key?(name)
104
+ if Array === post[name]
105
+ post[name].push body.sub(/\r\n--#{boundary}--\r\n$/,"").sub(/\r\n$/,"")
106
+ else
107
+ post[name] = [post[name], body.sub(/\r\n--#{boundary}--\r\n$/,"").sub(/\r\n$/,"")]
108
+ end
109
+ else
110
+ post[name] = body.sub(/\r\n--#{boundary}--\r\n$/,"").sub(/\r\n$/,"")
111
+ end
81
112
  end
82
113
  rescue => e
83
114
  Waxx.debug "Error parse_multipart: #{e}"
@@ -113,30 +144,41 @@ module Waxx::Http
113
144
  end
114
145
 
115
146
  def parse_data(env, meth, io, head)
116
- Waxx.debug "parse_data"
117
- if %w(PUT POST PATCH).include? meth
118
- data = io.read(env['Content-Length'].to_i)
119
- Waxx.debug "data.size: #{data.size} #{env['Content-Type']}"
120
- if env['Content-Length'].to_i == 0
147
+ begin
148
+ Waxx.debug "parse_data"
149
+ if %w(PUT POST PATCH).include? meth
150
+ data = io.read(env['content-length'].to_i)
151
+ Waxx.debug "data.size: #{data.size} #{env['content-type']}"
152
+ # No content
153
+ if env['content-length'].to_i == 0
154
+ post = {}.freeze
155
+ data = nil
156
+ # Raw file saved by nginx with path in header
157
+ elsif env['x-file-path']
158
+ post = {}.freeze
159
+ data = {"file_path" => env['x-file-path']}
160
+ # Parse based on content type
161
+ else
162
+ case env['content-type']
163
+ when /x-www-form-urlencoded/
164
+ post = query_string_to_hash(data).freeze
165
+ when /multipart/
166
+ post = parse_multipart(env, data).freeze
167
+ when /json/
168
+ post = (JSON.parse(data)).freeze
169
+ else
170
+ post = {"data" => data}.freeze
171
+ end
172
+ end
173
+ else
121
174
  post = {}.freeze
122
175
  data = nil
123
- else
124
- case (env['Content-Type'] || env['content-type'] || env['Content-type'])
125
- when /x-www-form-urlencoded/
126
- post = query_string_to_hash(data).freeze
127
- when /multipart/
128
- post = parse_multipart(env, data).freeze
129
- when /json/
130
- post = (JSON.parse(data)).freeze
131
- else
132
- post = data.freeze
133
- end
134
176
  end
135
- else
136
- post = {}.freeze
137
- data = nil
177
+ [post, data]
178
+ rescue => e
179
+ Waxx.debug("ERROR: Parsing POST data: #{e}")
180
+ [{error: e.to_s}.freeze, nil]
138
181
  end
139
- [post, data]
140
182
  end
141
183
 
142
184
  Status = {
@@ -169,6 +211,7 @@ module Waxx::Http
169
211
  js: "application/javascript; charset=utf-8",
170
212
  json: "application/json; charset=utf-8",
171
213
  tab: "text/tab-separated-values; charset=utf-8",
214
+ tsv: "text/tab-separated-values; charset=utf-8",
172
215
  txt: "text/plain; charset=utf-8",
173
216
  xml: "text/xml",
174
217
  gif: "image/gif",
data/lib/waxx/init.rb CHANGED
@@ -182,7 +182,7 @@ init:
182
182
  puts "The databse must already exist and the user must have create table privileges."
183
183
  print " Create waxx table (y|n) [y]:"
184
184
  initdb = $stdin.gets.chomp
185
- initdb = initdb == '' or not (initdb =~ /[Yy]/).nil?
185
+ initdb = initdb == '' or not (initdb =~ /[Yy]/).nil? rescue false
186
186
  input['init']['db'] = initdb
187
187
  end
188
188
  input
@@ -235,6 +235,7 @@ init:
235
235
  puts ""
236
236
  puts "Waxx installed successfully."
237
237
  puts "cd into #{install_folder} and run `waxx on` to get your waxx on"
238
+ puts "Optionally run `waxx gen app TABLE_NAME` where TABLE_NAME is the name of a table in your database."
238
239
  end
239
240
 
240
241
  def create_waxx_table(x, input)
@@ -254,7 +255,7 @@ init:
254
255
  CONSTRAINT waxx_uniq UNIQUE(name)
255
256
  )
256
257
  )
257
- insert_sql = %(INSERT INTO waxx (name, value) VALUES ('db.app.migration.last', '0'))
258
+ insert_sql = %(INSERT INTO waxx (name, value) VALUES ('db.migration.last', '0'))
258
259
  puts " Connecting to: #{input/:databases/:app}"
259
260
  begin
260
261
  db = Waxx::Database.connect(input/:databases/:app)
data/lib/waxx/object.rb CHANGED
@@ -59,11 +59,11 @@ module Waxx::Object
59
59
  # # A proc that return boolean (true = access granted, false = access denied)
60
60
  # # The x variable is passed in.
61
61
  # acl: -> (x) {
62
- # x.req.env['X-Key'] == "Secret Key"
62
+ # x.req.env['x-key'] == "Secret Key"
63
63
  # }
64
64
  # # Another example based on the client IP set by the proxy server
65
65
  # acl: -> (x) {
66
- # [x.req.env['X-Forwarded-For']].flatten.first == "10.20.40.80"
66
+ # [x.req.env['x-forwarded-for']].flatten.first == "10.20.40.80"
67
67
  # }
68
68
  # # Require a user to be in two groups
69
69
  # acl: -> (x) {