noms-command 0.5.0 → 2.1.1

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 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(