noms-command 0.5.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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +191 -0
  5. data/README.rst +376 -0
  6. data/ROADMAP.rst +127 -0
  7. data/Rakefile +49 -0
  8. data/TODO.rst +7 -0
  9. data/bin/noms2 +20 -0
  10. data/fixture/dnc.rb +120 -0
  11. data/fixture/identity +5 -0
  12. data/fixture/public/dnc.json +22 -0
  13. data/fixture/public/echo.json +7 -0
  14. data/fixture/public/files/data.json +12 -0
  15. data/fixture/public/files/foo.json +2 -0
  16. data/fixture/public/lib/dnc.js +81 -0
  17. data/fixture/public/lib/noms-args.js +13 -0
  18. data/fixture/public/lib/showopt.js +18 -0
  19. data/fixture/public/location.json +8 -0
  20. data/fixture/public/showopt.json +15 -0
  21. data/fixture/rig2json +21 -0
  22. data/lib/noms/command/application.rb +204 -0
  23. data/lib/noms/command/auth/identity.rb +62 -0
  24. data/lib/noms/command/auth.rb +117 -0
  25. data/lib/noms/command/base.rb +22 -0
  26. data/lib/noms/command/document.rb +59 -0
  27. data/lib/noms/command/error.rb +11 -0
  28. data/lib/noms/command/formatter.rb +178 -0
  29. data/lib/noms/command/urinion/data.rb +63 -0
  30. data/lib/noms/command/urinion.rb +29 -0
  31. data/lib/noms/command/useragent.rb +134 -0
  32. data/lib/noms/command/version.rb +7 -0
  33. data/lib/noms/command/window.rb +95 -0
  34. data/lib/noms/command/xmlhttprequest.rb +181 -0
  35. data/lib/noms/command.rb +107 -0
  36. data/noms-command.gemspec +30 -0
  37. data/spec/01noms-command_spec.rb +30 -0
  38. data/spec/02noms-command.sh +31 -0
  39. data/spec/03application_spec.rb +47 -0
  40. data/spec/04application_spec.rb +61 -0
  41. data/spec/05formatter_spec.rb +195 -0
  42. data/spec/06urinion_data.rb +20 -0
  43. data/spec/07js_spec.rb +87 -0
  44. data/spec/08xhr_spec.rb +209 -0
  45. data/spec/09bookmarks_spec.rb +60 -0
  46. data/spec/10auth_spec.rb +33 -0
  47. data/spec/spec_helper.rb +40 -0
  48. metadata +228 -0
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bats
2
+
3
+ @test "command usage" {
4
+ noms2 | grep Usage:
5
+ }
6
+
7
+ @test "command error" {
8
+ run noms2 foo
9
+ [ $status -ne 0 ]
10
+ echo "$output" | grep -q "noms error: noms command \"foo\" not found: not a URL or bookmark"
11
+ }
12
+
13
+ @test "command js error" {
14
+ run noms2 'data:application/json,{"$doctype":"noms-v2","$script":["window.alert(\"test error string\")"],"$body":[]}'
15
+ echo "$output" | grep -q "test error string"
16
+ }
17
+
18
+ @test "command js error (different namespace)" {
19
+ run noms2 'data:application/json,{"$doctype":"noms-v2","$script":["alert(\"test error string\")"],"$body":[]}'
20
+ echo "$output" | grep -q "test error string"
21
+ }
22
+
23
+
24
+ @test "scriptable auth" {
25
+ rake start
26
+ chmod 0600 test/identity
27
+ noms2 -i test/identity http://localhost:8787/auth/dnc.json | grep -q Usage:
28
+ ec=$?
29
+ rake stop
30
+ [ $ec -eq 0 ]
31
+ }
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'noms/command/application'
6
+
7
+ describe "NOMS::Command::Application" do
8
+
9
+ before(:all) do
10
+ setup_fixture 'test'
11
+ end
12
+
13
+ after(:all) do
14
+ teardown_fixture 'test'
15
+ end
16
+
17
+ describe '.new' do
18
+ context 'with local file' do
19
+ before(:all) do
20
+ @doc = NOMS::Command::Application.new(
21
+ "file:///#{Dir.pwd}/test/public/files/foo.json", [])
22
+ @doc.fetch!
23
+ end
24
+
25
+ specify { expect(@doc.type).to eq 'noms-v2' }
26
+ specify { expect(@doc.body).to have_key '$doctype' }
27
+ specify { expect(@doc.body).to have_key '$body' }
28
+ specify { expect(@doc.display).to eq 'Test output for foo.json' }
29
+ end
30
+
31
+ context 'with data URL' do
32
+ before(:all) do
33
+ @doc = NOMS::Command::Application.new(
34
+ 'data:application/json,{"$doctype":"noms-v2","$body":[]}',
35
+ [])
36
+ @doc.fetch!
37
+ end
38
+
39
+ specify { expect(@doc.type).to eq 'noms-v2' }
40
+ specify { expect(@doc.body).to have_key '$doctype' }
41
+ specify { expect(@doc.body).to have_key '$body' }
42
+ specify { expect(@doc.display).to eq '' }
43
+ end
44
+ end
45
+
46
+ end
47
+
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'uri'
6
+ require 'noms/command/application'
7
+ require 'noms/command/window'
8
+
9
+ describe NOMS::Command::Application do
10
+
11
+ before(:all) do
12
+ # Start the DNC application web server on port 8787
13
+ setup_fixture
14
+ start_server
15
+ end
16
+
17
+ after(:all) do
18
+ stop_server
19
+ teardown_fixture
20
+ end
21
+
22
+ describe '.new' do
23
+
24
+ context 'with no arguments' do
25
+
26
+ before(:each) do
27
+ @app = NOMS::Command::Application.new(
28
+ URI.parse('http://localhost:8787/dnc.json'),
29
+ [])
30
+ end
31
+
32
+ end
33
+
34
+ it "should produce a usage message" do
35
+ app = NOMS::Command::Application.new('http://localhost:8787/dnc.json',
36
+ ['dnc'])
37
+ app.fetch!
38
+ app.render!
39
+ expect(app.display).to include 'Usage:'
40
+ end
41
+
42
+ it "should produce a list of DNC records" do
43
+ app = NOMS::Command::Application.new('http://localhost:8787/dnc.json',
44
+ ['dnc', 'list'])
45
+ app.fetch!
46
+ app.render!
47
+ expect(app.display.split("\n").length).to be > 9
48
+ end
49
+
50
+ it "should follow a redirect" do
51
+ app = NOMS::Command::Application.new('http://localhost:8787/alt/dnc.json',
52
+ ['dnc', 'list'])
53
+ app.fetch!
54
+ app.render!
55
+ expect(app.display.split("\n").length).to be > 9
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'yaml'
6
+
7
+ require 'noms/command/formatter'
8
+
9
+ describe NOMS::Command::Formatter do
10
+
11
+ describe '#render' do
12
+
13
+ subject(:formatter) { NOMS::Command::Formatter.new }
14
+
15
+ it 'renders nil as ""' do
16
+ expect(formatter.render(nil)).to eq ''
17
+ end
18
+
19
+ it 'renders a string as a string' do
20
+ expect(formatter.render("one")).to eq 'one'
21
+ end
22
+
23
+ it 'renders a number as a string' do
24
+ expect(formatter.render(1)).to eq '1'
25
+ end
26
+
27
+ it 'renders a boolean as a string' do
28
+ expect(formatter.render(true)).to eq 'true'
29
+ end
30
+
31
+ it 'renders an array of strings as a list of lines' do
32
+ lines = [
33
+ 'one',
34
+ 'two',
35
+ 'three' ]
36
+ expect(formatter.render(lines)).to eq "one\ntwo\nthree"
37
+ end
38
+
39
+ it 'renders a raw object as a YAML object' do
40
+ object = {
41
+ 'one' => 1,
42
+ 'two' => 2,
43
+ 'three' => 3
44
+ }
45
+ expect(formatter.render(object)).to eq object.to_yaml
46
+ end
47
+
48
+ it 'renders a object-list as formatted lines' do
49
+ object_list = {
50
+ '$type' => 'object-list',
51
+ '$header' => true,
52
+ '$columns' => [
53
+ {
54
+ 'field' => 'priority',
55
+ 'width' => 3,
56
+ 'align' => 'right',
57
+ 'heading' => 'Pri'
58
+ },
59
+ {
60
+ 'field' => 'title',
61
+ 'width' => 10,
62
+ 'heading' => 'Name'
63
+ },
64
+ 'description'
65
+ ],
66
+ '$data' => [
67
+ {
68
+ 'title' => 'Issue 1',
69
+ 'priority' => 3,
70
+ 'description' => 'The first issue'
71
+ },
72
+ {
73
+ 'title' => 'Issue 2',
74
+ 'priority' => 2,
75
+ 'description' => 'The second issue'
76
+ }
77
+ ]
78
+ }
79
+ output = <<-TEST.gsub(/^\s{12}/,'').gsub(/\n$/,'')
80
+ Pri Name description
81
+ 3 Issue 1 The first issue
82
+ 2 Issue 2 The second issue
83
+ TEST
84
+ expect(formatter.render(object_list)).to eq output
85
+ end
86
+
87
+ it 'renders an object as record-formatted' do
88
+ object = {
89
+ '$type' => 'object',
90
+ '$data' => {
91
+ 'title' => 'Issue 2',
92
+ 'priority' => 3,
93
+ 'description' => 'The second issue',
94
+ 'contributors' => [
95
+ 'fred',
96
+ 'wilma',
97
+ 'barney',
98
+ 'gazoo'
99
+ ]
100
+ }
101
+ }
102
+ output = <<-TEST.gsub(/^\s{12}/,'').gsub(/\n$/,'')
103
+ contributors: ["fred","wilma","barney","gazoo"]
104
+ description: The second issue
105
+ priority: 3
106
+ title: Issue 2
107
+ TEST
108
+ expect(formatter.render(object)).to eq output
109
+ end
110
+
111
+ it 'honors field list and option for object' do
112
+ object = {
113
+ '$type' => 'object',
114
+ '$fields' => ['title'],
115
+ '$labels' => false,
116
+ '$data' => {
117
+ 'title' => 'Issue 2',
118
+ 'priority' => 3,
119
+ 'description' => 'The second issue',
120
+ 'contributors' => [
121
+ 'fred',
122
+ 'wilma',
123
+ 'barney',
124
+ 'gazoo'
125
+ ]
126
+ }
127
+ }
128
+ output = 'Issue 2'
129
+ expect(formatter.render(object)).to eq output
130
+ end
131
+
132
+ it 'renders an object-list as CSV' do
133
+ object_list = {
134
+ '$type' => 'object-list',
135
+ '$header' => true,
136
+ '$format' => 'csv',
137
+ '$columns' => [
138
+ {
139
+ 'field' => 'priority',
140
+ 'width' => 3,
141
+ 'align' => 'right',
142
+ 'heading' => 'Pri'
143
+ },
144
+ {
145
+ 'field' => 'title',
146
+ 'width' => 10,
147
+ 'heading' => 'Name'
148
+ },
149
+ 'description'
150
+ ],
151
+ '$data' => [
152
+ {
153
+ 'title' => 'Issue 1',
154
+ 'priority' => 3,
155
+ 'description' => 'The first issue'
156
+ },
157
+ {
158
+ 'title' => 'Issue 2',
159
+ 'priority' => 2,
160
+ 'description' => 'The second issue'
161
+ }
162
+ ]
163
+ }
164
+ output = <<-TEST.gsub(/^\s{12}/,'').gsub(/\n$/,'')
165
+ Pri,Name,description
166
+ 3,Issue 1,The first issue
167
+ 2,Issue 2,The second issue
168
+ TEST
169
+ expect(formatter.render(object_list)).to eq output
170
+ end
171
+
172
+ it 'renders an object as JSON' do
173
+ object = {
174
+ '$type' => 'object',
175
+ '$format' => 'json',
176
+ '$fields' => ['title','priority'],
177
+ '$data' => {
178
+ 'title' => 'Issue 2',
179
+ 'priority' => 3,
180
+ 'description' => 'The second issue',
181
+ 'contributors' => [
182
+ 'fred',
183
+ 'wilma',
184
+ 'barney',
185
+ 'gazoo'
186
+ ]
187
+ }
188
+ }
189
+ output = JSON.pretty_generate({ 'title' => 'Issue 2', 'priority' => 3 })
190
+ expect(formatter.render(object)).to eq output
191
+ end
192
+
193
+ end
194
+
195
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'noms/command/urinion/data'
6
+
7
+ describe NOMS::Command::URInion::Data do
8
+ describe '.parse' do
9
+ context 'when parsing JSON' do
10
+ subject(:url) { NOMS::Command::URInion::Data.parse('data:application/json;charset=UTF-8,{"one":1}') }
11
+ specify { expect(url.host).to be_nil }
12
+ specify { expect(url.scheme).to eq 'data' }
13
+ specify { expect(url.path).to be_nil }
14
+ specify { expect(url.data_encoding).to be_nil }
15
+ specify { expect(url.mime_type).to eq 'application/json' }
16
+ specify { expect(url.character_set).to eq 'UTF-8' }
17
+ specify { expect(url.data).to eq '{"one":1}' }
18
+ end
19
+ end
20
+ end
data/spec/07js_spec.rb ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'json'
6
+ require 'base64'
7
+ require 'logger'
8
+
9
+ require 'noms/command/application'
10
+
11
+ def make_script_app(js, opt={})
12
+ doc = {
13
+ '$doctype' => 'noms-v2',
14
+ '$body' => ['Usage:',' noms echo <string>'],
15
+ '$script' => [js]
16
+ }
17
+ NOMS::Command::Application.new('data:application/json,' + JSON.pretty_generate(doc),
18
+ %w(echo one two three), opt)
19
+ end
20
+
21
+ describe NOMS::Command::Application do
22
+
23
+ describe '.render!' do
24
+
25
+ subject(:doc) do
26
+ make_script_app "window.document.body = window.document.argv.slice(1).join(' ')"
27
+ end
28
+
29
+ it "should have initial output" do
30
+ doc.fetch!
31
+ expect(doc.display).to eq "Usage:\n noms echo <string>"
32
+ end
33
+
34
+ it "should render by echoing arguments" do
35
+ doc.fetch!
36
+ doc.render!
37
+ expect(doc.display).to eq "one two three"
38
+ end
39
+
40
+ end
41
+
42
+ context 'in javascript runtime' do
43
+
44
+ describe ' window' do
45
+
46
+ describe '.name' do
47
+
48
+ it "should have a name" do
49
+ app = make_script_app "document.body = window.name"
50
+ app.window.name = 'test-js_spec'
51
+ app.fetch!
52
+ app.render!
53
+ expect(app.display).to eq 'test-js_spec'
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ describe 'console' do
61
+
62
+ it "should produce debugging output" do
63
+ logcatcher = StringIO.new
64
+ log = Logger.new(logcatcher)
65
+ log.level = Logger::DEBUG
66
+
67
+ app = make_script_app("console.log('test debug output')", :logger => log)
68
+ app.fetch!
69
+ app.render!
70
+ expect(logcatcher.string).to match(/^D,.*test debug output$/)
71
+ end
72
+
73
+ end
74
+
75
+ describe 'location' do
76
+
77
+ it "should describe URL location" do
78
+ app = make_script_app "document.body = location.protocol"
79
+ app.fetch!
80
+ app.render!
81
+ expect(app.display).to eq 'data'
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'v8'
6
+ require 'json'
7
+
8
+ require 'noms/command/xmlhttprequest'
9
+
10
+ describe NOMS::Command::XMLHttpRequest do
11
+
12
+ before(:all) do
13
+ # I'm going to go with this class variable for now
14
+
15
+ # Start the DNC application web server on port 8787
16
+ setup_fixture 'test'
17
+ start_server 'test'
18
+ end
19
+
20
+ after(:all) do
21
+ stop_server 'test'
22
+ teardown_fixture 'test'
23
+ end
24
+
25
+ before(:each) do
26
+ NOMS::Command::XMLHttpRequest.origin = 'http://localhost:8787/files/dnc.json'
27
+ NOMS::Command::XMLHttpRequest.useragent = nil
28
+ end
29
+
30
+ context 'from ruby' do
31
+
32
+ before(:each) do
33
+ @xhr = NOMS::Command::XMLHttpRequest.new
34
+ end
35
+
36
+ describe '#readyState' do
37
+ it 'should be 0' do
38
+ expect(@xhr.readyState).to eq 0
39
+ end
40
+ end
41
+
42
+ describe 'statuses' do
43
+ it 'should be equal to the correct number' do
44
+ expect(@xhr.OPENED).to eq 1
45
+ expect(@xhr.HEADERS_RECEIVED).to eq 2
46
+ expect(@xhr.LOADING).to eq 3
47
+ expect(@xhr.DONE).to eq 4
48
+ end
49
+ end
50
+
51
+ describe '#open' do
52
+ # open, send, check readyState and responseText
53
+
54
+ it 'should retrieve web content' do
55
+ @xhr.open('GET', 'http://localhost:8787/files/data.json', false)
56
+ @xhr.send()
57
+ expect(@xhr.responseText).to match /^\[/
58
+ end
59
+
60
+ it 'should retrieve web content from relative URL' do
61
+ @xhr.open('GET', '/files/data.json', false)
62
+ @xhr.send()
63
+ expect(@xhr.responseText).to match /^\[/
64
+ end
65
+
66
+ it 'should raise an error on different-origin' do
67
+ expect {
68
+ @xhr.open('GET', 'http://localhost:8786/files/data.json', false)
69
+ }.to raise_error NOMS::Command::Error
70
+ end
71
+
72
+ end
73
+
74
+ describe '#setRequestHeader' do
75
+
76
+ it 'should set the request header' do
77
+ @xhr.setRequestHeader('Accept', 'text/plain')
78
+ expect(@xhr.headers['Accept']).to eq 'text/plain'
79
+ end
80
+ end
81
+
82
+ describe '#abort' do
83
+
84
+ end
85
+
86
+ end
87
+
88
+ context 'from javascript' do
89
+
90
+ before(:each) do
91
+ @v8 = V8::Context.new
92
+ @v8[:XMLHttpRequest] = NOMS::Command::XMLHttpRequest
93
+ @v8.eval 'var xhr = new XMLHttpRequest()'
94
+ @xhr = @v8[:xhr]
95
+ end
96
+
97
+ describe '#open' do
98
+ # open, send, check readyState and responseText
99
+ it 'should retrieve web content' do
100
+ @v8.eval 'xhr.open("GET", "http://localhost:8787/files/data.json", false)'
101
+ @v8.eval 'xhr.send()'
102
+ expect(@v8.eval 'xhr.responseText').to match(/^\[/)
103
+ expect(@v8.eval 'xhr.readyState').to eq 4
104
+ end
105
+
106
+ it 'should retrieve web content from relative URL' do
107
+ @v8.eval 'xhr.open("GET", "/files/data.json", false)'
108
+ @v8.eval 'xhr.send()'
109
+ expect(@v8.eval 'xhr.responseText').to match(/^\[/)
110
+ expect(@v8.eval 'xhr.readyState').to eq 4
111
+ end
112
+
113
+ it 'should post objects to rest app' do
114
+ @v8[:newdata] = {
115
+ 'name' => 'Frances Simmons',
116
+ 'street' => '180 Tomkins Blcd',
117
+ 'city' => 'Minneapolis, MN 55401',
118
+ 'phone' => '(612) 555-0180'
119
+ }.to_json
120
+ @v8.eval 'xhr.open("POST", "/dnc", false);'
121
+ @v8.eval 'xhr.send(newdata);'
122
+ obj = JSON.parse(@v8.eval 'xhr.responseText')
123
+ expect(obj).to have_key 'id'
124
+
125
+ @v8.eval "xhr.open(\"GET\", \"/dnc/#{obj['id']}\", false);"
126
+ @v8.eval 'xhr.send();'
127
+ retobj = JSON.parse(@v8.eval 'xhr.responseText')
128
+ expect(retobj['name']).to eq 'Frances Simmons'
129
+ end
130
+
131
+ it 'should update objects in rest app' do
132
+ @v8.eval 'xhr.open("GET", "/dnc/1", false);'
133
+ @v8.eval 'xhr.send();'
134
+ obj = JSON.parse(@v8.eval 'xhr.responseText')
135
+ obj['phone'] = '(999) 555-9999'
136
+ @v8[:newobject] = obj.to_json
137
+
138
+ @v8.eval 'xhr.open("PUT", "/dnc/1", false);'
139
+ @v8.eval 'xhr.send(newobject);'
140
+ newobject = JSON.parse(@v8.eval 'xhr.responseText')
141
+ expect(newobject['id']).to eq 1
142
+ expect(newobject['phone']).to eq '(999) 555-9999'
143
+
144
+ @v8.eval 'xhr.open("GET", "/dnc/1", false);'
145
+ @v8.eval 'xhr.send();'
146
+ retobj = JSON.parse(@v8.eval 'xhr.responseText')
147
+ expect(retobj['phone']).to eq '(999) 555-9999'
148
+ end
149
+
150
+ it 'should delete objects in rest app' do
151
+ @v8.eval 'xhr.open("DELETE", "/dnc/2", false);'
152
+ @v8.eval 'xhr.send();'
153
+ expect(@v8.eval 'xhr.status').to eq 204
154
+
155
+ @v8.eval 'xhr.open("GET", "/dnc/2", false);'
156
+ @v8.eval 'xhr.send();'
157
+ expect(@v8.eval 'xhr.status').to eq 404
158
+ end
159
+
160
+ it 'should not follow cross-origin redirects' do
161
+ @v8.eval 'xhr.open("GET", "/readme", false);'
162
+ @v8.eval 'xhr.send();'
163
+ expect(@v8.eval 'xhr.status').to eq 302
164
+ expect(@v8.eval 'xhr.responseText').to eq 'README'
165
+ end
166
+
167
+ end
168
+
169
+ describe '#setRequestHeader' do
170
+
171
+ end
172
+
173
+ describe '#onreadystatechange' do
174
+
175
+ it 'should be triggered by state change events' do
176
+ @v8.eval <<-JS
177
+ var content = "";
178
+ xhr.onreadystatechange = function() {
179
+ if (this.readyState == this.DONE) {
180
+ content = this.responseText;
181
+ }
182
+ }
183
+ JS
184
+ @v8.eval 'xhr.open("GET", "/files/data.json", true)'
185
+ @v8.eval 'xhr.send()'
186
+ @xhr.useragent.wait
187
+ expect(@v8[:content]).to match(/^\[/)
188
+ end
189
+
190
+ end
191
+
192
+ describe '#abort' do
193
+
194
+ it 'cancels a request' do
195
+ # We don't really have true asynchronous requests,
196
+ # in particular we can't interrupt between calling
197
+ # send() and the readyState changing to 4. So
198
+ # this is kind of a no-op.
199
+
200
+ @v8.eval 'xhr.open("GET", "/files/data.json", true);'
201
+ @v8.eval 'xhr.abort();'
202
+ expect(@v8.eval 'xhr.readyState').to eq 0
203
+ end
204
+
205
+ end
206
+
207
+ end
208
+
209
+ end