pinstripe 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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.pinstripe +3 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +24 -0
  6. data/LICENSE +20 -0
  7. data/README.md +5 -0
  8. data/Rakefile +10 -0
  9. data/exe/pinstripe +4 -0
  10. data/lib/pinstripe/call_handler.rb +12 -0
  11. data/lib/pinstripe/command.rb +20 -0
  12. data/lib/pinstripe/commands/list_commands.rb +16 -0
  13. data/lib/pinstripe/commands/start_console.rb +14 -0
  14. data/lib/pinstripe/concern.rb +19 -0
  15. data/lib/pinstripe/database/row.rb +48 -0
  16. data/lib/pinstripe/database/table.rb +131 -0
  17. data/lib/pinstripe/database/table_alias_manager.rb +20 -0
  18. data/lib/pinstripe/database/union.rb +20 -0
  19. data/lib/pinstripe/database.rb +56 -0
  20. data/lib/pinstripe/helper.rb +20 -0
  21. data/lib/pinstripe/helpers.rb +8 -0
  22. data/lib/pinstripe/inflector/inflections.rb +122 -0
  23. data/lib/pinstripe/inflector.rb +28 -0
  24. data/lib/pinstripe/monkey_patches/pluralize.rb +8 -0
  25. data/lib/pinstripe/monkey_patches/require_all.rb +18 -0
  26. data/lib/pinstripe/monkey_patches/singularize.rb +8 -0
  27. data/lib/pinstripe/monkey_patches/to_params_hash.rb +20 -0
  28. data/lib/pinstripe/params_hash.rb +44 -0
  29. data/lib/pinstripe/project.rb +29 -0
  30. data/lib/pinstripe/registry.rb +46 -0
  31. data/lib/pinstripe/resource_factories/call_handler.rb +8 -0
  32. data/lib/pinstripe/resource_factories/env.rb +9 -0
  33. data/lib/pinstripe/resource_factories/environment.rb +8 -0
  34. data/lib/pinstripe/resource_factories/project.rb +8 -0
  35. data/lib/pinstripe/resource_factory.rb +32 -0
  36. data/lib/pinstripe/resource_provider.rb +50 -0
  37. data/lib/pinstripe/version.rb +3 -0
  38. data/lib/pinstripe/workspace.rb +8 -0
  39. data/lib/pinstripe.rb +24 -0
  40. data/package.json +15 -0
  41. data/pinstripe +4 -0
  42. data/pinstripe.gemspec +38 -0
  43. data/rollup.config.js +19 -0
  44. data/web/javascripts/_pinstripe/event_wrapper.js +29 -0
  45. data/web/javascripts/_pinstripe/index.js +3 -0
  46. data/web/javascripts/_pinstripe/initialize.js +29 -0
  47. data/web/javascripts/_pinstripe/node_wrapper.js +354 -0
  48. data/web/javascripts/_pinstripe/node_wrappers/anchor.js +56 -0
  49. data/web/javascripts/_pinstripe/node_wrappers/document.js +27 -0
  50. data/web/javascripts/_pinstripe/node_wrappers/form.js +52 -0
  51. data/web/javascripts/_pinstripe/node_wrappers/frame.js +72 -0
  52. data/web/javascripts/_pinstripe/node_wrappers/index.js +8 -0
  53. data/web/javascripts/_pinstripe/node_wrappers/input.js +23 -0
  54. data/web/javascripts/_pinstripe/node_wrappers/modal.js +36 -0
  55. data/web/javascripts/_pinstripe/node_wrappers/script.js +17 -0
  56. data/web/javascripts/_pinstripe/string_reader.js +24 -0
  57. data/web/javascripts/_pinstripe/url.js +94 -0
  58. data/web/javascripts/_pinstripe/util/benchmark.js +10 -0
  59. data/web/javascripts/_pinstripe/util/capitalize.js +4 -0
  60. data/web/javascripts/_pinstripe/util/index.js +4 -0
  61. data/web/javascripts/_pinstripe/util/unescape_html.js +13 -0
  62. data/web/javascripts/_pinstripe/virtual_node.js +156 -0
  63. data/yarn.lock +72 -0
  64. metadata +162 -0
@@ -0,0 +1,354 @@
1
+
2
+ import { capitalize } from './util'
3
+ import { VirtualNode } from './virtual_node'
4
+ import { EventWrapper } from './event_wrapper'
5
+
6
+ const nodeWrappers = [];
7
+
8
+ export class NodeWrapper {
9
+
10
+ static get selector(){ return `.${this.name}` }
11
+
12
+ static register(){
13
+ const klass = this
14
+
15
+ nodeWrappers.unshift(klass)
16
+
17
+ Object.defineProperty(NodeWrapper.prototype, klass.name, {
18
+ get: function(){
19
+ let current = this.parent
20
+ while(current){
21
+ if(current instanceof klass){
22
+ return current
23
+ }
24
+ current = current.parent
25
+ }
26
+ }
27
+ })
28
+
29
+ Object.defineProperty(NodeWrapper.prototype, `is${capitalize(klass.name)}`, {
30
+ get: function(){
31
+ return this instanceof klass
32
+ }
33
+ })
34
+ }
35
+
36
+ static instanceFor(node){
37
+ if(!node.$p){
38
+ node.$p = new NodeWrapper(node)
39
+ nodeWrappers.some((klass) => {
40
+ if(node.$p.is(klass.selector)){
41
+ node.$p = new klass(node)
42
+ return true
43
+ }
44
+ })
45
+ }
46
+ return node.$p
47
+ }
48
+
49
+ constructor(node){
50
+ this.node = node
51
+ this.$registeredEventListeners = []
52
+ }
53
+
54
+
55
+ get type(){
56
+ return this.node instanceof DocumentType ? '#doctype' : this.node.nodeName.toLowerCase()
57
+ }
58
+
59
+ get attributes(){
60
+ const out = {}
61
+ if(this.node.attributes){
62
+ for(let i = 0; i < this.node.attributes.length; i++){
63
+ out[this.node.attributes[i].name] = this.node.attributes[i].value
64
+ }
65
+ }
66
+ return out
67
+ }
68
+
69
+ get text(){
70
+ return this.node.textContent
71
+ }
72
+
73
+ get realParent(){
74
+ return this.node.parentNode ? this.constructor.instanceFor(this.node.parentNode) : null
75
+ }
76
+
77
+ get parent(){
78
+ if(this.$parent){
79
+ return this.$parent
80
+ }
81
+ return this.realParent
82
+ }
83
+
84
+ get parents(){
85
+ const out = []
86
+ let current = this
87
+ while(current.parent){
88
+ current = current.parent
89
+ out.push(parent)
90
+ }
91
+ return out
92
+ }
93
+
94
+ get children(){
95
+ return [...this.node.childNodes].map(
96
+ node => this.constructor.instanceFor(node)
97
+ )
98
+ }
99
+
100
+ get siblings(){
101
+ if(this.parent){
102
+ return this.parent.children
103
+ } else {
104
+ return [this]
105
+ }
106
+ }
107
+
108
+ get previousSibling(){
109
+ if(this.node.previousSibling){
110
+ return this.constructor.instanceFor(this.node.previousSibling)
111
+ } else {
112
+ return null
113
+ }
114
+ }
115
+
116
+ get nextSibling(){
117
+ if(this.node.nextSibling){
118
+ return this.constructor.instanceFor(this.node.nextSibling)
119
+ } else {
120
+ return null
121
+ }
122
+ }
123
+
124
+ get nextSiblings(){
125
+ const out = []
126
+ let current = this
127
+ while(current.nextSibling){
128
+ current = current.nextSibling
129
+ out.push(current)
130
+ }
131
+ return out
132
+ }
133
+
134
+ get previousSiblings(){
135
+ const out = []
136
+ let current = this
137
+ while(current.previousSibling){
138
+ current = current.previousSibling
139
+ out.push(current)
140
+ }
141
+ return out
142
+ }
143
+
144
+ get descendants(){
145
+ return this.find(() => true)
146
+ }
147
+
148
+ find(selector, out = []){
149
+ this.children.forEach((child) => {
150
+ if(child.is(selector)){
151
+ out.push(child)
152
+ }
153
+ child.find(selector, out)
154
+ })
155
+ return out;
156
+ }
157
+
158
+ is(selector){
159
+ if(typeof selector == 'function'){
160
+ return selector.call(this, this)
161
+ }
162
+ return (this.node.matches || this.node.matchesSelector || this.node.msMatchesSelector || this.node.mozMatchesSelector || this.node.webkitMatchesSelector || this.node.oMatchesSelector || (() => false)).call(this.node, selector)
163
+ }
164
+
165
+ on(name, ...args){
166
+ const fn = args.pop()
167
+ const selector = args.pop()
168
+
169
+ const wrapperFn = (event, ...args) => {
170
+ const eventWrapper = EventWrapper.instanceFor(event)
171
+ if(selector){
172
+ if(eventWrapper.target.is(selector)){
173
+ return fn.call(eventWrapper.target, eventWrapper, ...args)
174
+ }
175
+ } else {
176
+ return fn.call(this, eventWrapper, ...args)
177
+ }
178
+ }
179
+
180
+ this.node.addEventListener(name, wrapperFn)
181
+
182
+ this.$registeredEventListeners.push([name, wrapperFn])
183
+
184
+ return this
185
+ }
186
+
187
+ trigger(name, data){
188
+ if (window.CustomEvent && typeof window.CustomEvent === 'function') {
189
+ var event = new CustomEvent(name, { bubbles: true, cancelable: true, detail: data } );
190
+ } else {
191
+ var event = document.createEvent('CustomEvent')
192
+ event.initCustomEvent(name, true, true, data)
193
+ }
194
+
195
+ this.node.dispatchEvent(event)
196
+
197
+ return this
198
+ }
199
+
200
+ remove(){
201
+ if(this.type != '#doctype'){
202
+ this.realParent.node.removeChild(this.node)
203
+ }
204
+ return this
205
+ }
206
+
207
+ addClass(name){
208
+ this.node.classList.add(name)
209
+ return this
210
+ }
211
+
212
+ removeClass(name){
213
+ this.node.classList.remove(name)
214
+ return this
215
+ }
216
+
217
+ patch(html){
218
+ cleanChildren.call(this)
219
+ patchChildren.call(this, VirtualNode.fromString(html).children)
220
+ initChildren.call(this)
221
+ return this.children
222
+ }
223
+
224
+ append(html){
225
+ return prepend.call(this, html)
226
+ }
227
+
228
+ prepend(html){
229
+ return prepend.call(this, html, this.children[0])
230
+ }
231
+
232
+ insertBefore(html){
233
+ return prepend.call(this.realParent, html, this)
234
+ }
235
+
236
+ insertAfter(html){
237
+ return prepend.call(this.realParent, html, this.nextSibling)
238
+ }
239
+
240
+ }
241
+
242
+ function cleanChildren(){
243
+ this.children.forEach(child => clean.call(child))
244
+ }
245
+
246
+ function clean(){
247
+ [...this.node.childNodes].forEach(node => node.$p && clean.call(node.$p))
248
+
249
+ while(this.$registeredEventListeners.length){
250
+ this.node.removeEventListener(...this.$registeredEventListeners.pop())
251
+ }
252
+ this.node.$p = undefined
253
+ }
254
+
255
+ function initChildren(){
256
+ this.children.forEach(child => initChildren.call(child))
257
+ }
258
+
259
+ function prepend(html, referenceChild){
260
+ const out = []
261
+ VirtualNode.fromString(html).children.forEach((virtualChild) => {
262
+ out.push(insert.call(this, virtualChild, referenceChild))
263
+ })
264
+ return out
265
+ }
266
+
267
+ function patch(attributes, virtualChildren){
268
+ patchAttributes.call(this, attributes)
269
+ patchChildren.call(this, virtualChildren)
270
+ }
271
+
272
+ function patchAttributes(attributes){
273
+ if(this.type == '#text' || this.type == '#comment'){
274
+ if(this.node.textContent != attributes.value){
275
+ this.node.textContent = attributes.value
276
+ }
277
+ } else if(this.type != '#doctype'){
278
+ const currentAttributes = this.attributes
279
+ Object.keys(currentAttributes).forEach((key) => {
280
+ if(attributes[key] === undefined){
281
+ this.node.removeAttribute(key)
282
+ }
283
+ })
284
+ Object.keys(attributes).forEach((key) => {
285
+ if(currentAttributes[key] != attributes[key]){
286
+ this.node.setAttribute(key, attributes[key])
287
+ }
288
+ })
289
+ }
290
+ }
291
+
292
+ function patchChildren(virtualChildren){
293
+ const children = [...this.node.childNodes].map(
294
+ node => new NodeWrapper(node)
295
+ )
296
+
297
+ for(let i = 0; i < virtualChildren.length; i++){
298
+ let child = children[0]
299
+ const virtualChild = virtualChildren[i]
300
+
301
+ if(child && child.type == virtualChild.type){
302
+ patch.call(children.shift(), virtualChild.attributes, virtualChild.children)
303
+ } else if(virtualChild.type == '#doctype'){
304
+ // ignore
305
+ } else if(virtualChild.type.match(/^#(text|comment)/)){
306
+ insert.call(this, virtualChild, child)
307
+ } else {
308
+ while(children.length > 0 && children[0].type.match(/^#/)){
309
+ children.shift().remove()
310
+ }
311
+ child = children[0]
312
+ if(child && child.type == virtualChild.type){
313
+ patch.call(children.shift(), virtualChild.attributes, virtualChild.children);
314
+ } else {
315
+ insert.call(this, virtualChild, child)
316
+ }
317
+ }
318
+ }
319
+
320
+ while(children.length > 0){
321
+ children.shift().remove()
322
+ }
323
+ }
324
+
325
+ function insert(virtualNode, referenceChild, returnNodeWrapper = true){
326
+ const { type, attributes, children } = virtualNode
327
+
328
+ let node
329
+
330
+ if(type == '#text'){
331
+ node = document.createTextNode(attributes.value)
332
+ } else if(type == '#comment'){
333
+ node = document.createComment(attributes.value)
334
+ } else {
335
+ node = document.createElement(type)
336
+ Object.keys(attributes).forEach((key) => {
337
+ node.setAttribute(key, attributes[key])
338
+ })
339
+ }
340
+
341
+ children.forEach(child => {
342
+ insert.call(new NodeWrapper(node), child, null, false)
343
+ })
344
+
345
+ this.node.insertBefore(
346
+ node,
347
+ referenceChild && referenceChild.node
348
+ )
349
+
350
+ if(returnNodeWrapper){
351
+ return NodeWrapper.instanceFor(node)
352
+ }
353
+ }
354
+
@@ -0,0 +1,56 @@
1
+
2
+ import { NodeWrapper } from '../node_wrapper'
3
+ import { Url } from '../url'
4
+
5
+ export class Anchor extends NodeWrapper {
6
+
7
+ static get name(){ return 'anchor' }
8
+
9
+ static get selector(){ return 'a, .anchor' }
10
+
11
+ constructor(...args){
12
+ super(...args)
13
+
14
+ this.on('click', (event) => {
15
+ if(this.url.host == this.frame.url.host && this.url.port == this.frame.url.port){
16
+ event.preventDefault()
17
+
18
+ const confirm = this.attributes['data-confirm']
19
+ const method = this.attributes['data-method'] || 'GET'
20
+ const target = this.attributes['target'] || this.attributes['data-target'] || '_top'
21
+
22
+ if(!confirm || window.confirm(confirm)){
23
+ if(target == '_modal'){
24
+ this.document.find('html').pop().addClass('is-clipped')
25
+ this.document.find('body').pop().append(`<div class="modal is-active" data-url="${this.url}"></div>`).forEach((modal) => {
26
+ modal.$parent = this
27
+ modal.load({})
28
+ })
29
+ } else {
30
+ this.frame.load({ $method: method, $url: this.url })
31
+ }
32
+ }
33
+ }
34
+ })
35
+ }
36
+
37
+ get url(){
38
+ if(this.$url === undefined){
39
+ this.$url = Url.fromString(
40
+ this.attributes['href'] || this.attributes['data-url'],
41
+ this.frame.url
42
+ )
43
+ }
44
+ return this.$url
45
+ }
46
+
47
+ set url(url){
48
+ this.$url = Url.fromString(
49
+ url,
50
+ this.url
51
+ )
52
+ }
53
+
54
+ }
55
+
56
+ Anchor.register()
@@ -0,0 +1,27 @@
1
+ import { Frame } from './frame'
2
+
3
+ export class Document extends Frame {
4
+
5
+ static get name(){ return 'document' }
6
+
7
+ static get selector(){ return function(){ return this.type == '#document' } }
8
+
9
+ constructor(...args){
10
+ super(...args)
11
+ window.onpopstate = (event) => {
12
+ this.load({ $pushState: false, $url: event.state || window.location })
13
+ }
14
+ window.$p = this
15
+ }
16
+
17
+ load({ $pushState = true, ...params }){
18
+ const previousUrl = this.url.toString()
19
+ super.load(params)
20
+ if($pushState && params.$method == 'GET' && previousUrl != this.url.toString()){
21
+ history.pushState(this.url.toString(), null, this.url.toString())
22
+ }
23
+ }
24
+
25
+ }
26
+
27
+ Document.register()
@@ -0,0 +1,52 @@
1
+
2
+ import { NodeWrapper } from '../node_wrapper'
3
+ import { Url } from '../url'
4
+
5
+ export class Form extends NodeWrapper {
6
+
7
+ static get name(){ return 'form' }
8
+
9
+ static get selector(){ return 'form, .form' }
10
+
11
+ constructor(...args){
12
+ super(...args)
13
+
14
+ this.on('submit', (event) => {
15
+ console.log('Form submit', event)
16
+ event.preventDefault();
17
+ this.frame.load({$method: this.method, $url: this.url, ...this.params })
18
+ })
19
+ }
20
+
21
+ get method(){
22
+ return this.attributes['method'] || this.attributes['data-method'] || 'POST'
23
+ }
24
+
25
+ get url(){
26
+ if(this.$url === undefined){
27
+ this.$url = Url.fromString(
28
+ this.attributes['action'] || this.attributes['data-url'],
29
+ this.frame.url
30
+ )
31
+ }
32
+ return this.$url
33
+ }
34
+
35
+ get inputs(){
36
+ return this.descendants.filter((descendant) => descendant.isInput)
37
+ }
38
+
39
+ get params(){
40
+ const out = {}
41
+ this.inputs.forEach(input => {
42
+ const value = input.value
43
+ if(value !== undefined){
44
+ out[input.name] = value
45
+ }
46
+ })
47
+ return out
48
+ }
49
+
50
+ }
51
+
52
+ Form.register()
@@ -0,0 +1,72 @@
1
+
2
+ import { NodeWrapper } from '../node_wrapper'
3
+ import { Url } from '../url'
4
+
5
+ export class Frame extends NodeWrapper {
6
+
7
+ static get name(){ return 'frame' }
8
+
9
+ constructor(...args){
10
+ super(...args)
11
+ }
12
+
13
+ get url(){
14
+ if(this.$url === undefined){
15
+ this.$url = Url.fromString(
16
+ this.attributes['data-url'] || window.location,
17
+ this.frame ? this.frame.url : window.location
18
+ )
19
+ }
20
+ return this.$url
21
+ }
22
+
23
+ set url(url){
24
+ this.$url = Url.fromString(
25
+ url,
26
+ this.url
27
+ )
28
+ }
29
+
30
+ load({$method = 'GET', $url = this.url.toString(), $headers = {}, ...params }){
31
+ if(this.request){
32
+ this.request.abort()
33
+ }
34
+
35
+ $method = $method.toUpperCase();
36
+
37
+ this.url = $url
38
+ const isRequestBody = $method == 'POST' || $method == 'PUT' || $method == 'PATCH';
39
+ if(!isRequestBody){
40
+ this.url.params = {...this.url.params, ...params}
41
+ }
42
+
43
+ this.request = new XMLHttpRequest();
44
+
45
+ this.request.open($method, this.url.toString(), true);
46
+
47
+ this.request.onload = () => {
48
+ if (this.request.status >= 200 && this.request.status < 400) {
49
+ this.patch(this.request.response)
50
+ }
51
+ }
52
+
53
+ const defaultHeaders = {};
54
+ const document = this.document || this
55
+ const csrfToken = document.find('meta[name="csrf-token"').map(nodeWrapper => nodeWrapper.attributes.content).pop()
56
+ if(csrfToken){
57
+ defaultHeaders['X-CSRF-Token'] = csrfToken
58
+ }
59
+ const headers = {...defaultHeaders, ...this.headers}
60
+ Object.keys(headers).forEach((name) => this.request.setRequestHeader(name, headers[name]))
61
+
62
+ const formData = new FormData();
63
+ if(isRequestBody){
64
+ Object.keys(params).forEach((name) => formData.append(name, params[name]));
65
+ }
66
+
67
+ this.request.send(formData)
68
+ }
69
+
70
+ }
71
+
72
+ Frame.register()
@@ -0,0 +1,8 @@
1
+
2
+ export * from './anchor'
3
+ export * from './document'
4
+ export * from './form'
5
+ export * from './frame'
6
+ export * from './input'
7
+ export * from './modal'
8
+ export * from './script'
@@ -0,0 +1,23 @@
1
+
2
+ import { NodeWrapper } from '../node_wrapper'
3
+
4
+ export class Input extends NodeWrapper {
5
+
6
+ static get name(){ return 'input' }
7
+
8
+ static get selector(){ return 'input, textarea, .input' }
9
+
10
+ get name(){
11
+ return this.attributes.name
12
+ }
13
+
14
+ get value(){
15
+ if(this.is('input[type="checkbox"], input[type="radio"]')){
16
+ return this.is(':checked') ? this.node.value : undefined
17
+ }
18
+ return this.node.value
19
+ }
20
+
21
+ }
22
+
23
+ Input.register()
@@ -0,0 +1,36 @@
1
+
2
+ import { Frame } from './frame'
3
+ import { NodeWrapper } from '../node_wrapper'
4
+
5
+ export class Modal extends Frame {
6
+
7
+ static get name(){ return 'modal' }
8
+
9
+ constructor(...args){
10
+ super(...args)
11
+ this.on('click', '.modal-background, .modal-close', (event) => {
12
+ event.stopPropagation()
13
+ this.close()
14
+ })
15
+ }
16
+
17
+ close(){
18
+ this.remove()
19
+ if(!this.document.find('body').pop().children.filter((child) => child.is('.modal')).length){
20
+ this.document.find('html').pop().removeClass('is-clipped')
21
+ }
22
+ }
23
+
24
+ patch(html){
25
+ return super.patch(`
26
+ <div class="modal-background"></div>
27
+ <div class="modal-content">
28
+ <div class="box">${html}</div>
29
+ </div>
30
+ <button class="modal-close is-large" aria-label="close"></button>
31
+ `)
32
+ }
33
+
34
+ }
35
+
36
+ Modal.register()
@@ -0,0 +1,17 @@
1
+
2
+ import { NodeWrapper } from '../node_wrapper'
3
+
4
+ export class Script extends NodeWrapper {
5
+
6
+ static get name(){ return 'script' }
7
+
8
+ static get selector(){ return 'script[type="pinstripe"]' }
9
+
10
+ constructor(...args){
11
+ super(...args)
12
+ eval(this.text)
13
+ }
14
+
15
+ }
16
+
17
+ Script.register()
@@ -0,0 +1,24 @@
1
+
2
+ export class StringReader {
3
+
4
+ constructor(string){
5
+ this.string = (string || '').toString();
6
+ }
7
+
8
+ get length(){
9
+ return this.string.length
10
+ }
11
+
12
+ toString(){
13
+ return this.string
14
+ }
15
+
16
+ match(...args){
17
+ const out = this.string.match(...args)
18
+ if(out){
19
+ this.string = this.string.substr(out[0].length)
20
+ }
21
+ return out
22
+ }
23
+
24
+ }