a2km 0.0.1 → 0.0.2
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 +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
|
+

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