couch-shell 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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