waxx 0.1.2

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 (75) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE +201 -0
  4. data/README.md +879 -0
  5. data/bin/waxx +120 -0
  6. data/lib/waxx/app.rb +173 -0
  7. data/lib/waxx/conf.rb +54 -0
  8. data/lib/waxx/console.rb +204 -0
  9. data/lib/waxx/csrf.rb +14 -0
  10. data/lib/waxx/database.rb +80 -0
  11. data/lib/waxx/encrypt.rb +38 -0
  12. data/lib/waxx/error.rb +60 -0
  13. data/lib/waxx/html.rb +33 -0
  14. data/lib/waxx/http.rb +268 -0
  15. data/lib/waxx/init.rb +273 -0
  16. data/lib/waxx/irb.rb +44 -0
  17. data/lib/waxx/irb_env.rb +18 -0
  18. data/lib/waxx/json.rb +23 -0
  19. data/lib/waxx/mongodb.rb +221 -0
  20. data/lib/waxx/mysql2.rb +234 -0
  21. data/lib/waxx/object.rb +115 -0
  22. data/lib/waxx/patch.rb +138 -0
  23. data/lib/waxx/pdf.rb +69 -0
  24. data/lib/waxx/pg.rb +246 -0
  25. data/lib/waxx/process.rb +270 -0
  26. data/lib/waxx/req.rb +116 -0
  27. data/lib/waxx/res.rb +98 -0
  28. data/lib/waxx/server.rb +304 -0
  29. data/lib/waxx/sqlite3.rb +237 -0
  30. data/lib/waxx/supervisor.rb +47 -0
  31. data/lib/waxx/test.rb +162 -0
  32. data/lib/waxx/util.rb +57 -0
  33. data/lib/waxx/version.rb +3 -0
  34. data/lib/waxx/view.rb +389 -0
  35. data/lib/waxx/waxx.rb +73 -0
  36. data/lib/waxx/x.rb +103 -0
  37. data/lib/waxx.rb +50 -0
  38. data/skel/README.md +11 -0
  39. data/skel/app/app/app.rb +39 -0
  40. data/skel/app/app/error/app_error.rb +16 -0
  41. data/skel/app/app/error/dhtml.rb +9 -0
  42. data/skel/app/app/error/html.rb +8 -0
  43. data/skel/app/app/error/json.rb +8 -0
  44. data/skel/app/app/error/pdf.rb +13 -0
  45. data/skel/app/app/log/app_log.rb +13 -0
  46. data/skel/app/app.rb +20 -0
  47. data/skel/app/home/home.rb +16 -0
  48. data/skel/app/home/html.rb +145 -0
  49. data/skel/app/html.rb +192 -0
  50. data/skel/app/usr/email.rb +66 -0
  51. data/skel/app/usr/html.rb +115 -0
  52. data/skel/app/usr/list.rb +51 -0
  53. data/skel/app/usr/password.rb +54 -0
  54. data/skel/app/usr/record.rb +98 -0
  55. data/skel/app/usr/usr.js +67 -0
  56. data/skel/app/usr/usr.rb +277 -0
  57. data/skel/app/waxx/waxx.rb +109 -0
  58. data/skel/bin/README.md +1 -0
  59. data/skel/db/README.md +11 -0
  60. data/skel/db/app/0-init.sql +88 -0
  61. data/skel/lib/README.md +1 -0
  62. data/skel/log/README.md +1 -0
  63. data/skel/opt/dev/config.yaml +1 -0
  64. data/skel/opt/prod/config.yaml +1 -0
  65. data/skel/opt/stage/config.yaml +1 -0
  66. data/skel/opt/test/config.yaml +1 -0
  67. data/skel/private/README.md +1 -0
  68. data/skel/public/lib/site.css +202 -0
  69. data/skel/public/lib/waxx/w.ico +0 -0
  70. data/skel/public/lib/waxx/w.png +0 -0
  71. data/skel/public/lib/waxx/waxx.js +111 -0
  72. data/skel/tmp/pids/README.md +1 -0
  73. data.tar.gz.sig +0 -0
  74. metadata +140 -0
  75. metadata.gz.sig +3 -0
@@ -0,0 +1,270 @@
1
+ # Waxx Copyright (c) 2016 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com> All rights reserved.
2
+ # Released under the Apache Version 2 License. See LICENSE.txt.
3
+
4
+ # Derived from dante
5
+
6
+ module Waxx::Process
7
+
8
+ class Runner
9
+ MAX_START_TRIES = 5
10
+
11
+ attr_accessor :options, :name, :description, :verify_options_hook
12
+
13
+ class << self
14
+ def run(*args, &block)
15
+ self.new(*args, &block)
16
+ end
17
+ end
18
+
19
+ def initialize(name, defaults={}, &block)
20
+ @name = name
21
+ @startup_command = block
22
+ @options = {
23
+ :host => '0.0.0.0',
24
+ :pid_path => "/var/run/#{@name}.pid",
25
+ :log_path => false,
26
+ :debug => true
27
+ }.merge(defaults)
28
+ end
29
+
30
+ # Accepts options for the process
31
+ # `@runner.with_options { |opts| opts.on(...) }`
32
+ def with_options(&block)
33
+ @with_options = block
34
+ end
35
+
36
+ # Executes the runner based on options
37
+ # `@runner.execute`
38
+ # `@runner.execute { ... }`
39
+ def execute(opts={}, &block)
40
+ parse_options
41
+ self.options.merge!(opts)
42
+
43
+ @verify_options_hook.call(self.options) if @verify_options_hook
44
+
45
+ if options.include?(:kill)
46
+ self.stop
47
+ else # create process
48
+ self.stop if options.include?(:restart)
49
+
50
+ # If a username, uid, groupname, or gid is passed,
51
+ # drop privileges accordingly.
52
+
53
+ if options[:group]
54
+ gid = options[:group].is_a?(Integer) ? options[:group] : Etc.getgrnam(options[:group]).gid
55
+ Process::GID.change_privilege(gid)
56
+ end
57
+
58
+ if options[:user]
59
+ uid = options[:user].is_a?(Integer) ? options[:user] : Etc.getpwnam(options[:user]).uid
60
+ Process::UID.change_privilege(uid)
61
+ end
62
+
63
+ @startup_command = block if block_given?
64
+ options[:daemonize] ? daemonize : start
65
+ end
66
+ end
67
+
68
+ def daemonize
69
+ return log("Process is already started") if self.daemon_running? # daemon already started
70
+
71
+ if !options[:log_path]
72
+ options[:log_path] = "/var/log/#{@name}.log"
73
+ end
74
+
75
+ # Start process
76
+ pid = fork do
77
+ exit if fork
78
+ Process.setsid
79
+ exit if fork
80
+ store_pid(Process.pid)
81
+ File.umask 0000
82
+ redirect_output!
83
+ start
84
+ end
85
+ Process.waitpid pid
86
+ # Ensure process is running
87
+ if until_true(MAX_START_TRIES) { self.daemon_running? }
88
+ log "Daemon has started successfully"
89
+ true
90
+ else # Failed to start
91
+ log "Daemonized process couldn't be started"
92
+ false
93
+ end
94
+ end
95
+
96
+ def start
97
+ log "Starting #{@name} service..."
98
+
99
+ if log_path = options[:log_path] && options[:daemonize].nil?
100
+ redirect_output!
101
+ end
102
+
103
+ trap("INT") {
104
+ interrupt
105
+ exit
106
+ }
107
+
108
+ trap("TERM"){
109
+ log "Trying to stop #{@name}..."
110
+ exit
111
+ }
112
+
113
+ @startup_command.call(self.options) if @startup_command
114
+ end
115
+
116
+ # Stops a daemonized process
117
+ def stop(kill_arg=nil)
118
+ if self.daemon_running?
119
+ kill_pid(kill_arg || options[:kill])
120
+ until_true(MAX_START_TRIES) { self.daemon_stopped? }
121
+ else # not running
122
+ log "No #{@name} processes are running"
123
+ false
124
+ end
125
+ end
126
+
127
+ def restart
128
+ self.stop
129
+ self.start
130
+ end
131
+
132
+ def interrupt
133
+ if options[:debug]
134
+ raise Interrupt
135
+ sleep 1
136
+ else
137
+ log "Interrupt received; stopping #{@name}"
138
+ end
139
+ end
140
+
141
+ # Returns true if process is not running
142
+ def daemon_stopped?
143
+ ! self.daemon_running?
144
+ end
145
+
146
+ # Returns running for the daemonized process
147
+ # self.daemon_running?
148
+ def daemon_running?
149
+ return false unless File.exist?(options[:pid_path])
150
+ Process.kill 0, File.read(options[:pid_path]).to_i
151
+ true
152
+ rescue Errno::ESRCH
153
+ false
154
+ end
155
+
156
+ protected
157
+
158
+ def parse_options
159
+ headline = [@name, @description].compact.join(" - ")
160
+ OptionParser.new do |opts|
161
+ opts.summary_width = 25
162
+ opts.banner = [headline, "\n\n",
163
+ "Usage: #{@name} [-p port] [-P file] [-d] [-k]\n",
164
+ " #{@name} --help\n"].compact.join("")
165
+ opts.separator ""
166
+
167
+ opts.on("-p", "--port PORT", Integer, "Specify port", "(default: #{options[:port]})") do |v|
168
+ options[:port] = v
169
+ end
170
+
171
+ opts.on("-P", "--pid FILE", String, "save PID in FILE when using -d option.", "(default: #{options[:pid_path]})") do |v|
172
+ options[:pid_path] = File.expand_path(v)
173
+ end
174
+
175
+ opts.on("-d", "--daemon", "Daemonize mode") do |v|
176
+ options[:daemonize] = v
177
+ end
178
+
179
+ opts.on("-l", "--log FILE", String, "Logfile for output", "(default: /var/log/#{@name}.log)") do |v|
180
+ options[:log_path] = v
181
+ end
182
+
183
+ opts.on("-k", "--kill [PORT]", String, "Kill specified running daemons - leave blank to kill all.") do |v|
184
+ options[:kill] = v
185
+ end
186
+
187
+ opts.on("-u", "--user USER", String, "User to run as") do |user|
188
+ options[:user] = user
189
+ end
190
+
191
+ opts.on("-G", "--group GROUP", String, "Group to run as") do |group|
192
+ options[:group] = group
193
+ end
194
+
195
+ opts.on_tail("-?", "--help", "Display this usage information.") do
196
+ puts "#{opts}\n"
197
+ exit
198
+ end
199
+
200
+ # Load options specified through 'with_options'
201
+ instance_exec(opts, &@with_options) if @with_options
202
+ end.parse!
203
+ options
204
+ end
205
+
206
+ def store_pid(pid)
207
+ FileUtils.mkdir_p(File.dirname(options[:pid_path]))
208
+ File.open(options[:pid_path], 'w'){|f| f.write("#{pid}\n")}
209
+ end
210
+
211
+ def kill_pid(k='*')
212
+ Dir[options[:pid_path]].each do |f|
213
+ begin
214
+ pid = IO.read(f).chomp.to_i
215
+ FileUtils.rm f
216
+ Process.kill('TERM', pid)
217
+ log "Stopped PID: #{pid} at #{f}"
218
+ rescue => e
219
+ log "Failed to stop! #{k}: #{e}"
220
+ end
221
+ end
222
+ end
223
+
224
+ # Redirect output based on log settings (reopens stdout/stderr to specified logfile)
225
+ # If log_path is nil, redirect to /dev/null to quiet output
226
+ def redirect_output!
227
+ if log_path = options[:log_path]
228
+ # if the log directory doesn't exist, create it
229
+ FileUtils.mkdir_p File.dirname(options[:log_path]), :mode => 0755
230
+ # touch the log file to create it
231
+ FileUtils.touch log_path
232
+ # Set permissions on the log file
233
+ File.chmod(0644, log_path)
234
+ # Reopen $stdout (NOT +STDOUT+) to start writing to the log file
235
+ $stdout.reopen(log_path, 'a')
236
+ # Redirect $stderr to $stdout
237
+ $stderr.reopen $stdout
238
+ $stdout.sync = true
239
+ else # redirect to /dev/null
240
+ # We're not bothering to sync if we're dumping to /dev/null
241
+ # because /dev/null doesn't care about buffered output
242
+ $stdin.reopen '/dev/null'
243
+ $stdout.reopen '/dev/null', 'a'
244
+ $stderr.reopen $stdout
245
+ end
246
+ log_path = options[:log_path] ? options[:log_path] : '/dev/null'
247
+ end
248
+
249
+ # Runs until the block condition is met or the timeout_seconds is exceeded
250
+ # until_true(10) { ...return_condition... }
251
+ def until_true(timeout_seconds, interval=1, &block)
252
+ elapsed_seconds = 0
253
+ while elapsed_seconds < timeout_seconds && block.call != true
254
+ elapsed_seconds += interval
255
+ sleep(interval)
256
+ end
257
+ elapsed_seconds < timeout_seconds
258
+ end
259
+
260
+ def log(message)
261
+ puts message
262
+ end
263
+
264
+ end
265
+
266
+ def self.run(name, options={}, &blk)
267
+ Waxx::Process::Runner.new(name, options, &blk).execute
268
+ end
269
+
270
+ end
data/lib/waxx/req.rb ADDED
@@ -0,0 +1,116 @@
1
+ # Waxx Copyright (c) 2016-2017 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Waxx
16
+
17
+ ##
18
+ # The Request Struct gets instanciated with each request (x.req)
19
+ # ```
20
+ # x.req.env # A hash of the request environment
21
+ # x.req.data # The raw body of put, post, patch requests
22
+ # x.req.meth # Request method as a string: GET, PUT, POST, PATCH, DELETE
23
+ # x.req.uri # The URI
24
+ # x.req.get # The GET/URL parameters (has with string keys and values) shortcut: x['name'] => 'value'
25
+ # x.req.post # The POST/BODY params with string keys and values) shortcut: x['name'] => 'value'
26
+ # ```
27
+ #
28
+ # GET params can be delimited with & or ;. The following are equivilant:
29
+ #
30
+ # ```
31
+ # http://localhost:7777/waxx/env?x=1&y=2&z=3
32
+ # http://localhost:7777/waxx/env?x=1;y=2;z=3
33
+ # ```
34
+ #
35
+ # Note that single params are single values and will be set to the last value received.
36
+ # Params names with "[]" appended are array values.
37
+ #
38
+ # ```
39
+ # http://localhost:7777/waxx/env?foo=1;foo=2;foo=3
40
+ # x['foo'] => "3"
41
+ #
42
+ # http://localhost:7777/waxx/env?foo[]=1;foo[]=2;foo[]=3
43
+ # x['foo'] => ["1", "2", "3"]
44
+ #
45
+ # ```
46
+ # If you are uploading JSON directly in the body (with the content type application/json or text/json), then the types are matched.
47
+ #
48
+ # Given the a request like:
49
+ #
50
+ # ```
51
+ # Content-Type: application/json
52
+ #
53
+ # {foo:123,bar:['a','1',2]}
54
+ # ```
55
+ #
56
+ # The following vars are of the type submitted
57
+ #
58
+ # ```
59
+ # x['foo'] => 123 (as an int)
60
+ # x['bar'] => ['a','1',2] (1 is a string and 2 is an int)
61
+ # ```
62
+ #
63
+ # ### File Uploads
64
+ # Given the form:
65
+ #
66
+ # ```
67
+ # <form action="/file/upload" method="post" enctype="multipart/form-data">
68
+ # <input type="file" name="file">
69
+ # <button type="submit">Upload File</button>
70
+ # </form>
71
+ # ```
72
+ #
73
+ # The following hash is available (symbol keys):
74
+ #
75
+ # ```
76
+ # x['file'][:filename] => 'file_name.ext'
77
+ # x['file'][:data] => The content of the file
78
+ # x['file'][:content_type] => The Content-Type as sent by the browser
79
+ # x['file'][:headers] => An hash of other headers send by the browser regarding this file
80
+ # ```
81
+ #
82
+ # How to save a file to the tmp folder:
83
+ #
84
+ # **app/file/file.rb**
85
+ #
86
+ # ```
87
+ # module App::File
88
+ # extend Waxx::Object
89
+ # runs(
90
+ # upload: {
91
+ # desc: 'Upload a file to the tmp folder',
92
+ # post: -> (x) {
93
+ # # Strip any non-word chars and save the file to the tmp folder
94
+ # File.open("#{Waxx::Root}/tmp/(x/:file/:filename).gsub(/[\W\.]/,'-'),'wb'){|f|
95
+ # f << x/:file/:data
96
+ # }
97
+ # x << "Your file has been uploaded."
98
+ # }
99
+ # }
100
+ # )
101
+ # end
102
+ # ```
103
+ #
104
+ Req = Struct.new(
105
+ :env,
106
+ :data,
107
+ :meth,
108
+ :uri,
109
+ :get,
110
+ :post,
111
+ :cookies,
112
+ :start_time
113
+ )
114
+
115
+ end
116
+
data/lib/waxx/res.rb ADDED
@@ -0,0 +1,98 @@
1
+ # Waxx Copyright (c) 2016-2017 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Waxx
16
+
17
+ ##
18
+ # The Response Struct gets instanciated with each request (x.res)
19
+ #
20
+ # ```
21
+ # x.res[name] = value # Set a response header
22
+ # x.res.as(extention) # Set the Content-Type header based on extension
23
+ # x.res.redirect '/path' # Redirect the client with 302 / Location header
24
+ # x.res.location '/path' # Redirect the client with 302 / Location header
25
+ # x.res.cookie( # Set a cookie
26
+ # name: "",
27
+ # value: nil,
28
+ # domain: nil,
29
+ # expires: nil,
30
+ # path: "/",
31
+ # secure: true,
32
+ # http_only: false,
33
+ # same_site: "Lax"
34
+ # )
35
+ # x << "ouput" # Append output to the response body
36
+ # x.res << "output" # Append output to the response body
37
+ # ```
38
+ Res = Struct.new(
39
+ :server,
40
+ :status,
41
+ :headers,
42
+ :out,
43
+ :error,
44
+ :cookies,
45
+ :no_cookies
46
+ ) do
47
+
48
+ # Send output to the client (may be buffered)
49
+ def << str
50
+ out << str
51
+ end
52
+
53
+ def [](n,v)
54
+ headers[n]
55
+ end
56
+
57
+ def []=(n,v)
58
+ headers[n] = v
59
+ end
60
+
61
+ def as(ext)
62
+ headers['Content-Type'] = Waxx::Http.ctype(ext)
63
+ end
64
+
65
+ def location(uri)
66
+ self.status = 302
67
+ headers['Location'] = uri
68
+ end
69
+ alias redirect location
70
+
71
+ # Return the response headers
72
+ def head
73
+ [
74
+ "HTTP/1.1 #{status} #{Waxx::Http::Status[status.to_s]}",
75
+ headers.map{|n,v| "#{n}: #{v}"},
76
+ (cookies.map{|c|
77
+ "Set-Cookie: #{c}"
78
+ } unless no_cookies),
79
+ "\r\n"
80
+ ].flatten.join("\r\n")
81
+ end
82
+
83
+ # Output the headers and the body
84
+ def complete
85
+ re = out.join
86
+ headers["Content-Length"] = re.bytesize
87
+ server.print head
88
+ server.print re
89
+ end
90
+
91
+ def cookie(name:"", value:nil, domain:nil, expires:nil, path:"/", secure:true, http_only: false, same_site: "Lax")
92
+ expires = expires.nil? ? "" : "expires=#{Time === expires ? expires.rfc2822 : expires}; "
93
+ cookies << "#{name}=#{Waxx::Http.escape(value.to_s)}; #{expires}#{";domain=#{domain}" if domain}; path=#{path}#{"; secure" if secure}#{"; HttpOnly" if http_only}; SameSite=#{same_site}"
94
+ end
95
+ end
96
+
97
+ end
98
+