wabur 0.4.0d1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -66
  3. data/bin/wabur +48 -11
  4. data/export/assets/css/wab.css +2 -0
  5. data/export/assets/css/wab.css.map +7 -0
  6. data/export/assets/fonts/wabfont/README +18 -0
  7. data/export/assets/fonts/wabfont/fonts/WAB.eot +0 -0
  8. data/export/assets/fonts/wabfont/fonts/WAB.svg +25 -0
  9. data/export/assets/fonts/wabfont/fonts/WAB.ttf +0 -0
  10. data/export/assets/fonts/wabfont/fonts/WAB.woff +0 -0
  11. data/export/assets/fonts/wabfont/selection.json +460 -0
  12. data/export/assets/fonts/wabfont/style.css +74 -0
  13. data/export/assets/js/transpile.js +8 -0
  14. data/export/assets/js/ui.js +377 -0
  15. data/export/assets/js/vendor/plugin-babel/babel-helpers.js +558 -0
  16. data/export/assets/js/vendor/plugin-babel/babel-helpers/asyncGenerator.js +112 -0
  17. data/export/assets/js/vendor/plugin-babel/babel-helpers/asyncGeneratorDelegate.js +51 -0
  18. data/export/assets/js/vendor/plugin-babel/babel-helpers/asyncIterator.js +14 -0
  19. data/export/assets/js/vendor/plugin-babel/babel-helpers/asyncToGenerator.js +28 -0
  20. data/export/assets/js/vendor/plugin-babel/babel-helpers/classCallCheck.js +5 -0
  21. data/export/assets/js/vendor/plugin-babel/babel-helpers/createClass.js +17 -0
  22. data/export/assets/js/vendor/plugin-babel/babel-helpers/defaults.js +14 -0
  23. data/export/assets/js/vendor/plugin-babel/babel-helpers/defineEnumerableProperties.js +10 -0
  24. data/export/assets/js/vendor/plugin-babel/babel-helpers/defineProperty.js +14 -0
  25. data/export/assets/js/vendor/plugin-babel/babel-helpers/extends.js +13 -0
  26. data/export/assets/js/vendor/plugin-babel/babel-helpers/get.js +24 -0
  27. data/export/assets/js/vendor/plugin-babel/babel-helpers/inherits.js +15 -0
  28. data/export/assets/js/vendor/plugin-babel/babel-helpers/instanceof.js +7 -0
  29. data/export/assets/js/vendor/plugin-babel/babel-helpers/interopRequireDefault.js +5 -0
  30. data/export/assets/js/vendor/plugin-babel/babel-helpers/interopRequireWildcard.js +16 -0
  31. data/export/assets/js/vendor/plugin-babel/babel-helpers/jsx.js +42 -0
  32. data/export/assets/js/vendor/plugin-babel/babel-helpers/newArrowCheck.js +5 -0
  33. data/export/assets/js/vendor/plugin-babel/babel-helpers/objectDestructuringEmpty.js +3 -0
  34. data/export/assets/js/vendor/plugin-babel/babel-helpers/objectWithoutProperties.js +11 -0
  35. data/export/assets/js/vendor/plugin-babel/babel-helpers/possibleConstructorReturn.js +7 -0
  36. data/export/assets/js/vendor/plugin-babel/babel-helpers/selfGlobal.js +1 -0
  37. data/export/assets/js/vendor/plugin-babel/babel-helpers/set.js +21 -0
  38. data/export/assets/js/vendor/plugin-babel/babel-helpers/slicedToArray.js +37 -0
  39. data/export/assets/js/vendor/plugin-babel/babel-helpers/slicedToArrayLoose.js +17 -0
  40. data/export/assets/js/vendor/plugin-babel/babel-helpers/taggedTemplateLiteral.js +7 -0
  41. data/export/assets/js/vendor/plugin-babel/babel-helpers/taggedTemplateLiteralLoose.js +4 -0
  42. data/export/assets/js/vendor/plugin-babel/babel-helpers/temporalRef.js +7 -0
  43. data/export/assets/js/vendor/plugin-babel/babel-helpers/temporalUndefined.js +1 -0
  44. data/export/assets/js/vendor/plugin-babel/babel-helpers/toArray.js +3 -0
  45. data/export/assets/js/vendor/plugin-babel/babel-helpers/toConsumableArray.js +9 -0
  46. data/export/assets/js/vendor/plugin-babel/babel-helpers/typeof.js +5 -0
  47. data/export/assets/js/vendor/plugin-babel/plugin-babel.js +222 -0
  48. data/export/assets/js/vendor/plugin-babel/regenerator-runtime.js +685 -0
  49. data/export/assets/js/vendor/plugin-babel/systemjs-babel-browser.js +1 -0
  50. data/export/assets/js/vendor/plugin-babel/systemjs-babel-node.js +55086 -0
  51. data/export/assets/js/vendor/systemjs/system-production.js +4 -0
  52. data/export/assets/js/vendor/systemjs/system-production.js.map +1 -0
  53. data/export/assets/js/vendor/systemjs/system-production.src.js +1711 -0
  54. data/export/assets/js/vendor/systemjs/system-production.src.js.map +1 -0
  55. data/export/assets/js/vendor/systemjs/system.js +4 -0
  56. data/export/assets/js/vendor/systemjs/system.js.map +1 -0
  57. data/export/assets/js/vendor/systemjs/system.src.js +4000 -0
  58. data/export/assets/js/vendor/systemjs/system.src.js.map +1 -0
  59. data/export/assets/js/wab.js +109 -0
  60. data/export/index.html +41 -0
  61. data/lib/wab.rb +13 -0
  62. data/lib/wab/controller.rb +2 -2
  63. data/lib/wab/errors.rb +12 -0
  64. data/lib/wab/impl.rb +2 -0
  65. data/lib/wab/impl/configuration.rb +24 -6
  66. data/lib/wab/impl/export_proxy.rb +39 -0
  67. data/lib/wab/impl/exprs/regex.rb +1 -1
  68. data/lib/wab/impl/handler.rb +14 -14
  69. data/lib/wab/impl/init.rb +108 -0
  70. data/lib/wab/impl/model.rb +4 -3
  71. data/lib/wab/impl/shell.rb +14 -4
  72. data/lib/wab/impl/templates/opo-rub.conf.template +253 -0
  73. data/lib/wab/impl/templates/opo.conf.template +224 -0
  74. data/lib/wab/impl/templates/spawn.rb.template +42 -0
  75. data/lib/wab/impl/templates/ui_controller.rb.template +12 -0
  76. data/lib/wab/impl/templates/wabur.conf.template +40 -0
  77. data/lib/wab/ui.rb +19 -0
  78. data/lib/wab/ui/create.rb +26 -0
  79. data/lib/wab/ui/display.rb +34 -0
  80. data/lib/wab/ui/flow.rb +54 -0
  81. data/lib/wab/ui/list.rb +59 -0
  82. data/lib/wab/ui/multi_flow.rb +23 -0
  83. data/lib/wab/ui/rest_flow.rb +89 -0
  84. data/lib/wab/ui/update.rb +27 -0
  85. data/lib/wab/ui/view.rb +62 -0
  86. data/lib/wab/utils.rb +4 -4
  87. data/lib/wab/version.rb +1 -1
  88. data/pages/Architecture.md +104 -1
  89. data/test/test_configuration.rb +1 -1
  90. data/test/test_impl.rb +1 -0
  91. data/test/test_init.rb +33 -0
  92. metadata +94 -7
  93. data/pages/Plan.md +0 -27
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $VERBOSE = true
5
+
6
+ $: << __dir__
7
+ while (index = ARGV.index('-I'))
8
+ _, path = ARGV.slice!(index, 2)
9
+ $: << path
10
+ end
11
+
12
+ require 'optparse'
13
+ require 'wab'
14
+ require 'wab/io'
15
+
16
+ # This app is expected to be spawned from a WAB runner. This demonstrates one
17
+ # possible mode for running WAB applications. It is the least performant of
18
+ # the WAB run options.
19
+
20
+ $verbose = false
21
+ $thread_count = 1
22
+ $opts = OptionParser.new("Usage: sample [options]
23
+
24
+ Acts as a remote Controller in a WAB deployment. Input is from stdin and output
25
+ is on stdout. If verbosity is turned on it is sent to stderr.
26
+ ")
27
+ $opts.on('-v', 'verbose output on stderr') { $verbose = true }
28
+ $opts.on('-t', '--thread-count Integer', Integer, 'thread count') { |t| $thread_count = t }
29
+ $opts.on('-h', '--help', 'show this page') { $stderr.puts $opts.help; Process.exit!(0) }
30
+
31
+ $opts.parse(ARGV)
32
+
33
+ shell = WAB::IO::Shell.new($thread_count, 'kind', 1)
34
+ shell.logger.level = Logger::INFO if $verbose
35
+
36
+ shell.register_controller('ui', UIController.new(shell))%{controllers}
37
+
38
+ begin
39
+ shell.start
40
+ rescue Interrupt
41
+ # ignore
42
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'wab/ui'
4
+
5
+ class UIController < WAB::UI::MultiFlow
6
+
7
+ def initialize(shell)
8
+ super
9
+ %{rest_flows}
10
+ end
11
+
12
+ end # UIController
@@ -0,0 +1,40 @@
1
+ # wabur.conf
2
+
3
+ # Configuration file for the Pure Ruby WAB Runner.
4
+
5
+ # The directory to store the runner data (relative to source directory).
6
+ store.dir = wabur/data
7
+
8
+ # URL path prefix for the data records.
9
+ path_prefix = /v1
10
+
11
+ # Field to expect the type, class, or kind of record in the stored JSON.
12
+ type_key = kind
13
+
14
+ # HTTP site directory.
15
+ http.dir = site
16
+
17
+ # HTTP port to listen on.
18
+ http.port = 6363
19
+
20
+ # Logging verbosity. Can be ERROR, WARN, INFO, or DEBUG.
21
+ verbosity = WARN
22
+
23
+ # The Ruby requires. Generally what ever class in the lib directory that
24
+ # should be imported.
25
+ require = ui_controller
26
+
27
+ # If export_proxy is true the wab default index.html, CSS, JavaScript, and
28
+ # fonts are loaded from the gem's export directory is they are not found in
29
+ # the site directory.
30
+ export_proxy = true
31
+
32
+ # The number of spaces to indent the JSON written to disk by the model and the
33
+ # JSON sent as a response to View requests.
34
+ indent = 0
35
+
36
+ # Handlers for each type of record and for the UI shich are Ruby generated UI
37
+ # configuration records.
38
+ handler.0.type = ui
39
+ handler.0.handler = UIController
40
+ %{handlers}
@@ -0,0 +1,19 @@
1
+
2
+ require 'wab'
3
+
4
+ module WAB
5
+ # Web Application Builder reference implemenation UI.
6
+ module UI
7
+ end
8
+ end
9
+
10
+ require 'wab/ui/display'
11
+ require 'wab/ui/flow'
12
+ require 'wab/ui/multi_flow'
13
+
14
+ # These are the classes needed for the REST displays/flow.
15
+ require 'wab/ui/rest_flow'
16
+ require 'wab/ui/list'
17
+ require 'wab/ui/view'
18
+ require 'wab/ui/create'
19
+ require 'wab/ui/update'
@@ -0,0 +1,26 @@
1
+
2
+ module WAB
3
+ module UI
4
+
5
+ # An object create display.
6
+ class Create < View
7
+
8
+ # Create an instance that will generate the HTML for a display.
9
+ def initialize(kind, id, template, transitions)
10
+ super(kind, id, template, transitions, 'ui.Create')
11
+ end
12
+
13
+ # Returns the HTML for a display.
14
+ def html
15
+ html = %{<div class="obj-form-frame"><table class="obj-form">}
16
+ html = append_fields(html, @name, template, false)
17
+ html << '</table>'
18
+ html << %{<div class="btn" id="#{@name}.save_button"><span>Save</span></div>}
19
+ html << %{<div class="btn" style="float:right;" id="#{@name}.cancel_button"><span>Cancel</span></div>}
20
+ html << '</div>'
21
+ end
22
+
23
+ end # Create
24
+ end # UI
25
+ end # WAB
26
+
@@ -0,0 +1,34 @@
1
+
2
+ module WAB
3
+ module UI
4
+
5
+ # Base class for other displays.
6
+ class Display
7
+
8
+ attr_reader :name
9
+ attr_reader :kind
10
+ attr_accessor :template
11
+ attr_accessor :display_class
12
+ attr_accessor :transitions
13
+
14
+ def initialize(kind, name, template, transitions, display_class)
15
+ @kind = kind
16
+ @name = name
17
+ @template = template
18
+ @display_class = display_class
19
+ @transitions = transitions
20
+ end
21
+
22
+ def spec
23
+ {
24
+ name: @name,
25
+ kind: @kind,
26
+ display_class: @display_class,
27
+ transitions: @transitions,
28
+ }
29
+ end
30
+
31
+ end # Display
32
+ end # UI
33
+ end # WAB
34
+
@@ -0,0 +1,54 @@
1
+
2
+ module WAB
3
+ module UI
4
+
5
+ # A controller that provides a description of the UI for the WAB UI
6
+ # reference implementation.
7
+ class Flow < WAB::Controller
8
+
9
+ attr_accessor :entry
10
+ attr_reader :displays
11
+
12
+ def initialize(shell)
13
+ super
14
+ @displays = {}
15
+ end
16
+
17
+ # Adds a display to the flow.
18
+ def add_display(display, entry=false)
19
+ name = display.name
20
+ raise DuplicateError.new(name) if @displays.has_key?(name)
21
+ @displays[name] = display
22
+ @entry = name if entry
23
+ end
24
+
25
+ def get_display(name)
26
+ @displays[name]
27
+ end
28
+
29
+ # Returns a description of the UI to be used. If a display name is
30
+ # includd in the path thenn just that display description is returned.
31
+ #
32
+ # path:: array of tokens in the path.
33
+ def read(path, _query)
34
+ results = []
35
+ if @shell.path_pos + 2 == path.length
36
+ # Return the description of the named display.
37
+ name = path[@shell.path_pos + 1]
38
+ display = get_display(name)
39
+ display[:entry] = true if !display.nil? && display.name == @entry
40
+ results << {id: name, data: display.spec} unless display.nil?
41
+ else
42
+ @displays.each_value { |display|
43
+ spec = display.spec
44
+ spec[:entry] = true if display.name == @entry
45
+ results << spec
46
+ }
47
+ end
48
+ @shell.data({code: 0, results: results})
49
+ end
50
+
51
+ end # Flow
52
+ end # UI
53
+ end # WAB
54
+
@@ -0,0 +1,59 @@
1
+
2
+ module WAB
3
+ module UI
4
+
5
+ # Represents a list display.
6
+ class List < Display
7
+
8
+ attr_accessor :list_paths
9
+
10
+ def initialize(kind, id, template, list_paths, transitions)
11
+ super(kind, id, template, transitions, 'ui.List')
12
+ @list_paths = list_paths
13
+ end
14
+
15
+ def spec
16
+ ui_spec = super
17
+ ui_spec[:table] = html_table
18
+ ui_spec[:row] = html_row
19
+ ui_spec
20
+ end
21
+
22
+ # Returns an HTML string to be used as the table of a list of
23
+ # objects. The table must have an +id+ attribute value of the +name+
24
+ # argument. Generally the column header should include the list_paths or
25
+ # more friendly alternatives. If a create button is desired then an
26
+ # element with the name joined with '.create_button' should be the +id+ of
27
+ # the element.
28
+ def html_table
29
+ html = %{<div class="table-wrapper"><h2 style="float: left; margin-top: 2px">#{@kind} List</h2><div class="btn" style="float: left" id="#{@name}.create_button"><span>Create</span></div><table class="obj-list-table" id="#{@name}.table"><tr>}
30
+ # The column headers.
31
+ @list_paths.map { |path| html << "<th>#{path.capitalize}</th>" }
32
+ # Add the view, edit, and delete buttons header.
33
+ html << %{<th colspan="3">Actions</th>}
34
+ html << %{</tr></table></div>}
35
+ end
36
+
37
+ # Returns an HTML string to be used as a row in a table of a list of
38
+ # objects. Each column element should include the list_paths identifier
39
+ # as a +path+ element attribute.
40
+ #
41
+ # Each row can contain the buttons +view_button+, +edit_button+, and
42
+ # +delete_button+. These will be used to set up the transitions.
43
+ def html_row
44
+ html = '<tr>'
45
+ @list_paths.map { |path| html << %{<td class="obj-list" path="#{path}"></td>} }
46
+ buttons = [
47
+ { title: 'View', icon: 'icon icon-eye', cn: 'actions' },
48
+ { title: 'Edit', icon: 'icon icon-pencil', cn: 'actions' },
49
+ { title: 'Delete', icon: 'icon icon-trash-o', cn: 'actions delete' }
50
+ ].map { |spec|
51
+ html << %{<td class="#{spec[:cn]}"><span class="#{spec[:icon]}" title="#{spec[:title]}"></span></td>}
52
+ }
53
+ html << '</tr>'
54
+ end
55
+
56
+ end # List
57
+ end # UI
58
+ end # WAB
59
+
@@ -0,0 +1,23 @@
1
+
2
+ module WAB
3
+ module UI
4
+
5
+ # A Flow controller that merges multiple flows into one single flow.
6
+ class MultiFlow < Flow
7
+
8
+ # Create a new instance that can be used to merge multiple flows into
9
+ # one single flow.
10
+ #
11
+ # shell:: shell containing the instancec
12
+ def initialize(shell)
13
+ super(shell)
14
+ end
15
+
16
+ def add_flow(flow)
17
+ flow.displays.each_pair { |name,display| @displays[name] = display }
18
+ @entry = flow.entry if @entry.nil?
19
+ end
20
+
21
+ end # MultiFlow
22
+ end # UI
23
+ end # WAB
@@ -0,0 +1,89 @@
1
+
2
+ module WAB
3
+ module UI
4
+
5
+ # A Flow controller that builds up a set of displays and provides those display descriptions
6
+ # when a read is called. The REST UI is built based on the template and
7
+ # list_paths provided in the initializer.
8
+ #
9
+ # The display can be modified or subclassing by changing the View, Create,
10
+ # and Update classes.
11
+ class RestFlow < Flow
12
+
13
+ # Creae a new instance based on the record template and path for the
14
+ # list display.
15
+ #
16
+ # shell:: shell containing the instancec
17
+ # template:: and example object with default values
18
+ # list_paths:: paths to values for the list display
19
+ def initialize(shell, template, list_paths)
20
+ super(shell)
21
+ kind = template[:kind]
22
+ raise WAB::ParseError.new('kind field missing from object template') if kind.nil?
23
+ add_list(kind, template, list_paths)
24
+ add_view(kind, template)
25
+ add_create(kind, template)
26
+ add_update(kind, template)
27
+ end
28
+
29
+ # Add a listdisplay to the spec delivered to the UI.
30
+ #
31
+ # kind:: the type of record to create the list for
32
+ # template:: and example object with default values
33
+ # list_paths:: paths to values for the list display
34
+ def add_list(kind, template, list_paths)
35
+ id = "#{kind}.list"
36
+ transitions = {
37
+ create: "#{kind}.create",
38
+ view: "#{kind}.view",
39
+ edit: "#{kind}.update",
40
+ delete: id,
41
+ }
42
+ add_display(List.new(kind, id, template, list_paths, transitions), true)
43
+ end
44
+
45
+ # Adds an object view specification.
46
+ #
47
+ # kind:: the type of record to create the list for
48
+ # template:: and example object with default values
49
+ def add_view(kind, template)
50
+ id = "#{kind}.view"
51
+ transitions = {
52
+ edit: "#{kind}.update",
53
+ list: "#{kind}.list",
54
+ delete: "#{kind}.list",
55
+ }
56
+ add_display(View.new(kind, id, template, transitions))
57
+ end
58
+
59
+ # Adds an object creation specification.
60
+ #
61
+ # kind:: the type of record to create the list for
62
+ # template:: and example object with default values
63
+ def add_create(kind, template)
64
+ id = "#{kind}.create"
65
+ transitions = {
66
+ save: "#{kind}.view",
67
+ cancel: "#{kind}.list",
68
+ }
69
+ add_display(Create.new(kind, id, template, transitions))
70
+ end
71
+
72
+ # Adds an object update specification.
73
+ #
74
+ # kind:: the type of record to create the list for
75
+ # template:: and example object with default values
76
+ def add_update(kind, template)
77
+ id = "#{kind}.update"
78
+ transitions = {
79
+ save: "#{kind}.view",
80
+ cancel: "#{kind}.view",
81
+ list: "#{kind}.list",
82
+ delete: "#{kind}.list",
83
+ }
84
+ add_display(Update.new(kind, id, template, transitions))
85
+ end
86
+
87
+ end # RestFlow
88
+ end # UI
89
+ end # WAB
@@ -0,0 +1,27 @@
1
+
2
+ module WAB
3
+ module UI
4
+
5
+ # An object update display.
6
+ class Update < View
7
+
8
+ # TBD pass in fields for the update
9
+ def initialize(kind, id, template, transitions)
10
+ super(kind, id, template, transitions, 'ui.Update')
11
+ end
12
+
13
+ def html
14
+ html = %{<div class="obj-form-frame"><table class="obj-form">}
15
+ html = append_fields(html, @name, template, false)
16
+ html << '</table>'
17
+ html << %{<div class="btn" id="#{@name}.save_button"><span>Save</span></div>}
18
+ html << %{<div class="btn" id="#{@name}.cancel_button"><span>Cancel</span></div>}
19
+ html << %{<div class="btn" id="#{@name}.list_button"><span>List</span></div>}
20
+ html << %{<div class="btn delete-btn" style="float:right;" id="#{@name}.delete_button"><span>Delete</span></div>}
21
+ html << '</div>'
22
+ end
23
+
24
+ end # Update
25
+ end # UI
26
+ end # WAB
27
+
@@ -0,0 +1,62 @@
1
+
2
+ module WAB
3
+ module UI
4
+
5
+ # An object view display.
6
+ class View < Display
7
+
8
+ def initialize(kind, id, template, transitions, display_class='ui.View')
9
+ super(kind, id, template, transitions, display_class)
10
+ end
11
+
12
+ def spec
13
+ ui_spec = super
14
+ ui_spec[:html] = html
15
+ ui_spec
16
+ end
17
+
18
+ def html
19
+ html = %{<div class="obj-form-frame readonly"><table class="obj-form">}
20
+ html = append_fields(html, @name, template, true)
21
+ html << '</table>'
22
+ html << %{<div class="btn" id="#{@name}.edit_button"><span>Edit</span></div>}
23
+ html << %{<div class="btn" id="#{@name}.list_button"><span>List</span></div>}
24
+ html << %{<div class="btn delete-btn" style="float:right;" id="#{@name}.delete_button"><span>Delete</span></div>}
25
+ html << '</div>'
26
+ end
27
+
28
+ def append_fields(html, path, template, readonly)
29
+ disabled = readonly ? ' disabled="disabled"' : ''
30
+ readonly = readonly ? ' readonly' : ''
31
+ template.each_pair { |id,value|
32
+ next if :kind == id
33
+ input = nil
34
+ input_id = "#{path}.#{id}"
35
+ text_input = %{<input class="form-field" id="#{input_id}" type="text" value="#{value}" #{readonly}>}
36
+
37
+ if value.is_a?(String)
38
+ count = value.count("\n")
39
+ if 0 < count # a text area
40
+ input = %{<textarea class="form-field" id="#{input_id}" rows="#{count}" #{readonly}>#{value.strip}</textarea>}
41
+ else
42
+ input = text_input
43
+ end
44
+ elsif value.is_a?(TrueClass)
45
+ input = %{<input class="form-field" id="#{input_id}" type="checkbox"#{disabled} checked>}
46
+ elsif value.is_a?(FalseClass)
47
+ input = %{<input class="form-field" id="#{input_id}" type="checkbox"#{disabled}>}
48
+ elsif value.is_a?(Integer) || WAB::Utils.pre_24_fixnum?(value) || value.is_a?(Number)
49
+ input = %{<input class="form-field" id="#{input_id}" type="number" value="#{value}" #{readonly}>}
50
+ elsif value.is_a?(Hash)
51
+ append_fields(html, input_id, value)
52
+ else
53
+ input = text_input
54
+ end
55
+ html << %{<tr><td class="field-label">#{id.capitalize}</td><td>#{input}</td></tr>}
56
+ }
57
+ html
58
+ end
59
+
60
+ end # View
61
+ end # UI
62
+ end # WAB