mushy 0.21.2 → 0.24.0
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.
- checksums.yaml +4 -4
- data/bin/mushy +5 -9
- data/lib/mushy/builder/api.rb +146 -152
- data/lib/mushy/builder/index.rb +1 -0
- data/lib/mushy/flux.rb +2 -1
- data/lib/mushy/fluxs/bash.rb +50 -58
- data/lib/mushy/fluxs/browser.rb +232 -240
- data/lib/mushy/fluxs/build_csv.rb +60 -68
- data/lib/mushy/fluxs/cli.rb +22 -28
- data/lib/mushy/fluxs/document.rb +33 -39
- data/lib/mushy/fluxs/environment.rb +23 -36
- data/lib/mushy/fluxs/file_watch.rb +114 -77
- data/lib/mushy/fluxs/write_json.rb +30 -40
- data/lib/mushy.rb +6 -6
- data/mushy.gemspec +1 -1
- metadata +1 -1
data/lib/mushy/fluxs/browser.rb
CHANGED
@@ -1,262 +1,254 @@
|
|
1
1
|
require 'ferrum'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
{
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
3
|
+
class Mushy::Browser < Mushy::Flux
|
4
|
+
def self.details
|
5
|
+
{
|
6
|
+
name: 'Browser',
|
7
|
+
title: 'Use a browser',
|
8
|
+
fluxGroup: { name: 'Web' },
|
9
|
+
description: 'Visit a page in a browser.',
|
10
|
+
config: {
|
11
|
+
url: {
|
12
|
+
description: 'The URL to visit.',
|
13
|
+
type: 'text',
|
14
|
+
value: 'https://www.google.com'
|
15
|
+
},
|
16
|
+
headless: {
|
17
|
+
description: 'Run this browser headless.',
|
18
|
+
type: 'boolean',
|
19
|
+
shrink: true,
|
20
|
+
value: ''
|
21
|
+
},
|
22
|
+
timeout: {
|
23
|
+
description: 'The default timeout (in seconds) before closing the browser. Default is 5 seconds.',
|
24
|
+
type: 'integer',
|
25
|
+
shrink: true,
|
26
|
+
value: ''
|
27
|
+
},
|
28
|
+
execute: {
|
29
|
+
description: 'Javascript to run after the page is loaded.',
|
30
|
+
type: 'textarea',
|
31
|
+
shrink: true,
|
32
|
+
value: ''
|
33
|
+
},
|
34
|
+
cookies: {
|
35
|
+
description: 'Cookies for the web request. These can be received from a previous browser event with {{cookies}}, or can be typed manually.',
|
36
|
+
type: 'editgrid',
|
37
|
+
shrink: true,
|
38
|
+
value: [],
|
39
|
+
editors: [
|
40
|
+
{ id: 'name', target: 'name', field: { type: 'text', value: '', default: '' } },
|
41
|
+
{ id: 'value', target: 'value', field: { type: 'text', value: '', default: '' } },
|
42
|
+
{ id: 'domain', target: 'domain', field: { type: 'text', value: '', default: '' } },
|
43
|
+
{ id: 'path', target: 'path', field: { type: 'text', value: '', default: '' } },
|
44
|
+
{ id: 'expires', target: 'expires', field: { type: 'text', value: '', default: '' } },
|
45
|
+
{ id: 'size', target: 'size', field: { type: 'integer', value: 0, default: 0 } },
|
46
|
+
{ id: 'httpOnly', target: 'httpOnly', field: { type: 'boolean', value: false, default: false } },
|
47
|
+
{ id: 'secure', target: 'secure', field: { type: 'boolean', value: true, default: true } },
|
48
|
+
{ id: 'sameSite', target: 'sameSite', field: { type: 'text', value: 'None', default: 'None' } },
|
49
|
+
{ id: 'priority', target: 'priority', field: { type: 'text', value: 'Medium', default: 'Medium' } },
|
50
|
+
]
|
51
|
+
},
|
52
|
+
carry_cookies_from: {
|
53
|
+
description: 'Carry the cookies from this path in the event. Defaults to "cookies".',
|
54
|
+
type: 'text',
|
55
|
+
shrink: true,
|
56
|
+
value: ''
|
57
|
+
},
|
58
|
+
headers: {
|
59
|
+
description: 'Headers for the web request. These can be received from a previous browser event with {{headers}}, or can be typed manually.',
|
60
|
+
type: 'keyvalue',
|
61
|
+
shrink: true,
|
62
|
+
value: {}
|
63
|
+
},
|
64
|
+
carry_headers_from: {
|
65
|
+
description: 'Carry the headers from this path in the event. Defaults to "headers".',
|
66
|
+
type: 'text',
|
67
|
+
shrink: true,
|
68
|
+
value: ''
|
69
|
+
},
|
70
|
+
wait_before_closing: {
|
71
|
+
description: 'Wait this many seconds before closing the browser.',
|
72
|
+
type: 'integer',
|
73
|
+
shrink: true,
|
74
|
+
value: ''
|
75
|
+
}
|
76
|
+
},
|
77
|
+
examples: {
|
78
|
+
'Successful Call' => {
|
79
|
+
description: 'This will open https://www.google.com and return the result.',
|
80
|
+
config: { url: "https://www.google.com" },
|
81
|
+
result: {
|
82
|
+
url: 'https://www.google.com/',
|
83
|
+
status: 200,
|
84
|
+
title: 'Google',
|
85
|
+
cookies: [
|
86
|
+
{
|
87
|
+
name: '1P_JAR',
|
88
|
+
value: '2021-10-06-12',
|
89
|
+
domain: '.google.com',
|
90
|
+
path: '/',
|
91
|
+
expires: 1636117150.583117,
|
92
|
+
size: 19,
|
93
|
+
httpOnly: false,
|
94
|
+
secure: true,
|
95
|
+
session: false,
|
96
|
+
sameSite: 'None',
|
97
|
+
priority: 'Medium'
|
98
|
+
}
|
99
|
+
],
|
100
|
+
headers: {},
|
101
|
+
time: 1.486214604,
|
102
|
+
body: '<html itemscope="" itemtype="http://schema.org/WebPage" lang="en">...</html>'
|
103
|
+
}
|
79
104
|
},
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
{
|
92
|
-
"name": "1P_JAR",
|
93
|
-
"value": "2021-10-06-12",
|
94
|
-
"domain": ".google.com",
|
95
|
-
"path": "/",
|
96
|
-
"expires": 1636117150.583117,
|
97
|
-
"size": 19,
|
98
|
-
"httpOnly": false,
|
99
|
-
"secure": true,
|
100
|
-
"session": false,
|
101
|
-
"sameSite": "None",
|
102
|
-
"priority": "Medium"
|
103
|
-
},
|
104
|
-
],
|
105
|
-
"headers": {},
|
106
|
-
"time": 1.486214604,
|
107
|
-
"body": "<html itemscope=\"\" itemtype=\"http://schema.org/WebPage\" lang=\"en\">...</html>"
|
108
|
-
}
|
109
|
-
},
|
110
|
-
"Login To a Site" => {
|
111
|
-
description: 'This will open https://www.yoursitepleasethankyou.com, login using javascript, and then return the state of the browser after logging in.',
|
112
|
-
input: {
|
113
|
-
url: "https://www.yoursitepleasethankyou.com",
|
114
|
-
username: "MYUSERNAME",
|
115
|
-
password: "MYPASSWORD",
|
116
|
-
},
|
117
|
-
config: {
|
118
|
-
url: "{{url}}",
|
119
|
-
timeout: 10,
|
120
|
-
execute: "$('#username').val('{{username}}');
|
105
|
+
'Login To a Site' => {
|
106
|
+
description: 'This will open https://www.yoursitepleasethankyou.com, login using javascript, and then return the state of the browser after logging in.',
|
107
|
+
input: {
|
108
|
+
url: 'https://www.yoursitepleasethankyou.com',
|
109
|
+
username: 'MYUSERNAME',
|
110
|
+
password: 'MYPASSWORD'
|
111
|
+
},
|
112
|
+
config: {
|
113
|
+
url: '{{url}}',
|
114
|
+
timeout: 10,
|
115
|
+
execute: "$('#username').val('{{username}}');
|
121
116
|
$('#next').click();
|
122
117
|
$('#password').val('{{password}}');
|
123
118
|
$('#login').click();"
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
}
|
119
|
+
},
|
120
|
+
result: {
|
121
|
+
url: 'https://yoursitepleasethankyou/',
|
122
|
+
status: 200,
|
123
|
+
title: '',
|
124
|
+
cookies: [
|
125
|
+
{
|
126
|
+
name: 'session_id',
|
127
|
+
value: '1jfujsx5xbnuxmsjmgjhzfpi',
|
128
|
+
domain: '.yoursitepleasethankyou',
|
129
|
+
path: '/',
|
130
|
+
expires: -1,
|
131
|
+
size: 41,
|
132
|
+
httpOnly: true,
|
133
|
+
secure: true,
|
134
|
+
session: true,
|
135
|
+
sameSite: 'Lax',
|
136
|
+
priority: 'Medium'
|
137
|
+
}
|
138
|
+
],
|
139
|
+
headers: {},
|
140
|
+
time: 4.633920809,
|
141
|
+
body: '<html xmlns="http://www.w3.org/1999/xhtml"><head></head>...</html>'
|
142
|
+
}
|
143
|
+
},
|
144
|
+
'Access a Page After Logging In' => {
|
145
|
+
description: 'This will open a page using cookies from the previous request. Note that the cookies came from another browser flux event.',
|
146
|
+
input: {
|
147
|
+
url: 'https://yoursitepleasethankyou/',
|
148
|
+
cookies: [
|
149
|
+
{
|
150
|
+
name: 'session_id',
|
151
|
+
value: '1jfujsx5xbnuxmsjmgjhzfpi',
|
152
|
+
domain: '.yoursitepleasethankyou',
|
153
|
+
path: '/',
|
154
|
+
expires: -1,
|
155
|
+
size: 41,
|
156
|
+
httpOnly: true,
|
157
|
+
secure: true,
|
158
|
+
session: true,
|
159
|
+
sameSite: 'Lax',
|
160
|
+
priority: 'Medium'
|
161
|
+
}
|
162
|
+
]
|
163
|
+
},
|
164
|
+
config: {
|
165
|
+
url: 'https://www.yoursitepleasethankyou.com/myaccount',
|
166
|
+
carry_cookies_from: '{{cookies}}'
|
167
|
+
},
|
168
|
+
result: {
|
169
|
+
url: 'https://yoursitepleasethankyou/',
|
170
|
+
status: 200,
|
171
|
+
title: '',
|
172
|
+
cookies: [
|
173
|
+
{
|
174
|
+
name: 'session_id',
|
175
|
+
value: '1jfujsx5xbnuxmsjmgjhzfpi',
|
176
|
+
domain: '.yoursitepleasethankyou',
|
177
|
+
path: '/',
|
178
|
+
expires: -1,
|
179
|
+
size: 41,
|
180
|
+
httpOnly: true,
|
181
|
+
secure: true,
|
182
|
+
session: true,
|
183
|
+
sameSite: 'Lax',
|
184
|
+
priority: 'Medium'
|
185
|
+
}
|
186
|
+
],
|
187
|
+
headers: {},
|
188
|
+
time: 4.633920809,
|
189
|
+
body: '<html><head></head>Your name is John Doe...</html>'
|
190
|
+
}
|
191
|
+
}
|
198
192
|
}
|
199
|
-
|
200
|
-
|
201
|
-
def process event, config
|
202
|
-
|
203
|
-
timeout = config[:timeout] ? config[:timeout].to_i : 5
|
193
|
+
}
|
194
|
+
end
|
204
195
|
|
205
|
-
|
206
|
-
|
207
|
-
timeout: timeout)
|
196
|
+
def process event, config
|
197
|
+
timeout = config[:timeout] ? config[:timeout].to_i : 5
|
208
198
|
|
209
|
-
|
199
|
+
browser = Ferrum::Browser.new(
|
200
|
+
headless: (config[:headless].to_s != 'false'),
|
201
|
+
timeout: timeout
|
202
|
+
)
|
210
203
|
|
211
|
-
|
204
|
+
get_the_cookies_from(event, config).each { |c| browser.cookies.set(c) }
|
212
205
|
|
213
|
-
|
214
|
-
browser.goto config[:url]
|
215
|
-
time = Time.now - the_start
|
206
|
+
browser.headers.add get_the_headers_from(event, config)
|
216
207
|
|
217
|
-
|
208
|
+
the_start = Time.now.utc
|
209
|
+
browser.goto config[:url]
|
210
|
+
time = Time.now.utc - the_start
|
218
211
|
|
219
|
-
|
212
|
+
browser.execute(config[:execute]) if config[:execute]
|
220
213
|
|
221
|
-
|
222
|
-
url: browser.url,
|
223
|
-
status: browser.network.status,
|
224
|
-
title: browser.frames[0].title,
|
225
|
-
cookies: browser.cookies.all.map { |k, v| v.instance_variable_get('@attributes') },
|
226
|
-
headers: browser.headers.get,
|
227
|
-
time: time,
|
228
|
-
body: browser.body
|
229
|
-
}
|
214
|
+
sleep(config[:wait_before_closing].to_i) if config[:wait_before_closing]&.to_i&.positive?
|
230
215
|
|
231
|
-
|
216
|
+
result = {
|
217
|
+
url: browser.url,
|
218
|
+
status: browser.network.status,
|
219
|
+
title: browser.frames[0].title,
|
220
|
+
cookies: browser.cookies.all.map { |_, v| v.instance_variable_get('@attributes') },
|
221
|
+
headers: browser.headers.get,
|
222
|
+
time: time,
|
223
|
+
body: browser.body
|
224
|
+
}
|
232
225
|
|
233
|
-
|
226
|
+
result = adjust({ browser: browser, result: result, config: config })
|
234
227
|
|
235
|
-
|
236
|
-
end
|
228
|
+
browser.quit
|
237
229
|
|
238
|
-
|
239
|
-
|
240
|
-
end
|
241
|
-
|
242
|
-
def get_the_cookies_from event, config
|
243
|
-
carry_cookies_from = config[:carry_cookies_from].to_s == '' ? 'cookies' : config[:carry_cookies_from]
|
244
|
-
cookies = event[carry_cookies_from.to_sym]
|
245
|
-
cookies = [] unless cookies.is_a?(Array)
|
246
|
-
config[:cookies] = [] unless config[:cookies].is_a?(Array)
|
247
|
-
config[:cookies].each { |x| cookies << x }
|
248
|
-
cookies
|
249
|
-
end
|
230
|
+
result
|
231
|
+
end
|
250
232
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
headers = {} unless headers.is_a?(Hash)
|
255
|
-
config[:headers] = {} unless config[:headers].is_a?(Hash)
|
256
|
-
config[:headers].each { |k, v| headers[k] = v }
|
257
|
-
headers
|
258
|
-
end
|
233
|
+
def adjust(input)
|
234
|
+
input[:result]
|
235
|
+
end
|
259
236
|
|
237
|
+
def get_the_cookies_from(event, config)
|
238
|
+
carry_cookies_from = config[:carry_cookies_from].to_s == '' ? 'cookies' : config[:carry_cookies_from]
|
239
|
+
cookies = event[carry_cookies_from.to_sym]
|
240
|
+
cookies = [] unless cookies.is_a?(Array)
|
241
|
+
config[:cookies] = [] unless config[:cookies].is_a?(Array)
|
242
|
+
config[:cookies].each { |x| cookies << x }
|
243
|
+
cookies
|
260
244
|
end
|
261
245
|
|
262
|
-
|
246
|
+
def get_the_headers_from(event, config)
|
247
|
+
carry_headers_from = config[:carry_headers_from].to_s == '' ? 'headers' : config[:carry_headers_from]
|
248
|
+
headers = event[carry_headers_from.to_sym]
|
249
|
+
headers = {} unless headers.is_a?(Hash)
|
250
|
+
config[:headers] = {} unless config[:headers].is_a?(Hash)
|
251
|
+
config[:headers].each { |k, v| headers[k] = v }
|
252
|
+
headers
|
253
|
+
end
|
254
|
+
end
|
@@ -1,76 +1,68 @@
|
|
1
1
|
require 'csv'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
3
|
+
class Mushy::BuildCsv < Mushy::Flux
|
4
|
+
def self.details
|
5
|
+
{
|
6
|
+
name: 'BuildCsv',
|
7
|
+
title: 'Build CSV',
|
8
|
+
description: 'Build a CSV.',
|
9
|
+
fluxGroup: { name: 'CSV' },
|
10
|
+
config: {
|
11
|
+
input_path: {
|
12
|
+
description: 'The path to the set of records to include in the CSV.',
|
13
|
+
type: 'text',
|
14
|
+
value: 'records'
|
15
|
+
},
|
16
|
+
output_path: {
|
17
|
+
description: 'The path to the CSV content in the outgoing event.',
|
18
|
+
type: 'text',
|
19
|
+
value: 'records'
|
20
|
+
},
|
21
|
+
headers: {
|
22
|
+
description: 'The values to include in the CSV, as well as the header values.',
|
23
|
+
type: 'keyvalue',
|
24
|
+
value: {}
|
25
|
+
},
|
26
|
+
header_row: {
|
27
|
+
description: 'Include a header row?',
|
28
|
+
type: 'boolean',
|
29
|
+
value: true
|
30
|
+
}
|
31
|
+
},
|
32
|
+
examples: {
|
33
|
+
'Build a Simple CSV' => {
|
34
|
+
description: 'Converts a set of records to a CSV.',
|
35
|
+
input: {
|
36
|
+
things: [
|
37
|
+
{ name: 'Apple', color: 'Red' },
|
38
|
+
{ name: 'Banana', color: 'Yellow' },
|
39
|
+
{ name: 'Pear', color: 'Green' }
|
40
|
+
]
|
41
|
+
},
|
42
|
+
config: {
|
43
|
+
input_path: 'things',
|
44
|
+
output_path: 'records',
|
45
|
+
headers: { name: 'Name', color: 'Color' },
|
46
|
+
header_row: true
|
47
|
+
},
|
48
|
+
result: {
|
49
|
+
records: 'Name,Color\nApple,Red\nBanana,Yellow\nPear,Green\n'
|
50
|
+
}
|
34
51
|
},
|
35
|
-
examples: {
|
36
|
-
"Build a Simple CSV" => {
|
37
|
-
description: 'Converts a set of records to a CSV.',
|
38
|
-
input: {
|
39
|
-
things: [
|
40
|
-
{ name: "Apple", color:"Red" },
|
41
|
-
{ name: "Banana", color: "Yellow" },
|
42
|
-
{ name: "Pear", color: "Green" }
|
43
|
-
]
|
44
|
-
},
|
45
|
-
config: {
|
46
|
-
input_path: "things",
|
47
|
-
output_path: "records",
|
48
|
-
headers: { name: "Name", color: "Color" },
|
49
|
-
header_row: true
|
50
|
-
},
|
51
|
-
result: {
|
52
|
-
records: "Name,Color\nApple,Red\nBanana,Yellow\nPear,Green\n"
|
53
|
-
}
|
54
|
-
},
|
55
|
-
}
|
56
52
|
}
|
57
|
-
|
58
|
-
|
59
|
-
def process event, config
|
60
|
-
records = event[config[:input_path].to_sym] || event[config[:input_path].to_s]
|
53
|
+
}
|
54
|
+
end
|
61
55
|
|
62
|
-
|
56
|
+
def process(event, config)
|
57
|
+
records = event[config[:input_path].to_sym] || event[config[:input_path].to_s]
|
63
58
|
|
64
|
-
|
65
|
-
config[:output_path] => CSV.generate do |c|
|
66
|
-
if config[:header_row].to_s == 'true'
|
67
|
-
c << headers.map { |h| h[1] }
|
68
|
-
end
|
69
|
-
records.each { |x| c << headers.map { |h| x[h[0].to_sym] || x[h[0].to_s] } }
|
70
|
-
end
|
71
|
-
}
|
72
|
-
end
|
59
|
+
headers = config[:headers]
|
73
60
|
|
61
|
+
{
|
62
|
+
config[:output_path] => CSV.generate do |c|
|
63
|
+
c << headers.map { |h| h[1] } if config[:header_row].to_s == 'true'
|
64
|
+
records.each { |x| c << headers.map { |h| x[h[0].to_sym] || x[h[0].to_s] } }
|
65
|
+
end
|
66
|
+
}
|
74
67
|
end
|
75
|
-
|
76
|
-
end
|
68
|
+
end
|