waxx 0.1.3 → 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 +4 -4
- data/README.md +8 -8
- data/bin/waxx +4 -2
- data/lib/.yardoc/checksums +30 -0
- data/lib/.yardoc/complete +0 -0
- data/lib/.yardoc/object_types +0 -0
- data/lib/.yardoc/objects/root.dat +0 -0
- data/lib/.yardoc/proxy_types +0 -0
- data/lib/waxx/app.rb +9 -5
- data/lib/waxx/conf.rb +3 -3
- data/lib/waxx/console.rb +13 -3
- data/lib/waxx/database.rb +12 -8
- data/lib/waxx/generate.rb +189 -0
- data/lib/waxx/http.rb +77 -34
- data/lib/waxx/init.rb +3 -2
- data/lib/waxx/object.rb +2 -2
- data/lib/waxx/patch.rb +7 -0
- data/lib/waxx/pdf.rb +2 -2
- data/lib/waxx/pg.rb +73 -12
- data/lib/waxx/res.rb +32 -8
- data/lib/waxx/server.rb +5 -4
- data/lib/waxx/version.rb +1 -1
- data/lib/waxx/view.rb +41 -8
- data/lib/waxx/waxx.rb +4 -4
- data/lib/waxx/x.rb +14 -2
- data/lib/waxx.rb +2 -1
- data/skel/app/about/about.rb +32 -0
- data/skel/app/home/html.rb +5 -135
- data/skel/app/html.rb +38 -124
- data/skel/app/usr/usr.rb +4 -4
- data/skel/app/waxx/waxx.rb +1 -1
- data/skel/public/lib/waxx/waxx.js +0 -1
- metadata +14 -36
- checksums.yaml.gz.sig +0 -0
- data/skel/public/lib/site.css +0 -202
- data.tar.gz.sig +0 -0
- metadata.gz.sig +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d48f18168120d265fbad00f5c097e897fc2100475c2fc5c16ca2e0ac0fdd065a
|
|
4
|
+
data.tar.gz: 49cb992829fadcf39605e9052db7ff1d0ec10b06b0a76c1f84d7f9d6b5f85c5e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
|
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
|
|
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['
|
|
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['
|
|
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.
|
|
136
|
-
7. The response is returned to the client. Partial, chunked, and streamed responses are supported as well
|
|
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(
|
|
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
|
-
|
|
104
|
-
App[:app_error][type.to_sym]
|
|
105
|
-
|
|
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]
|
|
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
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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['
|
|
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|
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
data
|
|
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.
|
|
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['
|
|
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['
|
|
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) {
|