couch-shell 0.0.5 → 0.0.6

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.
@@ -0,0 +1,241 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require "couch-shell/plugin"
4
+
5
+ module CouchShell
6
+
7
+ class CorePlugin < Plugin
8
+
9
+ var "A fresh uuid from the CouchDB server."
10
+ def lookup_uuid
11
+ shell.execute "uuids"
12
+ if shell.responses.current(&:ok?)
13
+ json = shell.responses.current.json_value
14
+ if json && (uuids = json["uuids"]) && uuids.array? && uuids.length > 0
15
+ uuids[0]
16
+ else
17
+ raise ShellError, "unkown json structure"
18
+ end
19
+ else
20
+ raise ShellError, "uuids request failed"
21
+ end
22
+ end
23
+
24
+ var "Value of the id or _id member of the last response."
25
+ def lookup_id
26
+ shell.responses.current { |r| r.attr "id", "_id" } or raise VarNotSet
27
+ end
28
+
29
+ var "Value of the rev or _rev member of the last response."
30
+ def lookup_rev
31
+ @responses.current { |r| r.attr "rev", "_rev" } or raise VarNotSet
32
+ end
33
+
34
+ var "Shortcut for $(id)?rev=$(rev)."
35
+ def lookup_idr
36
+ shell.interpolate "$(id)?rev=$(rev)"
37
+ end
38
+
39
+ var "Content-Type of the last response."
40
+ def lookup_content_type
41
+ shell.responses.current(&:content_type)
42
+ end
43
+
44
+ var "Current server url."
45
+ def lookup_server
46
+ raise VarNotSet unless shell.server_url
47
+ u = shell.server_url
48
+ "#{u.scheme}://#{u.host}:#{u.port}#{u.path}"
49
+ end
50
+
51
+ var "Get response with index X."
52
+ def lookup_prefix_r(name)
53
+ i = name.to_i
54
+ raise VarNotSet unless shell.responses.readable_index?(i)
55
+ shell.responses[i]
56
+ end
57
+
58
+ var "Get json of response with index X."
59
+ def lookup_prefix_j(name)
60
+ i = name.to_i
61
+ if shell.responses.readable_index?(i)
62
+ if shell.responses[i].json_value
63
+ shell.responses[i].json_value
64
+ else
65
+ raise ShellError, "no json in response #{i}"
66
+ end
67
+ else
68
+ raise ShellError, "no response index #{i}"
69
+ end
70
+ end
71
+
72
+ def request_command_with_body(method, argstr)
73
+ if argstr =~ CouchShell::JSON_DOC_START_RX
74
+ url, bodyarg = nil, argstr
75
+ else
76
+ url, bodyarg= argstr.split(/\s+/, 2)
77
+ end
78
+ if bodyarg && bodyarg.start_with?("@")
79
+ filename, content_type = bodyarg[1..-1].split(/\s+/, 2)
80
+ body = CouchShell::FileToUpload.new(filename, content_type)
81
+ else
82
+ body = bodyarg
83
+ end
84
+ real_url = shell.interpolate(url)
85
+ shell.request method, real_url, body
86
+ real_url
87
+ end
88
+
89
+ cmd "Perform a GET http request.", synopsis: "[URL]"
90
+ def execute_get(argstr)
91
+ shell.request "GET", shell.interpolate(argstr)
92
+ end
93
+
94
+ cmd "Perform a PUT http request.",
95
+ synopsis: "[URL] [JSON|@FILENAME]"
96
+ def execute_put(argstr)
97
+ request_command_with_body("PUT", argstr)
98
+ end
99
+
100
+ cmd "put, followed by cd if put was successful"
101
+ def execute_cput(argstr)
102
+ url = request_command_with_body("PUT", argstr)
103
+ cd url if shell.responses.current(&:ok?)
104
+ end
105
+
106
+ cmd "Perform a POST http request.",
107
+ synopsis: "[URL] [JSON|@FILENAME]"
108
+ def execute_post(argstr)
109
+ request_command_with_body("POST", argstr)
110
+ end
111
+
112
+ cmd "Perform a DELETE http request.", synopsis: "[URL]"
113
+ def execute_delete(argstr)
114
+ shell.request "DELETE", shell.interpolate(argstr)
115
+ end
116
+
117
+ cmd "Change current path which will be used to interpret relative urls.",
118
+ synopsis: "[PATH]"
119
+ def execute_cd(argstr)
120
+ shell.cd shell.interpolate(argstr), false
121
+ end
122
+
123
+ cmd "cd followed by get",
124
+ synopsis: "[PATH]"
125
+ def execute_cg(argstr)
126
+ shell.cd shell.interpolate(argstr), true
127
+ end
128
+
129
+ cmd "quit shell"
130
+ def execute_exit(argstr)
131
+ raise Quit
132
+ end
133
+
134
+ cmd "quit shell"
135
+ def execute_quit(argstr)
136
+ raise Quit
137
+ end
138
+
139
+ cmd "Request uuid(s) from CouchDB server.",
140
+ synopsis: "[COUNT]"
141
+ def execute_uuids(argstr)
142
+ count = argstr ? argstr.to_i : 1
143
+ shell.request "GET", "/_uuids?count=#{count}"
144
+ end
145
+
146
+ cmd "Echos ARG after interpolating $(...) expressions.",
147
+ synopsis: "[ARG]"
148
+ def execute_echo(argstr)
149
+ if argstr
150
+ shell.stdout.puts shell.interpolate(argstr)
151
+ end
152
+ end
153
+
154
+ cmd "Evaluate EXPR and print the result in a compact form.",
155
+ synopsis: "EXPR"
156
+ def execute_print(argstr)
157
+ raise ShellError, "expression required" unless argstr
158
+ shell.stdout.puts shell.eval_expr(argstr)
159
+ end
160
+
161
+ cmd "Evaluate EXPR and print the result in a pretty form.",
162
+ synopsis: "EXPR"
163
+ def execute_format(argstr)
164
+ raise ShellError, "expression required" unless argstr
165
+ val = shell.eval_expr(argstr)
166
+ if val.respond_to?(:couch_shell_format_string!)
167
+ shell.stdout.puts val.couch_shell_format_string!
168
+ else
169
+ shell.stdout.puts val
170
+ end
171
+ end
172
+
173
+ cmd "Set URL of CouchDB server.",
174
+ synopsis: "[URL]"
175
+ def execute_server(argstr)
176
+ shell.server = argstr
177
+ end
178
+
179
+ cmd "Show full url for PATH after interpolation.",
180
+ synopsis: "[PATH]"
181
+ def execute_expand(argstr)
182
+ shell.stdout.puts shell.expand(shell.interpolate(argstr))
183
+ end
184
+
185
+ cmd "Execute COMMAND in your operating system's shell.",
186
+ synopsis: "COMMAND"
187
+ def execute_sh(argstr)
188
+ raise ShellError, "argument required" unless argstr
189
+ unless system(argstr)
190
+ shell.errmsg "command exited with status #{$?.exitstatus}"
191
+ end
192
+ end
193
+
194
+ cmd "Set member KEY of document at current path to VALUE.",
195
+ synopsis: "KEY VALUE"
196
+ def execute_member(argstr)
197
+ id, rev = nil, nil
198
+ json = shell.responses.current(&:json_value)
199
+ unless json && (id = json.attr_or_nil!("_id")) &&
200
+ (rev = json.attr_or_nil!("_rev")) &&
201
+ (shell.pathstack.size > 0) &&
202
+ (shell.pathstack.last == id.to_s)
203
+ raise ShellError,
204
+ "`cg' the desired document first, e.g.: `cg /my_db/my_doc_id'"
205
+ end
206
+ # TODO: read json string as attribute name if argstr starts with double
207
+ # quote
208
+ attr_name, new_valstr = argstr.split(/\s+/, 2)
209
+ unless attr_name && new_valstr
210
+ raise ShellError,
211
+ "attribute name and new value argument required"
212
+ end
213
+ if new_valstr == "remove"
214
+ json.delete_attr!(attr_name)
215
+ else
216
+ new_val = JsonValue.parse(new_valstr)
217
+ json.set_attr!(attr_name, new_val)
218
+ end
219
+ shell.request "PUT", "?rev=#{rev}", json.to_s
220
+ end
221
+
222
+ cmd "Set the USERNAME and password for authentication in requests.",
223
+ synopsis: "USERNAME",
224
+ doc_text: "Prompts for password."
225
+ def execute_user(argstr)
226
+ shell.prompt_msg("Password:", false)
227
+ shell.password = shell.read_secret
228
+ # we save the username only after the password was entered
229
+ # to allow cancellation during password input
230
+ shell.username = argstr
231
+ end
232
+
233
+ cmd "Use PLUGIN.",
234
+ synopsis: "PLUGIN"
235
+ def execute_plugin(argstr)
236
+ shell.plugin argstr
237
+ end
238
+
239
+ end
240
+
241
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require "couch-shell/plugin"
4
+
5
+ module CouchShell
6
+
7
+ class CoreDesignsPlugin < Plugin
8
+
9
+ def all_designs_url
10
+ '/' + dbname! + '/_all_docs?startkey="_design/"&endkey="_design0"'
11
+ end
12
+
13
+ cmd "Get a list of design names in current database."
14
+ def execute_designs(argstr)
15
+ raise ShellError, "argument not allowed" if argstr
16
+ res = request!("GET", all_designs_url, nil, false)
17
+ res.json["rows"].each { |row|
18
+ shell.stdout.puts row["key"].sub(%r{\A_design/}, '')
19
+ }
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require "couch-shell/plugin"
4
+
5
+ module CouchShell
6
+
7
+ class CoreEditPlugin < Plugin
8
+
9
+ def plugin_initialization
10
+ @edittext = nil
11
+ end
12
+
13
+ var "Text saved by the last invocation of edit."
14
+ def lookup_edittext
15
+ @edittext or raise VarNotSet
16
+ end
17
+
18
+ cmd "Edit a document in your editor.",
19
+ synopsis: "[URL]"
20
+ def execute_edit(argstr)
21
+ url = shell.interpolate(argstr)
22
+ res = request! "GET", url, nil, false
23
+ doc = res.json_value.to_s(true)
24
+ new_doc = edittext!(doc)
25
+ if new_doc == doc
26
+ shell.msg "Document hasn't changed. Nothing to submit."
27
+ return
28
+ end
29
+ continue? "Press ENTER to PUT updated document on server " +
30
+ "or CTRL+C to cancel "
31
+ unless shell.request("PUT", url, new_doc).ok?
32
+ shell.msg "recover document text with `print edittext'"
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,54 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require "couch-shell/plugin"
4
+
5
+ module CouchShell
6
+
7
+ class CoreLucenePlugin < Plugin
8
+
9
+ def plugin_initialization
10
+ @ftitext = nil
11
+ end
12
+
13
+ var "Text saved by the last invocation of editfti."
14
+ def lookup_ftitext
15
+ @ftitext or raise VarNotSet
16
+ end
17
+
18
+ cmd "Edit fulltext index function in your editor.",
19
+ synopsis: "DESIGN FULLTEXTINDEX"
20
+ def execute_editfti(argstr)
21
+ dbname = dbname!
22
+ design_name, index_name = argstr.split(/\s+/, 2) if argstr
23
+ if design_name.nil? || index_name.nil?
24
+ raise ShellError, "design and fulltext index name required"
25
+ end
26
+ res = request! "GET", "/#{dbname}/_design/#{design_name}", nil, false
27
+ design = res.json_value
28
+ index = design.fulltext[index_name] if design["fulltext"]
29
+ indexfun = index && index["index"]
30
+ new_indexfun = edittext!(indexfun ||
31
+ "function(doc) {\n var ret = Document.new();\n\n return ret;\n}\n")
32
+ @ftitext = new_indexfun
33
+ if new_indexfun == indexfun
34
+ shell.msg "Index function hasn't changed. Nothing to submit."
35
+ return
36
+ end
37
+ continue? "Press ENTER to submit #{indexfun ? 'updated' : 'new'} " +
38
+ "index function, CTRL+C to cancel "
39
+ if design["fulltext"].nil?
40
+ design.set_attr!("fulltext", {})
41
+ end
42
+ if index.nil?
43
+ design.fulltext.set_attr!(index_name, {})
44
+ index = design.fulltext[index_name]
45
+ end
46
+ index.set_attr!("index", new_indexfun)
47
+ unless shell.request("PUT", "/#{dbname}/_design/#{design_name}", design.to_s).ok?
48
+ shell.msg "recover index text with `print ftitext'"
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,165 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require "couch-shell/plugin"
4
+
5
+ module CouchShell
6
+
7
+ class CoreViewsPlugin < Plugin
8
+
9
+ def plugin_initialization
10
+ @viewtext = nil
11
+ end
12
+
13
+ var "Text saved by the last invocation of editview."
14
+ def lookup_viewtext
15
+ @viewtext or raise VarNotSet
16
+ end
17
+
18
+ cmd "Edit map, and optionally reduce function in your editor.",
19
+ synopsis: "DESIGN VIEW"
20
+ def execute_editview(argstr)
21
+ ensure_at_database
22
+ design_name, view_name = argstr.split(/\s+/, 2)
23
+ if design_name.nil? || view_name.nil?
24
+ raise ShellError, "design and view name required"
25
+ end
26
+ shell.request "GET", "_design/#{design_name}", nil, false
27
+ return unless shell.responses.current(&:ok?)
28
+ design = shell.responses.current.json_value
29
+ view = design.views[view_name] if design["views"]
30
+ mapval = view && view["map"]
31
+ reduceval = view && view["reduce"]
32
+ t = Tempfile.new(["view", ".js"])
33
+ t.puts("map")
34
+ if mapval
35
+ t.puts mapval
36
+ else
37
+ t.puts "function(doc) {\n emit(doc._id, doc);\n}"
38
+ end
39
+ if reduceval || view.nil?
40
+ t.puts
41
+ t.puts("reduce")
42
+ if reduceval
43
+ t.puts reduceval
44
+ else
45
+ t.puts "function(keys, values, rereduce) {\n\n}"
46
+ end
47
+ end
48
+ t.close
49
+ continue?("Press ENTER to edit #{view ? 'existing' : 'new'} view, " +
50
+ "CTRL+C to cancel ")
51
+ unless system(shell.editor_bin!, t.path)
52
+ raise ShellError, "editing command failed with exit status #{$?.exitstatus}"
53
+ end
54
+ text = t.open.read
55
+ @viewtext = text
56
+ t.close
57
+ mapf = nil
58
+ reducef = nil
59
+ inmap = false
60
+ inreduce = false
61
+ i = 0
62
+ text.each_line { |line|
63
+ i += 1
64
+ case line
65
+ when /^map\s*(.*)$/
66
+ unless $1.empty?
67
+ shell.msg "recover view text with `print viewtext'"
68
+ raise ShellError, "invalid map line at line #{i}"
69
+ end
70
+ unless mapf.nil?
71
+ shell.msg "recover view text with `print viewtext'"
72
+ raise ShellError, "duplicate map line at line #{i}"
73
+ end
74
+ inreduce = false
75
+ inmap = true
76
+ mapf = ""
77
+ when /^reduce\s*(.*)$/
78
+ unless $1.empty?
79
+ shell.msg "recover view text with `print viewtext'"
80
+ raise ShellError, "invalid reduce line at line #{i}"
81
+ end
82
+ unless reducef.nil?
83
+ shell.msg "recover view text with `print viewtext'"
84
+ raise ShellError, "duplicate reduce line at line #{i}"
85
+ end
86
+ inmap = false
87
+ inreduce = true
88
+ reducef = ""
89
+ else
90
+ if inmap
91
+ mapf << line
92
+ elsif inreduce
93
+ reducef << line
94
+ elsif line =~ /^\s*$/
95
+ # ignore
96
+ else
97
+ shell.msg "recover view text with `print viewtext'"
98
+ raise ShellError, "unexpected content at line #{i}"
99
+ end
100
+ end
101
+ }
102
+ mapf.strip! if mapf
103
+ reducef.strip! if reducef
104
+ mapf = nil if mapf && mapf.empty?
105
+ reducef = nil if reducef && reducef.empty?
106
+ shell.prompt_msg "View parsed, following actions would be taken:"
107
+ if mapf && mapval.nil?
108
+ shell.prompt_msg " Add map function."
109
+ elsif mapf.nil? && mapval
110
+ shell.prompt_msg " Remove map function."
111
+ elsif mapf && mapval && mapf != mapval
112
+ shell.prompt_msg " Update map function."
113
+ end
114
+ if reducef && reduceval.nil?
115
+ shell.prompt_msg " Add reduce function."
116
+ elsif reducef.nil? && reduceval
117
+ shell.prompt_msg " Remove reduce function."
118
+ elsif reducef && reduceval && reducef != reduceval
119
+ shell.prompt_msg " Update reduce function."
120
+ end
121
+ continue? "Press ENTER to submit, CTRL+C to cancel "
122
+ if !design.respond_to?(:views)
123
+ design.set_attr!("views", {})
124
+ end
125
+ if view.nil?
126
+ design.views.set_attr!(view_name, {})
127
+ view = design.views[view_name]
128
+ end
129
+ if mapf.nil?
130
+ view.delete_attr!("map")
131
+ else
132
+ view.set_attr!("map", mapf)
133
+ end
134
+ if reducef.nil?
135
+ view.delete_attr!("reduce")
136
+ else
137
+ view.set_attr!("reduce", reducef)
138
+ end
139
+ shell.request "PUT", "_design/#{design_name}", design.to_s
140
+ unless shell.responses.current(&:ok?)
141
+ shell.msg "recover view text with `print viewtext'"
142
+ end
143
+ ensure
144
+ if t
145
+ t.close
146
+ t.unlink
147
+ end
148
+ end
149
+
150
+ cmd "Shortcut to GET a view.",
151
+ synopsis: "DESIGN/VIEW[?params]"
152
+ def execute_view(argstr)
153
+ if shell.pathstack.size != 1
154
+ raise ShellError, "current directory must be database"
155
+ end
156
+ design_name, view_name = argstr.split("/", 2)
157
+ if design_name.nil? || view_name.nil?
158
+ raise ShellError, "argument in the form DESIGN/VIEW required"
159
+ end
160
+ shell.request "GET", "_design/#{design_name}/_view/#{view_name}"
161
+ end
162
+
163
+ end
164
+
165
+ end