wabur 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +106 -15
  3. data/bin/wabur +6 -0
  4. data/export/assets/js/ui.js +18 -0
  5. data/lib/wab.rb +7 -1
  6. data/lib/wab/client.rb +145 -0
  7. data/lib/wab/controller.rb +1 -1
  8. data/lib/wab/errors.rb +6 -0
  9. data/lib/wab/impl.rb +1 -1
  10. data/lib/wab/impl/agoo.rb +18 -0
  11. data/lib/wab/impl/agoo/export_proxy.rb +55 -0
  12. data/lib/wab/impl/agoo/handler.rb +51 -0
  13. data/lib/wab/impl/agoo/sender.rb +50 -0
  14. data/lib/wab/impl/agoo/server.rb +59 -0
  15. data/lib/wab/impl/agoo/tql_handler.rb +35 -0
  16. data/lib/wab/impl/model.rb +8 -1
  17. data/lib/wab/impl/rack_error.rb +27 -0
  18. data/lib/wab/impl/rack_handler.rb +69 -0
  19. data/lib/wab/impl/shell.rb +56 -51
  20. data/lib/wab/impl/sinatra.rb +18 -0
  21. data/lib/wab/impl/sinatra/export_proxy.rb +57 -0
  22. data/lib/wab/impl/sinatra/handler.rb +50 -0
  23. data/lib/wab/impl/sinatra/sender.rb +53 -0
  24. data/lib/wab/impl/sinatra/server.rb +66 -0
  25. data/lib/wab/impl/sinatra/tql_handler.rb +35 -0
  26. data/lib/wab/impl/templates/wabur.conf.template +1 -1
  27. data/lib/wab/impl/webrick.rb +18 -0
  28. data/lib/wab/impl/webrick/export_proxy.rb +41 -0
  29. data/lib/wab/impl/webrick/handler.rb +116 -0
  30. data/lib/wab/impl/webrick/sender.rb +34 -0
  31. data/lib/wab/impl/webrick/server.rb +39 -0
  32. data/lib/wab/impl/webrick/tql_handler.rb +58 -0
  33. data/lib/wab/racker.rb +25 -0
  34. data/lib/wab/version.rb +1 -1
  35. data/pages/Architecture.md +15 -6
  36. data/test/test_client.rb +282 -0
  37. data/test/test_impl.rb +2 -0
  38. data/test/test_runner.rb +267 -91
  39. metadata +27 -5
  40. data/lib/wab/impl/export_proxy.rb +0 -39
  41. data/lib/wab/impl/handler.rb +0 -98
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ca329c76ee3951ecd328480754dd09a2bdcd6a32
4
- data.tar.gz: d811b8c2f08585329308d2eef96d395b9f0d3626
2
+ SHA256:
3
+ metadata.gz: dbf0b84d397a6216caa0d5190e69b2d024a2c3274ddd18137ccc54154cca80ab
4
+ data.tar.gz: ee17aea00b5048cf3f9cfddc562ec6bbf98b13f48482343e8805dd6e8c03db55
5
5
  SHA512:
6
- metadata.gz: 4f2fc06292ecba5c5505fe79c20a555d11ee2a0057176495b367ec2c960886cb36a998abd488a888b0fa31b161afd61545b45ab2fc7571c9e9551c8653807307
7
- data.tar.gz: e6b7a19e414c9d7a011229e66c76a412406e0bde1d508c1290390d097a637b689c236c6af331ab173e26592a099b6d7df26945d796c0becd5a05749cbfe219ec
6
+ metadata.gz: dcd2a2c8e493a38300942c9f76ad38b4ccaac4503637bc7d0929774371e55bc855441bebb9b49de76e49a619f86deb438f2b2f5d3a0e1ee9ba69ac1e9a1534a0
7
+ data.tar.gz: a04cadc7cd14f6ecc69a842334a41e57a2d7c6737590ca453c63bbd542211bdc05a0110c0948287a0057ab5bb0176edfac4f1ef376bf2d514c5b2386cdf2b74b
data/README.md CHANGED
@@ -5,27 +5,115 @@
5
5
  [![Gem Version](https://badge.fury.io/rb/wabur.svg)](https://rubygems.org/gems/wabur)
6
6
  [![Gem](https://img.shields.io/gem/dt/wabur.svg)](https://rubygems.org/gems/wabur)
7
7
 
8
- WABuR is a Web Application Builder using Ruby. It employs a modern NoSQL JSON
9
- data store and a single-page UI using JavaScript. The best part is that it is
10
- simple and very fast, hitting over 200,000 fetches a second with a Ruby core!
8
+ WABuR is a Web Application Builder using Ruby. It is easy to use, taking just
9
+ a minute to create a `Hello World` web application and it is _FAST_, hitting
10
+ over 200,000 fetches a second with a Ruby core! It employs a modern NoSQL JSON
11
+ data store and a single-page UI using JavaScript.
11
12
 
12
13
  It is pluggable and extendable in many ways to allow new additions,
13
14
  alternative databases, and any number of UIs.
14
15
 
15
- A natural question is *"What about Rails?"*. Rails is well established and has
16
- a huge user base. WABuR is not a replacement for Rails. It is an alternative
17
- for those who want to explore using JSON databases with a single-page dynamic
18
- JavaScript UI.
16
+ ## Quick Start
17
+
18
+ With WABuR you are up and running in minutes with only one file to modify to
19
+ specify attributes. It doesn't get any simpler. Heres how.
20
+
21
+ Install the wabur gem.
22
+
23
+ ```
24
+ $ gem install wabur
25
+ ```
26
+
27
+ Create a new project and `cd` into the directory.
19
28
 
20
- For further reading there is an [architecture page](pages/Architecture.md)
29
+ ```
30
+ $ wabur new --base blog Entry
31
+ $ cd blog
32
+ ```
21
33
 
22
- ## Try It!
34
+ Define attributes for the data elements along with the attributes that will be
35
+ displayed in a list view. Open `lib/ui_controller.rb` and modify by adding two
36
+ lines and changing one.
37
+
38
+ ```ruby
39
+ require 'wab/ui'
40
+
41
+ class UIController < WAB::UI::MultiFlow
42
+
43
+ def initialize(shell)
44
+ super
45
+ add_flow(WAB::UI::RestFlow.new(shell,
46
+ {
47
+ kind: 'Entry',
48
+ title: '',
49
+ content: "\n\n\n\n",
50
+ }, ['$ref', 'title']))
51
+ end
52
+ end
53
+ ```
54
+
55
+ Just run the `wabur` command from inside the project directory. Open
56
+ `localhost:6363` in a browser.
57
+
58
+ ```
59
+ $ wabur
60
+ ```
23
61
 
24
62
  Want to know more? A tutorial is available in the [tutorial](tutorial/README.md)
25
- directory.
63
+ directory. It takes a couple of minutes to go through the first lesson.
64
+
65
+ ## WABuR Benchmarks
66
+
67
+ <img src="pages/setup.svg" width="400">
68
+ <table>
69
+ <tr><td><img src="pages/throughput.svg" width="400"></td><td><img src="pages/latency.svg" width="400"></td></tr>
70
+ </table>
71
+
72
+ | Runner | Read Throughput | Read Latency | Create Throughput | Create Latency | Setup time |
73
+ | ------------- | --------------- | ------------ | ----------------- | -------------- | ---------- |
74
+ | WAB Pure Ruby | 2.8K Reads/sec | 1.4 msecs | 2.2K Creates/sec | 1.8 msecs | 1 minute |
75
+ | OpO-Rub | 212K Reads/sec | 0.09 msecs | 134K Creates/sec | 0.15 msecs | 1 minute |
76
+ | OpO Direct | 364K Reads/sec | 0.05 msecs | 157K Creates/sec | 0.13 msecs | 1 minute |
77
+ | Ruby on Rails | 123 Reads/sec | 175 msecs | ---- Creates/sec | ---- msecs | 20 minutes |
78
+ | Sinatra | 1.5K Reads/sec | 13 msecs | ---- Creates/sec | ---- msecs | 60 minutes |
26
79
 
27
80
  More interested in the benchmarks? Then take a look at the [benchmarks page](benchmarks/README.md).
28
81
 
82
+ ## Where to use WABuR
83
+
84
+ WABuR takes a different approach which opens up new possibilities for uses of
85
+ Ruby. Some examples that WABuR is suitable for are:
86
+
87
+ * Race Results - With the ability to handle massive traffic WABuR can keep up
88
+ with the load imposed by hundreds of thousands of users requesting results
89
+ during the race and with the ability to have multiple UIs realtime displays
90
+ can be different than those used to display results after the race.
91
+
92
+ * Voting Systems - Voting systems collect and forward results to central
93
+ servers. Loads are high during peak periods but well within the limits that
94
+ WABuR is able to handle. The ease extending WABuR makes it easy to
95
+ implement the migration of data to a central server or multiple servers.
96
+
97
+ * Operations Monitoring - Log using JSON and WABuR can be used to monitor and
98
+ query logs.
99
+
100
+ * Market Data - With the high throughput and low latency WABuR is a good
101
+ candidate for systems that need to display market data and processing of
102
+ market data.
103
+
104
+ * Mapping - Systems that display data on a map can use WABuRs flexibility
105
+ with regard to the UI.
106
+
107
+ * Directory - As an example of a quick and easy build, an directory or
108
+ address book can be started in minutes.
109
+
110
+ ## Rails
111
+
112
+ A natural question is *"What about Rails?"*. Rails is well established and has
113
+ a huge user base. WABuR is not a replacement for Rails. It is an alternative
114
+ for those who want to explore using JSON databases with a single-page dynamic
115
+ JavaScript UI.
116
+
29
117
  ## Participate and Contribute
30
118
 
31
119
  If you like the idea and want to help out, or become a core developer on the
@@ -36,20 +124,23 @@ and lets make something awesome together.
36
124
 
37
125
  These are the simple guidelines for contributing.
38
126
 
39
- 1. Coordinate with me first before getting started to avoid duplication of
127
+ 1. Take a look at the [architecture page](pages/Architecture.md) and the source code.
128
+
129
+ 2. Coordinate with me first before getting started to avoid duplication of
40
130
  effort or implementing something in conflict with the plans.
41
131
 
42
- 2. Branch off the develop branch and submit a PR.
132
+ 3. Branch off the `develop` branch and submit a PR.
43
133
 
44
- 3. Write unit tests.
134
+ 4. Write unit tests.
45
135
 
46
- 4. Write straight forward, clean, and simple code. No magic stuff, no monkey
136
+ 5. Write straight forward, clean, and simple code. No magic stuff, no monkey
47
137
  patching Ruby core classes, and no inheriting from core classes.
48
138
 
49
139
  ## References and Links
50
140
 
141
+ - [Agoo](https://github.com/ohler55/agoo) Web Server GemBuR.
51
142
  - [Oj](https://github.com/ohler55/oj) JSON parser used in WABuR.
52
143
  - [OpO](http://opo.technology) home of the Opo-Rub runner.
53
144
  - [Sass](http://sass-lang.com) used to build the reference implementation UI CSS.
54
145
  - [SystemJS Babel Plugin](https://github.com/systemjs/plugin-babel) also used to transpile JavaScript.
55
- - [SystemJS](https://github.com/systemjs/systemjs) used to convert JavaScript ES6 to ES5 in the browser.
146
+ - [SystemJS](https://github.com/systemjs/systemjs) used to convert JavaScript ES6 to ES5 in the browser.
data/bin/wabur CHANGED
@@ -107,6 +107,12 @@ options = {
107
107
  doc: 'HTTP Port to listen on.',
108
108
  arg: 'PORT',
109
109
  },
110
+ server: {
111
+ val: 'WEBrick',
112
+ type: String,
113
+ doc: 'Web server to use. Can be Agoo, Sinatra, or WEBRick.',
114
+ arg: 'GEM',
115
+ },
110
116
  },
111
117
  indent: {
112
118
  val: 0,
@@ -205,6 +205,24 @@ export class ObjectDisplay extends Display {
205
205
  value = obj[key];
206
206
  if (value instanceof Object) {
207
207
  this._setFields(`${path}.${key}`, value);
208
+ } else if (undefined != element.alt) {
209
+ switch (element.getAttribute('alt')) {
210
+ case 'local-time':
211
+ element.value = new Date(obj[key]).toLocaleString();
212
+ break;
213
+ case 'zulu-time':
214
+ element.value = new Date(obj[key]).toUTCString();
215
+ break;
216
+ case 'long-time':
217
+ element.value = new Date(obj[key]).toString();
218
+ break;
219
+ case 'date':
220
+ element.value = new Date(obj[key]).toDateString();
221
+ break;
222
+ default:
223
+ element.value = obj[key];
224
+ break;
225
+ }
208
226
  } else {
209
227
  element.value = obj[key];
210
228
  }
data/lib/wab.rb CHANGED
@@ -11,12 +11,17 @@ module WAB
11
11
  end
12
12
  raise ForbiddenError.new(path) if path.include?('..')
13
13
  path = File.expand_path("#{__dir__}/../export#{path}")
14
- File.open(path) { |f| f.read() }
14
+ begin
15
+ File.open(path) { |f| f.read() }
16
+ rescue Exception
17
+ nil
18
+ end
15
19
  end
16
20
 
17
21
  end
18
22
 
19
23
  require 'wab/controller'
24
+ require 'wab/racker'
20
25
  require 'wab/data'
21
26
  require 'wab/errors'
22
27
  require 'wab/open_controller'
@@ -25,3 +30,4 @@ require 'wab/shell_logger'
25
30
  require 'wab/utils'
26
31
  require 'wab/uuid'
27
32
  require 'wab/version'
33
+ require 'wab/client'
@@ -0,0 +1,145 @@
1
+
2
+ require 'net/http'
3
+ require 'oj'
4
+
5
+ module WAB
6
+
7
+ # A client for a WAB server. It is not specific to any particular
8
+ # runner. The client allows direct access to the server data. It is just
9
+ # another view implementation.
10
+ class Client
11
+ # Address of the WAB server.
12
+ attr_accessor :server_address
13
+ # The port the WAB serer is listening on.
14
+ attr_accessor :server_port
15
+ # Prefix to add to the URL path.
16
+ attr_accessor :path_prefix
17
+ # The URL path to execute TQL,
18
+ attr_accessor :tql_path
19
+ # The key for the type. The default is 'kind'.
20
+ attr_accessor :type_key
21
+ # If true the connection to the server is Keep-Alive.
22
+ attr_accessor :keep_alive
23
+
24
+ # Create a new Client for the server at +addr+ and +port+. The provided
25
+ # options should match the attribute names and types.
26
+ def initialize(addr, port, options={})
27
+ @server_address = addr
28
+ @server_port = port
29
+ @path_prefix = options.fetch(:path_prefix, '/v1/')
30
+ @tql_path = options.fetch(:tql_path, '/tql')
31
+ @type_key = options.fetch(:type_key, 'kind').to_sym
32
+ @keep_alive = !!options.fetch(:keep_alive, true)
33
+ @http = nil
34
+ end
35
+
36
+ # Create a new data object. If a query is provided it is treated as a
37
+ # check against an existing object with the same key/value pairs.
38
+ #
39
+ # On error an Exception will be raised.
40
+ #
41
+ # data:: the data to use as a new object.
42
+ # kind:: the kind of the data. If nil the kind is taken from the data
43
+ # query:: query parameters to match against existing instances. A match fails the insert.
44
+ def create(data, kind=nil, query=nil)
45
+ kind ||= data[@type_key] || data[@type_key.to_s]
46
+ send_request('PUT', kind, query, data)
47
+ end
48
+
49
+ # Return the objects according to the kind and query arguments. The
50
+ # following patterns supported:
51
+ #
52
+ # * query is a ref [12345] looks for MyType with reference ID of 12345
53
+ # * query is a Hash {name:fred,age:63} looks for all MyTypes with a name
54
+ # of 'fred' and an age of 63.
55
+ #
56
+ # kind:: the data type
57
+ # query:: query parameters as a Hash.
58
+ def read(kind, query=nil)
59
+ send_request('GET', kind, query, nil)
60
+ end
61
+
62
+ # Replaces the object data for the identified object. The query can be a
63
+ # reference to a specific object or a set of parameters to match.
64
+ #
65
+ # The return should be the identifiers for the object updated.
66
+ #
67
+ # On error an Exception should be raised.
68
+ #
69
+ # kind:: the data type
70
+ # data:: the data to use as a new object.
71
+ # query:: query parameters.
72
+ def update(kind, data, query)
73
+ raise ArgError.new('data') if data.nil?
74
+ raise ArgError.new('query') if query.nil?
75
+ send_request('POST', kind, query, data)
76
+ end
77
+
78
+ # Deletes the data for the identified object(s). The query can be a
79
+ # reference to a specific object or a set of parameters to match.
80
+ #
81
+ # The return is the identifiers for the object updated.
82
+ #
83
+ # On error an Exception should be raised.
84
+ #
85
+ # kind:: the data type
86
+ # query:: query parameters.
87
+ def delete(kind, query=nil)
88
+ send_request('DELETE', kind, query, nil)
89
+ end
90
+
91
+ # Request the server evaluate a TQL query.
92
+ #
93
+ # tql:: query to evaluate.
94
+ def find(tql)
95
+ raise ArgError.new('tql') if tql.nil?
96
+ send_request('POST', 'tql', nil, tql)
97
+ end
98
+
99
+ private
100
+
101
+ def connect
102
+ @http ||= Net::HTTP.new(@server_address, @server_port)
103
+ end
104
+
105
+ def form_path(kind, query)
106
+ return @tql_path if kind == 'tql'
107
+ path = "#{@path_prefix}#{kind}"
108
+ if query.is_a?(Hash)
109
+ first = true
110
+ query.each_pair { |k,v|
111
+ if first
112
+ path << '?'
113
+ first = false
114
+ else
115
+ path << '&'
116
+ end
117
+ path << "#{k}=#{v}"
118
+ }
119
+ elsif !query.nil?
120
+ path << '/'
121
+ path << query.to_s
122
+ end
123
+ path
124
+ end
125
+
126
+ def send_request(method, kind, query, content)
127
+ connect
128
+ header = {'Connection' => (@keep_alive ? 'Keep-Alive' : 'Close') }
129
+ unless content.nil?
130
+ if content.is_a?(Data)
131
+ content = content.json
132
+ else
133
+ content = Oj.dump(content, mode: :wab, indent: 0)
134
+ end
135
+ header['Content-Type'] = 'application/json'
136
+ resp = @http.send_request(method, form_path(kind, query), content, header)
137
+ else
138
+ resp = @http.send_request(method, form_path(kind, query))
139
+ end
140
+ raise Error.new(resp.body) unless resp.is_a?(Net::HTTPOK)
141
+ Oj.load(resp.body, mode: :wab)
142
+ end
143
+
144
+ end # Client
145
+ end # WAB
@@ -21,7 +21,7 @@ module WAB
21
21
  end
22
22
 
23
23
  # Handler for paths that do not match the REST pattern or for unregistered
24
- # types. Only called on the default controller.
24
+ # types.
25
25
  #
26
26
  # Processing result are passed back to the view which forward the result
27
27
  # on to the requester. The result, if not nil, should be a Data instance.
@@ -28,4 +28,10 @@ module WAB
28
28
  end
29
29
  end
30
30
 
31
+ class ArgError < Error
32
+ def initialize(arg)
33
+ super("#{arg} missing or nil")
34
+ end
35
+ end
36
+
31
37
  end # WAB
@@ -26,9 +26,9 @@ require 'wab/impl/expr'
26
26
  require 'wab/impl/path_expr'
27
27
  require 'wab/impl/bool_expr'
28
28
  require 'wab/impl/shell'
29
- require 'wab/impl/export_proxy'
30
29
  require 'wab/impl/utils'
31
30
  require 'wab/impl/init'
31
+ require 'wab/impl/rack_error'
32
32
 
33
33
  # Require the concrete Expr subclasses so a mapping table can be created for
34
34
  # the parser.
@@ -0,0 +1,18 @@
1
+
2
+ require 'wab'
3
+
4
+ module WAB
5
+ module Impl
6
+
7
+ # The Agoo module contains handlers for the Agoo web server.
8
+ module Agoo
9
+
10
+ end # Agoo
11
+ end # Impl
12
+ end # WAB
13
+
14
+ require 'wab/impl/agoo/sender'
15
+ require 'wab/impl/agoo/handler'
16
+ require 'wab/impl/agoo/tql_handler'
17
+ require 'wab/impl/agoo/export_proxy'
18
+ require 'wab/impl/agoo/server'
@@ -0,0 +1,55 @@
1
+
2
+ module WAB
3
+ module Impl
4
+ module Agoo
5
+
6
+ # A handler that provides missing files in an assets directory where the
7
+ # files are the wab and wab UI files.
8
+ class ExportProxy
9
+ include Sender
10
+
11
+ def initialize(shell)
12
+ @shell = shell
13
+ end
14
+
15
+ def on_request(req, res)
16
+ path = (req.script_name + req.path_info)
17
+ query = parse_query(req.query_string)
18
+ path = '/index.html' if '/' == path
19
+ mime = nil
20
+ index = path.rindex('.')
21
+ unless index.nil?
22
+ mime = {
23
+ 'css' => 'text/css',
24
+ 'eot' => 'application/vnd.ms-fontobject',
25
+ 'es5' => 'application/javascript',
26
+ 'es6' => 'application/javascript',
27
+ 'gif' => 'image/gif',
28
+ 'html' => 'text/html',
29
+ 'ico' => 'image/x-icon',
30
+ 'jpeg' => 'image/jpeg',
31
+ 'jpg' => 'image/jpeg',
32
+ 'js' => 'application/javascript',
33
+ 'json' => 'application/json',
34
+ 'png' => 'image/png',
35
+ 'sse' => 'text/plain',
36
+ 'svg' => 'image/svg+xml',
37
+ 'ttf' => 'application/font-sfnt',
38
+ 'txt' => 'text/plain',
39
+ 'woff' => 'application/font-woff',
40
+ 'woff2' => 'font/woff2',
41
+ }[path[index + 1..-1].downcase]
42
+ end
43
+ mime = 'text/plain' if mime.nil?
44
+ content = WAB.get_export(path)
45
+ res.code = 200
46
+ res['Content-Type'] = mime
47
+ res.body = content
48
+ rescue Exception => e
49
+ send_error(e, res)
50
+ end
51
+
52
+ end # ExportProxy
53
+ end # Agoo
54
+ end # Impl
55
+ end # WAB