pinstripe 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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
+ }