-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathworkspace.py
268 lines (223 loc) · 8.57 KB
/
workspace.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
from bead.exceptions import InvalidArchive
import os
import sys
from bead import tech
from bead.workspace import Workspace
from bead import layouts
import bead.spec as bead_spec
from .cmdparse import Command
from .common import assert_valid_workspace, die, warning
from .common import DefaultArgSentinel
from .common import OPTIONAL_WORKSPACE, OPTIONAL_ENV
from .common import BEAD_REF_BASE, BEAD_TIME, resolve_bead
from .common import verify_with_feedback
from . import arg_metavar
from . import arg_help
timestamp = tech.timestamp.timestamp
def assert_may_be_valid_name(name):
'''
Refuse bead names that are non cross platform file-system compatible
'''
valid_syntax = (
name
and os.path.sep not in name
and '/' not in name
and '\\' not in name
and ':' not in name
)
if not valid_syntax:
die(f'Invalid name "{name}"')
class CmdNew(Command):
'''
Create and initialize new workspace directory for a new bead.
'''
def declare(self, arg):
arg('workspace', type=Workspace, metavar=arg_metavar.WORKSPACE,
help='bead and directory to create')
def run(self, args):
workspace: Workspace = args.workspace
assert_may_be_valid_name(workspace.name)
if os.path.exists(workspace.directory):
die(f'Directory {workspace.name} already exists.')
kind = tech.identifier.uuid()
workspace.create(kind)
print(f'Created "{workspace.name}"')
def WORKSPACE_defaulting_to(default_workspace):
def opt_workspace(parser):
parser.arg(
'workspace', nargs='?', type=Workspace,
default=default_workspace,
metavar=arg_metavar.WORKSPACE, help=arg_help.WORKSPACE)
return opt_workspace
USE_THE_ONLY_BOX = DefaultArgSentinel(
'if there is exactly one box,' +
' store there, otherwise it MUST be specified')
class CmdSave(Command):
'''
Save workspace in a box.
'''
def declare(self, arg):
arg('box_name', nargs='?', default=USE_THE_ONLY_BOX, type=str,
metavar=arg_metavar.BOX, help=arg_help.BOX)
arg(OPTIONAL_WORKSPACE)
arg(OPTIONAL_ENV)
def run(self, args):
box_name = args.box_name
workspace = args.workspace
env = args.get_env()
assert_valid_workspace(workspace)
# XXX: (usability) save - support saving directly to a directory outside of workspace
if box_name is USE_THE_ONLY_BOX:
boxes = env.get_boxes()
if not boxes:
warning('No boxes have been defined')
beadbox = os.path.expanduser('~/BeadBox')
sys.stderr.write(
f'Creating and using a new one with name `home` and location {beadbox}')
tech.fs.ensure_directory(beadbox)
env.add_box('home', beadbox)
env.save()
# continue with newly created box
boxes = env.get_boxes()
assert len(boxes) == 1
if len(boxes) > 1:
die(
'BOX parameter is not optional!\n' +
'(more than one boxes exists)')
box = boxes[0]
else:
box = env.get_box(box_name)
if box is None:
die(f'Unknown box: {box_name}')
location = box.store(workspace, timestamp())
print(f'Successfully stored bead at {location}.')
DERIVE_FROM_BEAD_NAME = DefaultArgSentinel('derive one from bead name')
class CmdDevelop(Command):
'''
Unpack a bead as a source tree.
Bead directory layout is created, but only the source files are
extracted.
'''
def declare(self, arg):
arg(BEAD_REF_BASE)
arg(BEAD_TIME)
arg(WORKSPACE_defaulting_to(DERIVE_FROM_BEAD_NAME))
arg('-x', '--extract-output', dest='extract_output',
default=False, action='store_true',
help='Extract output data as well (normally it is not needed!).')
arg(OPTIONAL_ENV)
def run(self, args):
extract_output = args.extract_output
env = args.get_env()
try:
bead = resolve_bead(env, args.bead_ref_base, args.bead_time)
except LookupError:
die('Bead not found!')
try:
verify_with_feedback(bead)
except InvalidArchive:
die('Bead is damaged')
if args.workspace is DERIVE_FROM_BEAD_NAME:
workspace = Workspace(bead.name)
else:
workspace = args.workspace
if os.path.exists(workspace.directory):
die(f'Workspace "{workspace.name}" directory already exists'
' - do you have an old checkout?')
bead.unpack_to(workspace)
assert workspace.is_valid
if extract_output:
output_directory = workspace.directory / layouts.Workspace.OUTPUT
bead.unpack_data_to(output_directory)
print(f'Extracted source into {workspace.directory}')
# XXX: try to load smaller inputs?
if workspace.inputs:
print('Input data not loaded, update if needed and load manually')
def print_inputs(env, workspace, verbose):
assert_valid_workspace(workspace)
inputs = sorted(workspace.inputs)
if inputs:
boxes = env.get_boxes()
print('Inputs:')
has_not_loaded = False
is_not_first_input = True
for input in inputs:
if is_not_first_input:
print('')
is_not_loaded = not workspace.is_loaded(input.name)
has_not_loaded = has_not_loaded or is_not_loaded
print(f'input/{input.name}')
print(f'\tStatus: {"**NOT LOADED**" if is_not_loaded else "loaded"}')
input_bead_name = workspace.get_input_bead_name(input.name)
print(f'\tBead: {input_bead_name} # {input.freeze_time_str}')
if verbose:
print(f'\tKind: {input.kind}')
print(f'\tContent id: {input.content_id}')
print('\tBox[es]:')
has_box = False
for box in boxes:
try:
context = box.get_context(
bead_spec.BEAD_NAME, input_bead_name, input.freeze_time)
except LookupError:
# not in this box
continue
bead = context.best
has_box = True
exact_match = bead.content_id == input.content_id
print(f'\t {"*" if exact_match else "?"} -r {box.name} # {bead.freeze_time_str}')
if not has_box:
print('\t - no candidates :(')
print('\t Maybe it has been renamed? or is it in an unreachable box?')
is_not_first_input = True
print('')
if has_not_loaded:
print('Some inputs are currently not loaded.')
print('You can "load" or "update" them manually.')
else:
print('No inputs defined')
class CmdStatus(Command):
'''
Show workspace status - name of bead, inputs and their unpack status.
'''
def declare(self, arg):
arg(OPTIONAL_WORKSPACE)
arg('-v', '--verbose', default=False, action='store_true',
help='show more detailed information')
arg(OPTIONAL_ENV)
def run(self, args):
workspace = args.workspace
verbose = args.verbose
env = args.get_env()
kind_needed = verbose
if workspace.is_valid:
print(f'Bead Name: {workspace.name}')
if kind_needed:
print(f'Bead kind: {workspace.kind}')
print()
print_inputs(env, workspace, verbose)
else:
warning(f'Invalid workspace ({workspace.directory})')
class CmdZap(Command):
'''
Delete the workspace, inluding data, code and documentation.
'''
def declare(self, arg):
arg(WORKSPACE_defaulting_to(Workspace.for_current_working_directory()))
def run(self, args):
workspace = args.workspace
assert_valid_workspace(workspace)
directory = workspace.directory
# on non-posix systems (Windows) it might happen, that we can not remove
# the directory we are in -> ignore errors
tech.fs.rmtree(directory, ignore_errors=os.name != 'posix')
print(f'Deleted workspace {directory}')
class CmdNuke(Command):
'''
No operation, you probably want zap, to delete the workspace.
Nuke was a bad name.
'''
def run(self, args):
print('Nothing happened.')
print()
print('You probably want to use zap, the nuke command is about to disappear.')