noms-command 0.5.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0bcefa4ec6a121ab3dc7f9be3c7242a87f58203c
4
- data.tar.gz: f12c00d11f170af8cb53f4ed3349d20d9ee74505
3
+ metadata.gz: a2ac7a3782b3dc374c77d11d618d66ac99d37c45
4
+ data.tar.gz: 241d7122a0756c8f8f5229d6ebfabcbd66eedcba
5
5
  SHA512:
6
- metadata.gz: f6d50e6bb50f761f49f766ffec47db3c8fbddadcc55cfa78223c99ffb2ceffcced639cc08e5a299d1bdcf3c11035cb337da761db5e3eb39532b40724bd465213
7
- data.tar.gz: 5c4e2f8970d23764a8cfabe04448748c2f9f717b16bd8073808ad35ca1b377b6f6558e113a99fe487b2540ffd6b57067055545b477e63084d4edf3fca279faa8
6
+ metadata.gz: 760eb723b068038cdf9a65b082c22610a44ebcda16fe745ec2d7b415498d399bf36d89a1b162e18d026f6c80a7a5061a926a99691fde8c68857707eb2b389489
7
+ data.tar.gz: dcbd6336014c56f90e70d9e64957650fe5aa61c287ffb0c1be0f8b1b2892d620fd72204dcba9ebf5f9771f1df320382e26a1a5f489bcca6164b1b7a8d9fce559
data/README.rst CHANGED
@@ -54,18 +54,18 @@ Syntax
54
54
 
55
55
  The basic way of invoking a **noms** command is as follows::
56
56
 
57
- noms *url* *options* *arguments*
57
+ noms <url> <options> <arguments>
58
58
 
59
59
  **noms** invokes the app at *url* with the given options and
60
- arguments, displaying the results.
60
+ arguments, displaying the results.
61
61
 
62
62
  **noms** has its own options, which can be given before the
63
- *url*, and are available when invoking ``noms --help`.
63
+ *url*, and are available when invoking ``noms --help``.
64
64
 
65
65
  Bookmarks
66
66
  ~~~~~~~~~
67
67
 
68
- * ``noms *bookmark*[/arg] ...``
68
+ * ``noms <bookmark>[/extra] ...``
69
69
 
70
70
  **noms** bookmarks are stored, by default, in ``~/.noms/bookmarks.json``,
71
71
  ``/usr/local/etc/noms/bookmarks.json``, and ``/etc/noms/bookmarks.json``
@@ -101,7 +101,8 @@ strictness policies may be configurable in a file, but the point of
101
101
  **noms** is to remove the distribution of thick client libraries that
102
102
  try to abstract away server-side interfaces and the configurations
103
103
  they require, so it would defeat part of its purpose to allow rich
104
- configuration of things like default values for objects and so forth.
104
+ configuration of things like default values for objects, extra
105
+ (application-level) connection or protocol information and so forth.
105
106
 
106
107
  Implementation
107
108
  --------------
@@ -119,28 +120,71 @@ according to `Dynamic Doctype`_, below. Otherwise, it is assumed to be
119
120
  structured list or object data, and **noms** will render the object or
120
121
  array using its default format (usually YAML).
121
122
 
123
+ Otherwise, **noms** assumes the data may be binary and will send it
124
+ to stdout as long as stdout is not a terminal.
125
+
122
126
  Authentication
123
127
  --------------
124
128
 
125
- *NOTE:* This is under active development, expect it to change
126
- frequently.
127
-
128
- **noms** supports Basic authentication. It will prompt the user for
129
- a username and password when required. A saved identity file can be
130
- provided with the ``--identity`` option to **noms** for non-interactive
131
- use.
129
+ **noms** supports Basic authentication. It prompts the user for a
130
+ username and password when authorization is required, and saves the
131
+ authentication identity information in a "vault" to be used for future
132
+ invocations (that is, you don't need to enter a password for every
133
+ **noms** command run).
134
+
135
+ If you are "idle" for an hour (have not used the saved authentication
136
+ vault for any web transactions), **noms** will time out the vault and
137
+ you will need to re-authenticate.
138
+
139
+ You can "close" the vault immediately by using the ``--logout``
140
+ option. I strongly suggest you call this in your ``~/.logout``
141
+ mechanism so that it runs immediately when you have completed a
142
+ login session.
143
+
144
+ You can bypass the "vault" by running **noms** with the
145
+ ``--plaintext-identity`` option. This has the effect of caching these
146
+ credentials permanently (well, until they are no longer good). You
147
+ should only use this option to generate an identity file for service
148
+ (non-interactive) accounts that need perpetual access to a web
149
+ service. The file will appear in the ``~/.noms/identities`` directory
150
+ in a file named for the SHA-1 hash of the munged authentication realm
151
+ and domain.
152
+
153
+ Such a saved identity file can be provided with the ``--identity`` option
154
+ to **noms** for non-interactive use.
155
+
156
+ Security
157
+ ~~~~~~~~
158
+
159
+ **noms** uses strong cryptography to implement the "vault" metaphor, but
160
+ this is only a metaphor and the security provided is limited. The vault
161
+ is opened and closed by means of an on-disk key. Because you can easily
162
+ remove it and because it times out when idle (when this happens, **noms**
163
+ overwrites it the next time it tries to use it), it's significantly better
164
+ than ``.netrc`` files or passwords in environment variables or on the
165
+ command-line; and more ergonomic as well. In the future, this cryptographic
166
+ service will be provided by an independent **noms-agent**, similar to
167
+ ssh-agent or gpg-agent. These still have the same problem in that they
168
+ reduce the security of the system to operating system mechanisms rather
169
+ than cryptographic mechanisms, but it is still an improvement.
132
170
 
133
171
  Dynamic Doctype
134
- ~~~~~~~~~~~~~~~
172
+ ---------------
135
173
 
136
174
  The dynamic doctype is the ``noms-v2`` type, which is an object with
137
175
  the following top-level attributes:
138
176
 
139
177
  ``$doctype``
140
- Must be ``noms-v2``. In future, backwards-incompatible extensions may be implemented in ``noms-v3`` or higher doctypes.
178
+ Must be ``noms-v2``. In future, backwards-incompatible extensions
179
+ may be implemented in ``noms-v3`` or higher doctypes.
141
180
 
142
181
  ``$script``
143
- An ordered array of scripts to fetch and evaluate; or Javascript text to evaluate directly.
182
+ An ordered array of script references to fetch and evaluate; or
183
+ Javascript strings to evaluate directly. A script reference
184
+ consists of object with a ``$source`` key, the value of which
185
+ is the URL of the script to load. Additional fields in the
186
+ object are ignored and can be used to document the origin
187
+ or license of the scripts.
144
188
 
145
189
  ``$argv``
146
190
  The arguments passed to the application. It's called ``$argv``
@@ -148,16 +192,18 @@ the following top-level attributes:
148
192
  invoked (that is, the bookmark or URL).
149
193
 
150
194
  ``$exitcode``
151
- The unix process exit code with which **noms** will exit at the completion of the command.
195
+ The unix process exit code with which **noms** will exit at the
196
+ completion of the command.
152
197
 
153
198
  ``$body``
154
- The body of the document is the data to display. See `Output Formatting`_ below.
199
+ The body of the document is the data to display. See `Output
200
+ Formatting`_ below.
155
201
 
156
202
  From the perspective of javascript executing within the application,
157
203
  these are accessible as properties of the global **document** object
158
204
  (e.g., ``document.argv`` is the array of arguments given on the **noms**
159
205
  command line; Javascript can set ``document.exitcode`` to determine
160
- **noms'** exit code.
206
+ **noms'** exit code).
161
207
 
162
208
  Output Formatting
163
209
  ~~~~~~~~~~~~~~~~~
@@ -202,8 +248,6 @@ The following entities are allowed in the body of a **noms-v2** document:
202
248
 
203
249
  * **labels**: Default ``true``; whether to display header row with field labels
204
250
 
205
- * **columns**: Field names, headings and widths
206
-
207
251
  * **data**: The objects to render
208
252
 
209
253
  * ``$type``: **object** An object has the following attributes:
@@ -251,7 +295,7 @@ Scripts have access to the following global objects:
251
295
  * **argv** - The arguments being invoked. The first element of this
252
296
  array is the first argument passed to **noms** itself (not the
253
297
  script it ultimately fetches, but how it's invoked, similar to
254
- ``$1``.
298
+ ``$0``.
255
299
 
256
300
  * **exitcode** - The numeric exit code with which **noms** will
257
301
  exit. Initially 0.
@@ -264,20 +308,6 @@ Scripts have access to the following global objects:
264
308
 
265
309
  .. _`NOMS::Command::XMLHttpRequest`: http://www.rubydoc.info/gems/noms-command/NOMS/Command/XMLHttpRequest
266
310
 
267
-
268
- Web 1.0 vs Web 2.0
269
- ------------------
270
-
271
- Like the "real web", **noms** commands can choose to do some
272
- calculation on the server and some on the client: **noms** doesn't
273
- care. You can use no ``$script`` tag at all and just calculate the
274
- entire document to be rendered in the client (though this currently
275
- doesn't allow for argument interpretation, in the future the
276
- arguments may be passed in request headers or **noms** may allow a way
277
- for them to show up in a query string or POST request--but **noms** is
278
- not really a command-line http client either). This is up to the
279
- application designer.
280
-
281
311
  Example Application
282
312
  -------------------
283
313
 
@@ -366,11 +396,48 @@ The example application is a very simple sinatra REST API to a data
366
396
  store consisting of a JSON file, and the static files comprising the
367
397
  Javascript source code and the **noms** application document.
368
398
 
369
- Running Examples
370
- ----------------
399
+ Hacking/Running Examples
400
+ ------------------------
401
+
402
+ Use Ruby 1.9.3 or higher (e.g. if you need to set PATH
403
+ so that Ruby 1.9 executables are found, do that:
404
+ ``export PATH=/usr/local/ruby1.9/bin:$PATH``.
371
405
 
372
406
  Use ``rake start`` to start the test webserver and run the
373
407
  example applications (see the comments inside the
374
408
  ``fixture/public/*.json`` files for syntax).
375
409
 
376
410
  Start with ``noms2 http://localhost:8787/echo.json hello world``.
411
+
412
+ Workflow
413
+ ~~~~~~~~
414
+
415
+ Set up your environment::
416
+
417
+ mkdir ~/.noms
418
+ echo '{ "dnc": "http://localhost:8787/dnc.json" }' >~/.noms/bookmarks.json
419
+ export PATH=`pwd`/bin:$PATH
420
+ expert RUBYLIB=lib
421
+ bundle install
422
+ noms2 # NOMS usage message
423
+ noms2 dnc # dnc usage message
424
+
425
+ Do ``rake start`` to start the webserver: web root is is ``test/``.
426
+
427
+ Hack files in:
428
+
429
+ * ``lib/`` - Ruby files for ``noms2`` command
430
+
431
+ * ``fixture/dnc.rb`` - Sinatra app which is webserver for dnc app (serves
432
+ static files and implements rest interface).
433
+
434
+ * ``fixture/public/dnc.json`` - App document for 'dnc' subcommand.
435
+
436
+ * ``fixture/public/lib`` - Javascript files, ``dnc.js`` implements dnc
437
+ operations
438
+
439
+ Do ``rake sync`` to sync over updated files from ``fixture`` and test
440
+ (the webserver document root is ``test/public``).
441
+
442
+ ``noms2 -d`` produces debugging showing full stack traces for Javascript
443
+ errors, ``console.log()`` output and web traffic.
data/TODO.rst CHANGED
@@ -1,7 +1,7 @@
1
1
  TODO
2
2
  ====
3
3
 
4
- * Flesh out example application CLI.
5
- * Auth sessions
6
- * Caching
4
+ * Warn for basic auth over http
5
+ * Warn for plaintext identity
6
+ * Caching - Consider switching to Typhoeus/libcurl
7
7
  * SSL handling, TOFU for SSL
data/fixture/dnc.rb CHANGED
@@ -7,7 +7,7 @@ class DNC < Sinatra::Application
7
7
 
8
8
  set :port, 8787
9
9
  set :root, File.expand_path("#{File.dirname(__FILE__)}")
10
- enable :static
10
+ enable :static, :sessions
11
11
 
12
12
  File.open(File.join(settings.root, 'dnc.pid'), 'w') {|f| f.puts Process.pid }
13
13
 
@@ -30,6 +30,41 @@ class DNC < Sinatra::Application
30
30
  @auth ||= Rack::Auth::Basic::Request.new(request.env)
31
31
  @auth.provided? and @auth.basic? and @auth.credentials and @auth.credentials == ['testuser', 'testpass']
32
32
  end
33
+
34
+ def generated_body(h={})
35
+ JSON.pretty_generate({ 'generated' => Time.now.httpdate }.merge(h)) + "\n"
36
+ end
37
+
38
+ def require_cookie_auth
39
+ return if cookie_authorized?
40
+ redirect "/cookie/login?return_to=#{CGI.escape(request.path)}"
41
+ end
42
+
43
+ def cookie_authorized?
44
+ session[:userid] == 'testuser'
45
+ end
46
+ end
47
+
48
+ before do
49
+ content_type 'application/json'
50
+ end
51
+
52
+ get '/cookie/login' do
53
+ require_auth
54
+ session[:userid] = @auth.credentials.first
55
+ landing = params[:return_to] || '/cookie/home'
56
+ redirect landing
57
+ end
58
+
59
+ get '/cookie/home' do
60
+ require_cookie_auth
61
+ generated_body({'cookie_user' => session[:userid] })
62
+ end
63
+
64
+ get '/cookie/logout' do
65
+ old_userid = session[:userid]
66
+ session[:userid] = nil
67
+ generated_body({'message' => "#{old_userid} logged out"})
33
68
  end
34
69
 
35
70
  get '/readme' do
@@ -66,6 +101,8 @@ class DNC < Sinatra::Application
66
101
  request.body.rewind
67
102
  new_object = JSON.parse request.body.read
68
103
 
104
+ puts "POST for object: #{new_object.inspect}"
105
+
69
106
  data = load_data
70
107
  # How unsafe is this?
71
108
  new_object['id'] = data.map { |e| e['id'] }.max + 1
@@ -115,6 +152,78 @@ class DNC < Sinatra::Application
115
152
  redirect to('/dnc.json')
116
153
  end
117
154
 
155
+ get '/auth/ok' do
156
+ require_auth
157
+ "SUCCESS"
158
+ end
159
+
160
+ # Caching client should let sit in cache
161
+ # for 4s then refetch
162
+ get '/static/max-age-4' do
163
+ cache_control :max_age => 4
164
+ generated_body
165
+ end
166
+
167
+ # Caching client must always revalidate
168
+ # even within 4s
169
+ get '/static/must-revalidate' do
170
+ cache_control :must_revalidate, :max_age => 4
171
+ expires 4
172
+ etag "10"
173
+ generated_body
174
+ end
175
+
176
+ # Caching client must never cache
177
+ get '/static/no-cache' do
178
+ cache_control :no_cache
179
+ generated_body
180
+ end
181
+
182
+ # Caching client should let sit in cache
183
+ # for 4s then refetch
184
+ get '/static/expires-4' do
185
+ expires 4
186
+ generated_body
187
+ end
188
+
189
+ # Caching client should let sit in cache
190
+ # for 4s then revalidate using If-Modified-Since
191
+ get '/static/last-modified' do
192
+ expires 4
193
+ $static_time ||= Time.now
194
+ last_modified $static_time
195
+ generated_body
196
+ end
197
+
198
+ # Caching client should let sit in cache
199
+ # for 4s then revalidate using If-None-Match
200
+ get '/static/expires-4-changing' do
201
+ expires 4
202
+ etag Time.now.httpdate
203
+ generated_body
204
+ end
205
+
206
+ # Caching client should let sit in cache
207
+ # for 2s then revalidate using If-None-Match
208
+ get '/static/expires-2-constant' do
209
+ etag "10"
210
+ expires 2
211
+ generated_body
212
+ end
213
+
214
+ get '/static/long-cache' do
215
+ etag "11"
216
+ expires 100
217
+ generated_body
218
+ end
219
+
220
+ get '/auth/cacheable' do
221
+ require_auth
222
+ expires 100
223
+ etag "11"
224
+ generated_body
225
+ end
226
+
118
227
  run! if app_file = $0
119
228
 
120
229
  end
@@ -8,15 +8,16 @@
8
8
  "$comment": "Optparse.js 1.0.3 - https://github.com/jfd/optparse-js; via rawgit.com" },
9
9
  { "$source": "https://rawgit.com/douglascrockford/JSON-js/master/json2.js",
10
10
  "$comment": "JSON in JavaScript - https://github.com/douglascrockford/JSON-js" },
11
- { "$source": "lib/noms-args.js" },
11
+ { "$source": "lib/nomsargs.js" },
12
12
  { "$source": "lib/dnc.js" }
13
13
  ],
14
14
  "$body": [
15
15
  "Usage:",
16
- " noms dnc query <field>=<value>",
16
+ " noms dnc query [<field>=<value> [...]] [<field> [...]]",
17
+ " noms dnc list [<field> [...]]",
18
+ " noms dnc show id [<field> [...]]",
17
19
  " noms dnc add <field>=<value> [<field>=<value> [...]]",
18
- " noms dnc remove <id>",
19
- " noms dnc check { <phone> | <name> }",
20
- " noms dnc list"
20
+ " noms dnc set id <field>=<value> [<field>=<value> [...]]",
21
+ " noms dnc remove <id>"
21
22
  ]
22
23
  }
@@ -1,32 +1,42 @@
1
1
  if (document.argv.length > 1) {
2
2
  var argv = document.argv;
3
3
  var me = argv.shift();
4
- var command;
5
4
  var format;
6
5
  var xmlhttp = new XMLHttpRequest();
7
6
 
8
- // document attributes can be set
9
- // but are immutable
10
- document.body = [ ];
11
- var output = [ ];
12
-
13
7
  var optspec = [
14
8
  ["-J", "--json", "Display JSON"],
15
9
  ["-Y", "--yaml", "Display YAML"],
16
10
  ["-C", "--csv", "Display CSV"],
17
11
  ["-v", "--verbose", "Enable verbose output"],
18
- ["--nofeedback", "Don't print feedback"]
12
+ ["-q", "--terse", "Use terse output"],
13
+ ["--nofeedback", "Don't print feedback"],
14
+ ["--nolabel", "Don't print field names"],
15
+ ["--noheader", "Don't print column headings"]
19
16
  ];
20
17
 
18
+ // document attributes can be set
19
+ // but are immutable
20
+ document.body = [ ];
21
+ var output = [ ];
22
+
21
23
  var parser = new optparse.OptionParser(optspec);
22
24
  var options = {
23
- "feedback": true,
24
25
  "format": "default",
25
- "verbose": false
26
+ "verbose": false,
27
+ "feedback": true,
28
+ "label": true,
26
29
  };
27
- var args = [ ];
28
30
 
29
- parser.on("verbose", function() { options["verbose"] = true });
31
+ var field_config = {
32
+ 'id': { 'field': 'id', 'width': 3, 'align': 'right' },
33
+ 'name': { 'field': 'name', 'width': 20 },
34
+ 'phone': { 'field': 'phone', 'width': 20 },
35
+ 'street': { 'field': 'street', 'width': 40 },
36
+ 'city': { 'field': 'city', 'width': 31 }
37
+ }
38
+
39
+ parser.on("verbose", function() { options["verbose"] = true; });
30
40
  parser.on("json", function() {
31
41
  options["format"] = "json";
32
42
  options["feedback"] = false;
@@ -40,36 +50,173 @@ if (document.argv.length > 1) {
40
50
  options["feedback"] = false;
41
51
  });
42
52
  parser.on("nofeedback", function() { options["feedback"] = false; });
43
- parser.on(0, function(arg) { command = arg });
44
- parser.on(function(arg) { args.push(arg); });
53
+ parser.on("nolabel", function() { options["label"] = false; });
54
+ parser.on("terse", function() {
55
+ options["feedback"] = false;
56
+ options["label"] = false;
57
+ });
58
+ // parser.on(0, function(arg) { command = arg; });
59
+
60
+ var args = parser.parse(argv);
61
+ var command = args.shift();
45
62
 
46
- parser.parse(argv);
63
+ var format, records, field_list;
47
64
 
48
65
  switch(command) {
49
- case "list":
50
- if (options["format"] === "default") {
51
- format = "lines";
66
+
67
+ case "add":
68
+ var keywords = new nomsargs.NomsArgs(args);
69
+
70
+ xmlhttp.open("POST", "/dnc", false);
71
+ xmlhttp.setRequestHeader("Content-type", "application/json");
72
+ xmlhttp.send(JSON.stringify(keywords.assignment));
73
+
74
+ if (xmlhttp.status == 201) {
75
+ record = JSON.parse(xmlhttp.responseText);
76
+ output.push("Entry created with id " + record['id']);
77
+ } else {
78
+ alert("Error " + xmlhttp.status + " creating entry");
79
+ document.exitcode = 2;
80
+ }
81
+
82
+ break;
83
+
84
+ case "remove":
85
+ var id = args.shift();
86
+
87
+ if (id == undefined) {
88
+ alert("No id to remove");
89
+ document.exitcode = 1;
90
+ } else {
91
+ xmlhttp.open("DELETE", "/dnc/" + id, false);
92
+ xmlhttp.send();
93
+
94
+ if (xmlhttp.status == 404) {
95
+ alert("Entry " + id + " does not exist");
96
+ } else if (xmlhttp.status != 204) {
97
+ alert("Error deleting id " + id);
98
+ document.exitcode = 2;
99
+ }
100
+ }
101
+
102
+ break;
103
+
104
+ case "set":
105
+ // We are not doing upsert
106
+ var id = args.shift();
107
+ var keywords = new nomsargs.NomsArgs(args);
108
+
109
+ if (id == undefined) {
110
+ alert("No id to set");
111
+ document.exitcode = 1;
52
112
  } else {
53
- format = options["format"];
113
+ xmlhttp.open("GET", "/dnc/" + id, false);
114
+ xmlhttp.send();
115
+
116
+ if (xmlhttp.status == 404) {
117
+ alert("Entry " + id + " does not exist (use add)");
118
+ document.exitcode = 2;
119
+ } else if (xmlhttp.status == 200) {
120
+ the_object = JSON.parse(xmlhttp.responseText);
121
+
122
+ // Update assigned fields in retrieved object
123
+ keywords.assignmentKeys().map(function (key) {
124
+ the_object[key] = keywords.assignment[key];
125
+ });
126
+
127
+ xmlhttp.open("PUT", "/dnc/" + id, false);
128
+ xmlhttp.send(JSON.stringify(the_object));
129
+
130
+ if (xmlhttp.status == 200) {
131
+ output.push("Entry " + id + " updated");
132
+ } else {
133
+ alert("Error " + xmlhttp.status + " updating entry " + id);
134
+ document.exitcode = 2;
135
+ }
136
+ }
137
+ }
138
+
139
+ break;
140
+
141
+ case "show":
142
+ var id = args.shift();
143
+ var field_list = args;
144
+ format = (options["format"] == "default" ? "record" : options["format"]);
145
+
146
+ if (id == undefined) {
147
+ alert("No id to show");
148
+ document.exitcode = 1;
149
+ } else {
150
+ xmlhttp.open("GET", "/dnc/" + id, false);
151
+ xmlhttp.send();
152
+
153
+ if (xmlhttp.status == 404) {
154
+ alert("Entry " + id + " not found");
155
+ document.exitcode = 2;
156
+ } else {
157
+ console.log("output for record");
158
+ record = JSON.parse(xmlhttp.responseText);
159
+
160
+ console.log(record);
161
+
162
+ output.push({
163
+ '$type': 'object',
164
+ '$format': format,
165
+ '$labels': options["label"],
166
+ '$fields': field_list,
167
+ '$data': record
168
+ });
169
+ }
170
+ }
171
+
172
+ break;
173
+
174
+ case "query":
175
+ var keywords = new nomsargs.NomsArgs(args);
176
+ format = (options["format"] == "default" ? "lines" : options["format"]);
177
+ var query = keywords.query();
178
+ var field_list = keywords.extra;
179
+
180
+ if (field_list.length == 0) {
181
+ field_list = ['id', 'name', 'phone'];
54
182
  }
183
+ xmlhttp.open("GET", "/dnc?" + query, false);
184
+ xmlhttp.send();
185
+ records = JSON.parse(xmlhttp.responseText);
186
+
187
+ output.push(
188
+ {
189
+ '$type': 'object-list',
190
+ '$format': format,
191
+ '$labels': options["label"],
192
+ '$columns': field_list.map(function(item) { return field_config[item]; }),
193
+ '$data': records
194
+ });
195
+ if (options["feedback"]) {
196
+ output.push(records.length + " objects");
197
+ }
198
+ break;
199
+
200
+ case "list":
201
+ format = (options["format"] == "default" ? "lines" : options["format"]);
202
+
55
203
  xmlhttp.open("GET", "/dnc", false);
56
204
  xmlhttp.send();
57
- var records = eval('(' + xmlhttp.responseText + ')');
205
+ records = JSON.parse(xmlhttp.responseText);
206
+ field_list = (args.length == 0 ? ['id', 'name', 'phone'] : args)
58
207
  output.push(
59
208
  {
60
209
  '$type': 'object-list',
61
210
  '$format': format,
62
- '$columns': [
63
- { 'field': 'id', 'width': 3, 'align': 'right' },
64
- { 'field': 'name', 'width': 20 },
65
- { 'field': 'phone', 'width': 20 }
66
- ],
211
+ '$labels': options["label"],
212
+ '$columns': field_list.map(function(item) { return field_config[item]; }),
67
213
  '$data': records
68
214
  });
69
215
  if (options["feedback"]) {
70
216
  output.push(records.length + " objects");
71
217
  }
72
218
  break;
219
+
73
220
  default:
74
221
  document.exitcode = 8;
75
222
  window.alert(