a2km 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +35 -0
- data/LICENSE +28 -0
- data/README.md +45 -0
- data/a2km.gemspec +21 -0
- data/bin/a2km +2 -283
- data/lib/a2km.rb +4 -0
- data/lib/a2km/api.rb +59 -0
- data/lib/a2km/command.rb +263 -0
- data/lib/a2km/env.rb +104 -0
- data/lib/a2km/version.rb +5 -0
- metadata +56 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1bd0be9d8cdd24b99dfca16dad1fdaca90ae3eb2
|
4
|
+
data.tar.gz: 3e45923484920d0c0849d9a408d0ba7e644257d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94d3adad6abb635bc980075c362bf875af5b8a78a9ed8f8d9fefa53b37e8eddcff9f1b0ee996a8dc756836a2e8b88d3b4966ac35bd7757e0ffb402e8a72d9a23
|
7
|
+
data.tar.gz: 5194abffa5630161a00d9f938d9ee29319f83bfe668d30cfb0689d3286b2edf6a34d08b7995df704f7277322a78f84125504dcd077f441eafae2adedfa30ba4b
|
data/.gitignore
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/vendor/bundle
|
26
|
+
/lib/bundler/man/
|
27
|
+
|
28
|
+
# for a library or gem, you might want to ignore these files since the code is
|
29
|
+
# intended to run in multiple environments; otherwise, check them in:
|
30
|
+
# Gemfile.lock
|
31
|
+
# .ruby-version
|
32
|
+
# .ruby-gemset
|
33
|
+
|
34
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
35
|
+
.rvmrc
|
data/LICENSE
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Copyright (c) 2015, Min RK
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
* Neither the name of a2km nor the names of its
|
15
|
+
contributors may be used to endorse or promote products derived from
|
16
|
+
this software without specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
|
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Assistant to the Kernel Manager
|
2
|
+
|
3
|
+
Utility for working with [Jupyter](https://jupyter.org) kernelspecs.
|
4
|
+
|
5
|
+
|
6
|
+
## Install
|
7
|
+
|
8
|
+
gem install a2km
|
9
|
+
|
10
|
+
|
11
|
+
## Examples
|
12
|
+
|
13
|
+
a2km clone python3 myenvpy3
|
14
|
+
a2km set python3-copy display_name "Super cool Python Kernel"
|
15
|
+
a2km add-env python3-copy SPARK_HOME=/path/to/spark
|
16
|
+
a2km add-argv python3-copy -- --debug
|
17
|
+
# <debug some stuff>
|
18
|
+
a2km rm-argv python3-copy -- debug
|
19
|
+
|
20
|
+
## Kernelspecs for environments
|
21
|
+
|
22
|
+
a2km has an `env-kernel` subcommand for creating kernelspecs for your conda or virtual environments.
|
23
|
+
Just pass a2km the name of the env, and you should be set:
|
24
|
+
|
25
|
+
conda create -n myenv ipykernel
|
26
|
+
a2km env-kernel myenv
|
27
|
+
|
28
|
+
|
29
|
+
## Commands
|
30
|
+
|
31
|
+
add-argv Add argument(s) to a kernelspec launch command
|
32
|
+
add-env Add environment variables to a kernelspec
|
33
|
+
clone Clone a kernelspec
|
34
|
+
env-kernel Create a kernel from an env (conda or virtualenv)
|
35
|
+
help Display global or [command] help documentation
|
36
|
+
locate Print the path of a kernelspec
|
37
|
+
rename Rename a kernelspec
|
38
|
+
rm Remove a kernelspec
|
39
|
+
rm-argv Remove arguments from a kernelspec launch command
|
40
|
+
rm-env Remove environment variables from a kernelspec
|
41
|
+
set Set a value in the kernelspec
|
42
|
+
show Show info about a kernelspec
|
43
|
+
|
44
|
+
|
45
|
+
![Assistant TO the Kernel Manager](http://i.imgur.com/F0WLaYR.jpg)
|
data/a2km.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/lib/a2km/version'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.require_paths = ["lib"]
|
6
|
+
s.name = 'a2km'
|
7
|
+
s.version = A2KM::VERSION
|
8
|
+
s.date = Date.today.to_s
|
9
|
+
s.summary = 'Assistant to the Kernel Manager'
|
10
|
+
s.description = 'Working with Juptyer kernels'
|
11
|
+
s.authors = ['Min RK']
|
12
|
+
s.email = 'benjaminrk@gmail.com'
|
13
|
+
s.homepage = 'http://github.com/minrk/a2km'
|
14
|
+
s.license = 'BSD'
|
15
|
+
s.executables = ['a2km']
|
16
|
+
s.files = `git ls-files`.split($/)
|
17
|
+
s.require_paths = %w(lib)
|
18
|
+
s.add_runtime_dependency 'commander'
|
19
|
+
s.add_runtime_dependency 'highline'
|
20
|
+
s.add_runtime_dependency 'liquid'
|
21
|
+
end
|
data/bin/a2km
CHANGED
@@ -1,286 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require '
|
3
|
+
require File.expand_path('../lib/a2km', __dir__)
|
4
4
|
|
5
|
-
|
6
|
-
require 'commander'
|
7
|
-
require 'highline'
|
8
|
-
|
9
|
-
|
10
|
-
class AssitantToTheKernelManager
|
11
|
-
include Commander::Methods
|
12
|
-
|
13
|
-
def initialize
|
14
|
-
@kernels = nil
|
15
|
-
end
|
16
|
-
|
17
|
-
def kernels
|
18
|
-
if @kernels.nil?
|
19
|
-
js = JSON.parse `jupyter kernelspec list --json`
|
20
|
-
@kernels = js['kernelspecs']
|
21
|
-
end
|
22
|
-
@kernels
|
23
|
-
end
|
24
|
-
|
25
|
-
def kernel_dirs
|
26
|
-
paths = JSON.parse `jupyter --paths --json`
|
27
|
-
paths['data'].map do |p|
|
28
|
-
p + '/' + 'kernels'
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def user_kernel_dir
|
33
|
-
kernel_dirs.first
|
34
|
-
end
|
35
|
-
|
36
|
-
def get_kernel(name)
|
37
|
-
"Get a kernel by name. Die with message if not found."
|
38
|
-
if not kernels.has_key? name
|
39
|
-
STDERR.puts "No such kernel: #{name}"
|
40
|
-
STDERR.puts "Found kernels: #{kernels.keys.sort.join(' ')}"
|
41
|
-
exit(-1)
|
42
|
-
end
|
43
|
-
kernels[name]
|
44
|
-
end
|
45
|
-
|
46
|
-
def kernel_json_path(name)
|
47
|
-
"Return path to a kernel's kernel.json"
|
48
|
-
kernel = get_kernel(name)
|
49
|
-
File.join(kernel['resources_dir'], 'kernel.json')
|
50
|
-
end
|
51
|
-
|
52
|
-
def get_kernel_json(name)
|
53
|
-
"Return kernel.json contents for a given kernelspec"
|
54
|
-
File.open(kernel_json_path(name)) do |f|
|
55
|
-
return JSON.parse f.read
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def save_kernel_json(name, data)
|
60
|
-
"save new kernel JSON"
|
61
|
-
File.open(kernel_json_path(name), 'w') do |f|
|
62
|
-
f.write JSON.pretty_generate(data)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def run
|
67
|
-
program :name, 'Assistant to the KernelManager'
|
68
|
-
program :version, '0.0.1'
|
69
|
-
program :description, 'Work with Jupyter kernelspecs'
|
70
|
-
|
71
|
-
command :rename do |c|
|
72
|
-
c.syntax = 'a2km rename <from> <to>'
|
73
|
-
c.summary = 'Rename a kernelspec'
|
74
|
-
c.description = 'Rename kernelspec FROM to TO'
|
75
|
-
c.action do |args, options|
|
76
|
-
from = args.shift
|
77
|
-
to = args.shift
|
78
|
-
kernel = get_kernel(from)
|
79
|
-
src = kernel['resources_dir']
|
80
|
-
|
81
|
-
dst = File.join File.dirname(src), to
|
82
|
-
if File.exists? dst
|
83
|
-
STDERR.puts "Destination already exists: #{dst}"
|
84
|
-
exit(-1)
|
85
|
-
end
|
86
|
-
|
87
|
-
puts "Moving #{src} → #{dst}"
|
88
|
-
FileUtils.mv src, dst
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
command :show do |c|
|
93
|
-
c.syntax = 'a2km show <spec>'
|
94
|
-
c.description = 'Show info about a kernelspec'
|
95
|
-
c.action do |args, options|
|
96
|
-
name = args.first
|
97
|
-
kernel = get_kernel(name)
|
98
|
-
spec = kernel['spec']
|
99
|
-
puts "Kernel: #{name} (#{spec['display_name']})"
|
100
|
-
puts " path: #{kernel['resources_dir']}"
|
101
|
-
puts " argv: #{spec['argv'].join(' ')}"
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
command :locate do |c|
|
106
|
-
c.syntax = 'a2km locate <spec>'
|
107
|
-
c.description = 'Print the path of a kernelspec'
|
108
|
-
c.action do |args, options|
|
109
|
-
puts get_kernel(args.first)['resources_dir']
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
command :"add-argv" do |c|
|
114
|
-
c.syntax = 'a2km add-argv <spec> <arg1> [arg2] ...'
|
115
|
-
c.description = 'Add argument(s) to a kernelspec launch command'
|
116
|
-
c.action do |args, options|
|
117
|
-
name = args.shift
|
118
|
-
kernelspec = get_kernel_json(name)
|
119
|
-
kernelspec['argv'] += args
|
120
|
-
save_kernel_json(name, kernelspec)
|
121
|
-
puts "New argv: #{kernelspec['argv'].join(' ')}"
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
command :"rm-argv" do |c|
|
126
|
-
c.syntax = 'a2km rm-argv <spec> <arg1> [arg2] ...'
|
127
|
-
c.summary = 'Remove arguments from a kernelspec launch command'
|
128
|
-
c.description = c.summary + ". To remove args starting with '-'," \
|
129
|
-
" use '--'."
|
130
|
-
c.examples = [
|
131
|
-
"a2km rm-argv myspec x",
|
132
|
-
"a2km rm-argv myspec -- --debug",
|
133
|
-
]
|
134
|
-
c.action do |args, options|
|
135
|
-
name = args.shift
|
136
|
-
to_remove = args
|
137
|
-
kernelspec = get_kernel_json(name)
|
138
|
-
argv = kernelspec['argv']
|
139
|
-
argv.reject! { |arg| to_remove.include? arg }
|
140
|
-
save_kernel_json(name, kernelspec)
|
141
|
-
puts "New argv: #{argv.join(' ')}"
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
command :"add-env" do |c|
|
146
|
-
c.syntax = 'a2km add-env <spec> <key=value> [key=value] ...'
|
147
|
-
c.summary = 'Add environment variables to a kernelspec'
|
148
|
-
c.description = 'Add environment variables to a kernelspec.' \
|
149
|
-
' If no value is given, the value from the current env is used.'
|
150
|
-
c.action do |args, options|
|
151
|
-
name = args.shift
|
152
|
-
spec = get_kernel_json(name)
|
153
|
-
if not spec.has_key? 'env'
|
154
|
-
spec['env'] = {}
|
155
|
-
end
|
156
|
-
env = spec['env']
|
157
|
-
args.each do |arg|
|
158
|
-
key_value = arg.split('=', 2)
|
159
|
-
key = key_value.first
|
160
|
-
if key_value.length == 2
|
161
|
-
value = key_value[1]
|
162
|
-
else
|
163
|
-
value = ENV[key]
|
164
|
-
end
|
165
|
-
env[key] = value
|
166
|
-
end
|
167
|
-
save_kernel_json(name, spec)
|
168
|
-
puts "New env: #{env}"
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
command :"rm-env" do |c|
|
173
|
-
c.syntax = 'a2km rm-env <spec> <key> [key] ...'
|
174
|
-
c.description = 'Remove environment variables from a kernelspec'
|
175
|
-
c.action do |args, options|
|
176
|
-
name = args.shift
|
177
|
-
spec = get_kernel_json(name)
|
178
|
-
if not spec.has_key? 'env'
|
179
|
-
spec['env'] = {}
|
180
|
-
end
|
181
|
-
env = spec['env']
|
182
|
-
args.each do |arg|
|
183
|
-
env.delete(arg)
|
184
|
-
end
|
185
|
-
save_kernel_json(name, spec)
|
186
|
-
puts "New env: #{env}"
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
command :rm do |c|
|
191
|
-
c.syntax = 'a2km rm <spec>'
|
192
|
-
c.description = 'Remove a kernelspec'
|
193
|
-
c.option '-f', "Force removal (skip confirmation)"
|
194
|
-
c.action do |args, options|
|
195
|
-
name = args.shift
|
196
|
-
if not kernels.has_key? name
|
197
|
-
STDERR.puts "No such kernel: #{name}"
|
198
|
-
STDERR.puts "Found kernels: #{kernels.keys.sort.join(' ')}"
|
199
|
-
exit(-1)
|
200
|
-
end
|
201
|
-
path = kernels[name]['resources_dir']
|
202
|
-
if options.f or not HighLine.agree("Permanently delete #{path}? (yes/no)")
|
203
|
-
STDERR.puts "Aborting."
|
204
|
-
exit(-1)
|
205
|
-
end
|
206
|
-
puts "Removing #{path}"
|
207
|
-
FileUtils.rm_r path
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
command :set do |c|
|
212
|
-
c.syntax = 'a2km set <name> <key> <value>'
|
213
|
-
c.description = 'Set a value in the kernelspec'
|
214
|
-
c.examples = [
|
215
|
-
'a2km set python3 display_name "My Python 3"'
|
216
|
-
]
|
217
|
-
c.action do |args, options|
|
218
|
-
name = args.shift
|
219
|
-
key = args.shift
|
220
|
-
value = args.shift
|
221
|
-
if value.nil?
|
222
|
-
STDERR.puts c.syntax
|
223
|
-
exit(-1)
|
224
|
-
end
|
225
|
-
puts "Setting #{name}.#{key} = '#{value}'"
|
226
|
-
spec = get_kernel_json(name)
|
227
|
-
spec[key] = value
|
228
|
-
save_kernel_json(name, spec)
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
command :clone do |c|
|
233
|
-
c.syntax = 'a2km clone <from> <to> [display_name]'
|
234
|
-
c.summary = 'Clone a kernelspec'
|
235
|
-
c.description = 'Clones kernelspec FROM to TO'
|
236
|
-
c.option '--user', 'Force clone to be in the user directory' \
|
237
|
-
' (default is to use the same directory as FROM)'
|
238
|
-
c.action do |args, options|
|
239
|
-
options.default :user => false
|
240
|
-
|
241
|
-
if args.length < 2 or args.length > 3
|
242
|
-
STDERR.puts "Must specify FROM and TO"
|
243
|
-
exit(-1)
|
244
|
-
end
|
245
|
-
from_name = args.shift
|
246
|
-
to_name = args.shift
|
247
|
-
if args.length > 0
|
248
|
-
display_name = args.shift
|
249
|
-
else
|
250
|
-
display_name = to_name
|
251
|
-
end
|
252
|
-
|
253
|
-
from = get_kernel(from_name)
|
254
|
-
from = kernels[from_name]
|
255
|
-
src = from['resources_dir']
|
256
|
-
|
257
|
-
if options.user?
|
258
|
-
dst_dir = user_kernel_dir
|
259
|
-
makedirs(dst_dir)
|
260
|
-
else
|
261
|
-
dst_dir = File.dirname src
|
262
|
-
end
|
263
|
-
dst = File.join dst_dir, to_name
|
264
|
-
if File.exists? dst
|
265
|
-
STDERR.puts "Destination already exists: #{dst}"
|
266
|
-
exit(-1)
|
267
|
-
end
|
268
|
-
|
269
|
-
puts "Cloning #{src} → #{dst}"
|
270
|
-
FileUtils.cp_r src, dst
|
271
|
-
kernel_json = File.join(dst, 'kernel.json')
|
272
|
-
kernelspec = File.open(kernel_json) do |f|
|
273
|
-
JSON.parse f.read
|
274
|
-
end
|
275
|
-
kernelspec['display_name'] = display_name
|
276
|
-
File.open(kernel_json, 'w') do |f|
|
277
|
-
f.write JSON.pretty_generate(kernelspec)
|
278
|
-
end
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
run!
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
AssitantToTheKernelManager.new.run
|
5
|
+
A2KM::run
|
data/lib/a2km.rb
ADDED
data/lib/a2km/api.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Copyright Min RK, License: BSD 3-clause
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module A2KM
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@kernels = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def A2KM.kernels
|
12
|
+
if @kernels.nil?
|
13
|
+
js = JSON.parse `jupyter kernelspec list --json`
|
14
|
+
@kernels = js['kernelspecs']
|
15
|
+
end
|
16
|
+
@kernels
|
17
|
+
end
|
18
|
+
|
19
|
+
def A2KM.kernel_dirs
|
20
|
+
paths = JSON.parse `jupyter --paths --json`
|
21
|
+
paths['data'].map do |p|
|
22
|
+
p + '/' + 'kernels'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def A2KM.user_kernel_dir
|
27
|
+
kernel_dirs.first
|
28
|
+
end
|
29
|
+
|
30
|
+
def A2KM.get_kernel(name)
|
31
|
+
"Get a kernel by name. Die with message if not found."
|
32
|
+
if not kernels.has_key? name
|
33
|
+
STDERR.puts "No such kernel: #{name}"
|
34
|
+
STDERR.puts "Found kernels: #{kernels.keys.sort.join(' ')}"
|
35
|
+
exit(-1)
|
36
|
+
end
|
37
|
+
kernels[name]
|
38
|
+
end
|
39
|
+
|
40
|
+
def A2KM.kernel_json_path(name)
|
41
|
+
"Return path to a kernel's kernel.json"
|
42
|
+
kernel = get_kernel(name)
|
43
|
+
File.join(kernel['resources_dir'], 'kernel.json')
|
44
|
+
end
|
45
|
+
|
46
|
+
def A2KM.get_kernel_json(name)
|
47
|
+
"Return kernel.json contents for a given kernelspec"
|
48
|
+
File.open(kernel_json_path(name)) do |f|
|
49
|
+
return JSON.parse f.read
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def A2KM.save_kernel_json(name, data)
|
54
|
+
"save new kernel JSON"
|
55
|
+
File.open(kernel_json_path(name), 'w') do |f|
|
56
|
+
f.write JSON.pretty_generate(data)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/a2km/command.rb
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
# Copyright Min RK, License: BSD 3-clause
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'commander'
|
7
|
+
require 'highline'
|
8
|
+
|
9
|
+
module A2KM
|
10
|
+
|
11
|
+
# Start the a2km entrypoint
|
12
|
+
def A2KM.run
|
13
|
+
CLI.new.run
|
14
|
+
end
|
15
|
+
|
16
|
+
# The A2KM CLI entrypoint
|
17
|
+
class CLI
|
18
|
+
include Commander::Methods
|
19
|
+
|
20
|
+
def run
|
21
|
+
program :name, 'Assistant to the KernelManager'
|
22
|
+
program :version, VERSION
|
23
|
+
program :description, 'Work with Jupyter kernelspecs'
|
24
|
+
|
25
|
+
command :rename do |c|
|
26
|
+
c.syntax = 'a2km rename <from> <to>'
|
27
|
+
c.summary = 'Rename a kernelspec'
|
28
|
+
c.description = 'Rename kernelspec FROM to TO'
|
29
|
+
c.action do |args, options|
|
30
|
+
from = args.shift
|
31
|
+
to = args.shift
|
32
|
+
kernel = A2KM.get_kernel(from)
|
33
|
+
src = kernel['resources_dir']
|
34
|
+
|
35
|
+
dst = File.join File.dirname(src), to
|
36
|
+
if File.exists? dst
|
37
|
+
STDERR.puts "Destination already exists: #{dst}"
|
38
|
+
exit(-1)
|
39
|
+
end
|
40
|
+
|
41
|
+
puts "Moving #{src} → #{dst}"
|
42
|
+
FileUtils.mv src, dst
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
command :show do |c|
|
47
|
+
c.syntax = 'a2km show <spec>'
|
48
|
+
c.description = 'Show info about a kernelspec'
|
49
|
+
c.action do |args, options|
|
50
|
+
name = args.first
|
51
|
+
kernel = A2KM.get_kernel(name)
|
52
|
+
spec = kernel['spec']
|
53
|
+
puts "Kernel: #{name} (#{spec['display_name']})"
|
54
|
+
puts " path: #{kernel['resources_dir']}"
|
55
|
+
puts " argv: #{spec['argv'].join(' ')}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
command :locate do |c|
|
60
|
+
c.syntax = 'a2km locate <spec>'
|
61
|
+
c.description = 'Print the path of a kernelspec'
|
62
|
+
c.action do |args, options|
|
63
|
+
puts A2KM.get_kernel(args.first)['resources_dir']
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
command :"add-argv" do |c|
|
68
|
+
c.syntax = 'a2km add-argv <spec> <arg1> [arg2] ...'
|
69
|
+
c.description = 'Add argument(s) to a kernelspec launch command'
|
70
|
+
c.action do |args, options|
|
71
|
+
name = args.shift
|
72
|
+
kernelspec = A2KM.get_kernel_json(name)
|
73
|
+
kernelspec['argv'] += args
|
74
|
+
A2KM.save_kernel_json(name, kernelspec)
|
75
|
+
puts "New argv: #{kernelspec['argv'].join(' ')}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
command :"rm-argv" do |c|
|
80
|
+
c.syntax = 'a2km rm-argv <spec> <arg1> [arg2] ...'
|
81
|
+
c.summary = 'Remove arguments from a kernelspec launch command'
|
82
|
+
c.description = c.summary + ". To remove args starting with '-'," \
|
83
|
+
" use '--'."
|
84
|
+
c.examples = [
|
85
|
+
"a2km rm-argv myspec x",
|
86
|
+
"a2km rm-argv myspec -- --debug",
|
87
|
+
]
|
88
|
+
c.action do |args, options|
|
89
|
+
name = args.shift
|
90
|
+
to_remove = args
|
91
|
+
kernelspec = A2KM.get_kernel_json(name)
|
92
|
+
argv = kernelspec['argv']
|
93
|
+
argv.reject! { |arg| to_remove.include? arg }
|
94
|
+
A2KM.save_kernel_json(name, kernelspec)
|
95
|
+
puts "New argv: #{argv.join(' ')}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
command :"add-env" do |c|
|
100
|
+
c.syntax = 'a2km add-env <spec> <key=value> [key=value] ...'
|
101
|
+
c.summary = 'Add environment variables to a kernelspec'
|
102
|
+
c.description = 'Add environment variables to a kernelspec.' \
|
103
|
+
' If no value is given, the value from the current env is used.'
|
104
|
+
c.action do |args, options|
|
105
|
+
name = args.shift
|
106
|
+
spec = A2KM.get_kernel_json(name)
|
107
|
+
if not spec.has_key? 'env'
|
108
|
+
spec['env'] = {}
|
109
|
+
end
|
110
|
+
env = spec['env']
|
111
|
+
args.each do |arg|
|
112
|
+
key_value = arg.split('=', 2)
|
113
|
+
key = key_value.first
|
114
|
+
if key_value.length == 2
|
115
|
+
value = key_value[1]
|
116
|
+
else
|
117
|
+
value = ENV[key]
|
118
|
+
end
|
119
|
+
env[key] = value
|
120
|
+
end
|
121
|
+
A2KM.save_kernel_json(name, spec)
|
122
|
+
puts "New env: #{env}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
command :"rm-env" do |c|
|
127
|
+
c.syntax = 'a2km rm-env <spec> <key> [key] ...'
|
128
|
+
c.description = 'Remove environment variables from a kernelspec'
|
129
|
+
c.action do |args, options|
|
130
|
+
name = args.shift
|
131
|
+
spec = A2KM.get_kernel_json(name)
|
132
|
+
if not spec.has_key? 'env'
|
133
|
+
spec['env'] = {}
|
134
|
+
end
|
135
|
+
env = spec['env']
|
136
|
+
args.each do |arg|
|
137
|
+
env.delete(arg)
|
138
|
+
end
|
139
|
+
A2KM.save_kernel_json(name, spec)
|
140
|
+
puts "New env: #{env}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
command :rm do |c|
|
145
|
+
c.syntax = 'a2km rm <spec>'
|
146
|
+
c.description = 'Remove a kernelspec'
|
147
|
+
c.option '-f', "Force removal (skip confirmation)"
|
148
|
+
c.action do |args, options|
|
149
|
+
name = args.shift
|
150
|
+
if not A2KM.kernels.has_key? name
|
151
|
+
STDERR.puts "No such kernel: #{name}"
|
152
|
+
STDERR.puts "Found kernels: #{kernels.keys.sort.join(' ')}"
|
153
|
+
exit(-1)
|
154
|
+
end
|
155
|
+
path = A2KM.kernels[name]['resources_dir']
|
156
|
+
if options.f or not HighLine.agree("Permanently delete #{path}? (yes/no)")
|
157
|
+
STDERR.puts "Aborting."
|
158
|
+
exit(-1)
|
159
|
+
end
|
160
|
+
puts "Removing #{path}"
|
161
|
+
FileUtils.rm_r path
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
command :"env-kernel" do |c|
|
166
|
+
c.syntax = 'a2km env-kernel [--venv|--conda] <name> [-e env-name] [--prefix PREFIX] [--user]'
|
167
|
+
c.description = 'Create a kernel from an env (conda or virtualenv)'
|
168
|
+
|
169
|
+
c.option '--conda', "use conda env (default)"
|
170
|
+
c.option '--venv', "use virtualenv"
|
171
|
+
c.option '-e ENV', "specify the environment name to use (if different from <name>)"
|
172
|
+
c.option '--prefix PREFIX', 'specify the prefix for the executable to be installed'
|
173
|
+
c.action do |args, options|
|
174
|
+
name = args.shift
|
175
|
+
options.default :user => false
|
176
|
+
options.default :e => name
|
177
|
+
options.default :prefix => '~'
|
178
|
+
env = options.e
|
179
|
+
|
180
|
+
kind = 'conda'
|
181
|
+
if options.venv
|
182
|
+
kind = 'venv'
|
183
|
+
end
|
184
|
+
puts "Making kernel '#{name}' for #{kind}:#{env}"
|
185
|
+
spec = A2KM::make_env_kernel(name, env, kind: kind, prefix: options.prefix)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
command :set do |c|
|
190
|
+
c.syntax = 'a2km set <name> <key> <value>'
|
191
|
+
c.description = 'Set a value in the kernelspec'
|
192
|
+
c.examples = [
|
193
|
+
'a2km set python3 display_name "My Python 3"'
|
194
|
+
]
|
195
|
+
c.action do |args, options|
|
196
|
+
name = args.shift
|
197
|
+
key = args.shift
|
198
|
+
value = args.shift
|
199
|
+
if value.nil?
|
200
|
+
STDERR.puts c.syntax
|
201
|
+
exit(-1)
|
202
|
+
end
|
203
|
+
puts "Setting #{name}.#{key} = '#{value}'"
|
204
|
+
spec = A2KM.get_kernel_json(name)
|
205
|
+
spec[key] = value
|
206
|
+
A2KM.save_kernel_json(name, spec)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
command :clone do |c|
|
211
|
+
c.syntax = 'a2km clone <from> <to> [display_name]'
|
212
|
+
c.summary = 'Clone a kernelspec'
|
213
|
+
c.description = 'Clones kernelspec FROM to TO'
|
214
|
+
c.option '--user', 'Force clone to be in the user directory' \
|
215
|
+
' (default is to use the same directory as FROM)'
|
216
|
+
c.action do |args, options|
|
217
|
+
options.default :user => false
|
218
|
+
|
219
|
+
if args.length < 2 or args.length > 3
|
220
|
+
STDERR.puts "Must specify FROM and TO"
|
221
|
+
exit(-1)
|
222
|
+
end
|
223
|
+
from_name = args.shift
|
224
|
+
to_name = args.shift
|
225
|
+
if args.length > 0
|
226
|
+
display_name = args.shift
|
227
|
+
else
|
228
|
+
display_name = to_name
|
229
|
+
end
|
230
|
+
|
231
|
+
from = A2KM.get_kernel(from_name)
|
232
|
+
from = A2KM.kernels[from_name]
|
233
|
+
src = from['resources_dir']
|
234
|
+
|
235
|
+
if options.user?
|
236
|
+
dst_dir = A2KM.user_kernel_dir
|
237
|
+
makedirs(dst_dir)
|
238
|
+
else
|
239
|
+
dst_dir = File.dirname src
|
240
|
+
end
|
241
|
+
dst = File.join dst_dir, to_name
|
242
|
+
if File.exists? dst
|
243
|
+
STDERR.puts "Destination already exists: #{dst}"
|
244
|
+
exit(-1)
|
245
|
+
end
|
246
|
+
|
247
|
+
puts "Cloning #{src} → #{dst}"
|
248
|
+
FileUtils.cp_r src, dst
|
249
|
+
kernel_json = File.join(dst, 'kernel.json')
|
250
|
+
kernelspec = File.open(kernel_json) do |f|
|
251
|
+
JSON.parse f.read
|
252
|
+
end
|
253
|
+
kernelspec['display_name'] = display_name
|
254
|
+
File.open(kernel_json, 'w') do |f|
|
255
|
+
f.write JSON.pretty_generate(kernelspec)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
run!
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
data/lib/a2km/env.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# Copyright Min RK, License: BSD 3-clause
|
2
|
+
|
3
|
+
require 'Open3'
|
4
|
+
require 'liquid'
|
5
|
+
|
6
|
+
class ENVError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
module A2KM
|
10
|
+
|
11
|
+
module ENV_UTILS
|
12
|
+
|
13
|
+
ACTIVATE_TPL_S = "{{activate}} {{name}}"
|
14
|
+
ACTIVATE_TPL = Liquid::Template.parse ACTIVATE_TPL_S
|
15
|
+
ENV_CMD_TPL = Liquid::Template.parse(<<-END
|
16
|
+
#!/usr/bin/env bash
|
17
|
+
set -e
|
18
|
+
#{ACTIVATE_TPL_S}
|
19
|
+
exec python -m ipykernel $@
|
20
|
+
END
|
21
|
+
)
|
22
|
+
|
23
|
+
CONDA_ACTIVATE = 'source activate'
|
24
|
+
VIRTUALENV_ACTIVATE = 'workon'
|
25
|
+
|
26
|
+
module_function
|
27
|
+
|
28
|
+
def activate_cmd(kind)
|
29
|
+
case kind
|
30
|
+
when 'conda'
|
31
|
+
return CONDA_ACTIVATE
|
32
|
+
when 'venv'
|
33
|
+
return VIRTUALENV_ACTIVATE
|
34
|
+
else
|
35
|
+
throw ArgumentError, "kind must be 'conda' or 'venv', not '#{kind}'"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def in_env(env, cmd, kind: 'conda')
|
40
|
+
Open3::popen2('bash') do |stdin, stdout, wait_thr|
|
41
|
+
stdin.puts 'set -e'
|
42
|
+
stdin.puts ACTIVATE_TPL.render('name' => env, 'activate' => activate_cmd(kind))
|
43
|
+
stdin.puts cmd
|
44
|
+
stdin.close
|
45
|
+
status = wait_thr.value
|
46
|
+
if status != 0
|
47
|
+
raise ENVError.new("Failed to run #{cmd} in #{kind} env: #{env}")
|
48
|
+
end
|
49
|
+
return stdout.read
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def kernel_script(name, kind: 'conda')
|
54
|
+
# return kernel script as a string
|
55
|
+
ENV_CMD_TPL.render('name' => name, 'activate' => activate_cmd(kind))
|
56
|
+
end
|
57
|
+
|
58
|
+
def ipykernel_version(env, kind: 'conda')
|
59
|
+
in_env(env, "python -c 'import ipykernel; print(ipykernel.__version__)'", kind: kind)
|
60
|
+
end
|
61
|
+
|
62
|
+
def make_kernel_exe(kernel_name, env_name, kind: 'conda', prefix: '/usr/local')
|
63
|
+
cmd = kernel_script(env_name, kind: kind)
|
64
|
+
bin = File.join(prefix, 'bin')
|
65
|
+
if not File.directory? bin
|
66
|
+
FileUtils.mkdir bin
|
67
|
+
end
|
68
|
+
kernel_exe = File.join(bin, "jupyter-kernel-#{kernel_name}")
|
69
|
+
puts "Making executable '#{kernel_exe}'"
|
70
|
+
|
71
|
+
File.open(kernel_exe, 'w') do |f|
|
72
|
+
f.write(cmd)
|
73
|
+
end
|
74
|
+
File.chmod(0o755, kernel_exe)
|
75
|
+
return kernel_exe
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_prefix(name, kind)
|
79
|
+
in_env(name, 'python -c "import sys; print(sys.prefix)"', kind: kind)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.make_env_kernel(kernel_name, env_name, kind: 'conda', prefix: '/usr/local', user: true)
|
84
|
+
prefix = File.expand_path(prefix)
|
85
|
+
v = ENV_UTILS.ipykernel_version(env_name, kind: kind)
|
86
|
+
|
87
|
+
puts "Found ipykernel-#{v}"
|
88
|
+
|
89
|
+
exe = ENV_UTILS.make_kernel_exe(kernel_name, env_name, kind: kind, prefix: prefix)
|
90
|
+
if user
|
91
|
+
user_arg = '--user'
|
92
|
+
else
|
93
|
+
user_arg = ''
|
94
|
+
end
|
95
|
+
|
96
|
+
ENV_UTILS.in_env(env_name, "python -m ipykernel.kernelspec --name #{kernel_name} #{user_arg}", kind: kind)
|
97
|
+
spec = A2KM.get_kernel_json(kernel_name)
|
98
|
+
spec['argv'] = [exe, '-f', '{connection_file}']
|
99
|
+
spec['display_name'] = "#{spec['display_name']} (#{kind}:#{env_name})"
|
100
|
+
A2KM.save_kernel_json(kernel_name, spec)
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
data/lib/a2km/version.rb
ADDED
metadata
CHANGED
@@ -1,15 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: a2km
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Min RK
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
12
|
-
dependencies:
|
11
|
+
date: 2015-09-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: commander
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: highline
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: liquid
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
13
55
|
description: Working with Juptyer kernels
|
14
56
|
email: benjaminrk@gmail.com
|
15
57
|
executables:
|
@@ -17,7 +59,16 @@ executables:
|
|
17
59
|
extensions: []
|
18
60
|
extra_rdoc_files: []
|
19
61
|
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- LICENSE
|
64
|
+
- README.md
|
65
|
+
- a2km.gemspec
|
20
66
|
- bin/a2km
|
67
|
+
- lib/a2km.rb
|
68
|
+
- lib/a2km/api.rb
|
69
|
+
- lib/a2km/command.rb
|
70
|
+
- lib/a2km/env.rb
|
71
|
+
- lib/a2km/version.rb
|
21
72
|
homepage: http://github.com/minrk/a2km
|
22
73
|
licenses:
|
23
74
|
- BSD
|
@@ -36,11 +87,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
36
87
|
- - ">="
|
37
88
|
- !ruby/object:Gem::Version
|
38
89
|
version: '0'
|
39
|
-
requirements:
|
40
|
-
- commander
|
41
|
-
- highline
|
90
|
+
requirements: []
|
42
91
|
rubyforge_project:
|
43
|
-
rubygems_version: 2.4.5
|
92
|
+
rubygems_version: 2.4.5.1
|
44
93
|
signing_key:
|
45
94
|
specification_version: 4
|
46
95
|
summary: Assistant to the Kernel Manager
|