kash-shell 0.3.10__py3-none-any.whl → 0.3.12__py3-none-any.whl
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.
- kash/actions/core/format_markdown_template.py +2 -5
- kash/actions/core/markdownify.py +2 -4
- kash/actions/core/readability.py +2 -4
- kash/actions/core/render_as_html.py +30 -11
- kash/actions/core/show_webpage.py +6 -11
- kash/actions/core/strip_html.py +4 -8
- kash/actions/core/{webpage_config.py → tabbed_webpage_config.py} +5 -3
- kash/actions/core/{webpage_generate.py → tabbed_webpage_generate.py} +5 -4
- kash/commands/base/basic_file_commands.py +21 -3
- kash/commands/base/files_command.py +29 -10
- kash/commands/extras/parse_uv_lock.py +12 -3
- kash/commands/workspace/selection_commands.py +1 -1
- kash/commands/workspace/workspace_commands.py +2 -3
- kash/config/colors.py +2 -2
- kash/config/env_settings.py +2 -42
- kash/config/logger.py +30 -25
- kash/config/logger_basic.py +6 -6
- kash/config/settings.py +23 -7
- kash/config/setup.py +33 -5
- kash/config/text_styles.py +25 -22
- kash/embeddings/cosine.py +12 -4
- kash/embeddings/embeddings.py +16 -6
- kash/embeddings/text_similarity.py +10 -4
- kash/exec/__init__.py +3 -0
- kash/exec/action_decorators.py +10 -25
- kash/exec/action_exec.py +43 -23
- kash/exec/llm_transforms.py +6 -3
- kash/exec/preconditions.py +10 -12
- kash/exec/resolve_args.py +4 -0
- kash/exec/runtime_settings.py +134 -0
- kash/exec/shell_callable_action.py +5 -3
- kash/file_storage/file_store.py +37 -38
- kash/file_storage/item_file_format.py +6 -3
- kash/file_storage/store_filenames.py +6 -3
- kash/help/function_param_info.py +1 -1
- kash/llm_utils/init_litellm.py +16 -0
- kash/llm_utils/llm_api_keys.py +6 -2
- kash/llm_utils/llm_completion.py +11 -4
- kash/local_server/local_server_routes.py +1 -7
- kash/mcp/mcp_cli.py +3 -2
- kash/mcp/mcp_server_routes.py +11 -12
- kash/media_base/transcription_deepgram.py +15 -2
- kash/model/__init__.py +1 -1
- kash/model/actions_model.py +6 -54
- kash/model/exec_model.py +79 -0
- kash/model/items_model.py +102 -35
- kash/model/operations_model.py +38 -15
- kash/model/paths_model.py +2 -0
- kash/shell/output/shell_output.py +10 -8
- kash/shell/shell_main.py +2 -2
- kash/shell/utils/exception_printing.py +2 -2
- kash/shell/utils/shell_function_wrapper.py +15 -15
- kash/text_handling/doc_normalization.py +16 -8
- kash/text_handling/markdown_render.py +1 -0
- kash/text_handling/markdown_utils.py +105 -2
- kash/utils/common/format_utils.py +2 -8
- kash/utils/common/function_inspect.py +360 -110
- kash/utils/common/inflection.py +22 -0
- kash/utils/common/task_stack.py +4 -15
- kash/utils/errors.py +14 -9
- kash/utils/file_utils/file_ext.py +4 -0
- kash/utils/file_utils/file_formats_model.py +32 -1
- kash/utils/file_utils/file_sort_filter.py +10 -3
- kash/web_gen/__init__.py +0 -4
- kash/web_gen/simple_webpage.py +52 -0
- kash/web_gen/tabbed_webpage.py +23 -16
- kash/web_gen/template_render.py +37 -2
- kash/web_gen/templates/base_styles.css.jinja +84 -59
- kash/web_gen/templates/base_webpage.html.jinja +85 -67
- kash/web_gen/templates/item_view.html.jinja +47 -37
- kash/web_gen/templates/simple_webpage.html.jinja +24 -0
- kash/web_gen/templates/tabbed_webpage.html.jinja +42 -32
- kash/workspaces/__init__.py +12 -3
- kash/workspaces/workspace_dirs.py +58 -0
- kash/workspaces/workspace_importing.py +1 -1
- kash/workspaces/workspaces.py +26 -90
- {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/METADATA +7 -7
- {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/RECORD +81 -76
- kash/shell/utils/argparse_utils.py +0 -20
- kash/utils/lang_utils/inflection.py +0 -18
- {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.10.dist-info → kash_shell-0.3.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
|
|
4
4
|
<head>
|
|
5
|
+
{% block meta %}
|
|
5
6
|
<meta charset="UTF-8" />
|
|
6
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
|
|
8
|
+
{% endblock meta %}
|
|
8
9
|
|
|
10
|
+
{% block title %}<title>{{ title }}</title>{% endblock title %}
|
|
11
|
+
|
|
12
|
+
{% block head_basic %}
|
|
9
13
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
15
|
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
|
|
@@ -26,12 +30,12 @@
|
|
|
26
30
|
|
|
27
31
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet" />
|
|
28
32
|
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js" defer></script>
|
|
33
|
+
{% endblock head_basic %}
|
|
29
34
|
|
|
30
|
-
{%
|
|
31
|
-
{{ extra_head|safe }}
|
|
32
|
-
{% endif %}
|
|
35
|
+
{% block head_extra %}{% endblock head_extra %}
|
|
33
36
|
|
|
34
37
|
<style>
|
|
38
|
+
{% block font_faces %}
|
|
35
39
|
/* https://fontsource.org/fonts/pt-serif/cdn */
|
|
36
40
|
/* pt-serif-latin-400-normal */
|
|
37
41
|
@font-face {
|
|
@@ -88,84 +92,98 @@
|
|
|
88
92
|
src: url(https://cdn.jsdelivr.net/fontsource/fonts/source-sans-3:vf@latest/latin-wght-italic.woff2) format('woff2-variations');
|
|
89
93
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
|
90
94
|
}
|
|
91
|
-
|
|
92
95
|
{# Other decent sans serif options: Work Sans Variable, Nunito Sans Variable #}
|
|
96
|
+
{% endblock font_faces %}
|
|
93
97
|
|
|
98
|
+
{% block base_styles %}
|
|
94
99
|
{% include "base_styles.css.jinja" %}
|
|
100
|
+
{% endblock base_styles %}
|
|
101
|
+
|
|
102
|
+
{% block content_styles %}
|
|
95
103
|
{% include "content_styles.css.jinja" %}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
{{
|
|
99
|
-
{% endif %}
|
|
104
|
+
{% endblock content_styles %}
|
|
105
|
+
|
|
106
|
+
{% block custom_styles %}{% endblock custom_styles %}
|
|
100
107
|
</style>
|
|
101
|
-
|
|
102
108
|
</head>
|
|
103
109
|
|
|
104
110
|
<body>
|
|
105
|
-
{{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// Wrap tables within the main content area for horizontal scrolling.
|
|
123
|
-
const containers = [];
|
|
124
|
-
document.querySelectorAll('.long-text').forEach(el => {
|
|
125
|
-
const pane = el.querySelector('.tab-pane');
|
|
126
|
-
containers.push(pane || el);
|
|
127
|
-
});
|
|
128
|
-
containers.forEach(container => {
|
|
129
|
-
// Grab all tables, then only wrap the ones whose parent is this container.
|
|
130
|
-
container.querySelectorAll('table').forEach(table => {
|
|
131
|
-
if (table.parentElement !== container) {
|
|
132
|
-
return; // Only direct children.
|
|
133
|
-
}
|
|
134
|
-
if (table.parentNode.classList.contains('table-container')) {
|
|
135
|
-
return; // Already wrapped.
|
|
136
|
-
}
|
|
137
|
-
const wrapper = document.createElement('div');
|
|
138
|
-
wrapper.className = 'table-container';
|
|
139
|
-
container.insertBefore(wrapper, table);
|
|
140
|
-
wrapper.appendChild(table);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
111
|
+
{% block body_header %}{% endblock body_header %}
|
|
112
|
+
|
|
113
|
+
{% block main_content %}
|
|
114
|
+
{{ content|safe }}
|
|
115
|
+
{% endblock main_content %}
|
|
116
|
+
|
|
117
|
+
{% block body_footer %}{% endblock body_footer %}
|
|
118
|
+
|
|
119
|
+
{% block scripts %}
|
|
120
|
+
<script>
|
|
121
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
122
|
+
// Send messages to the parent window, in case we are in a viewport where that matters
|
|
123
|
+
// (e.g. an iframe tooltip).
|
|
124
|
+
// Request a resize of the parent viewport. This iframe size message format isn't
|
|
125
|
+
// standardized by ResizeObserver, but is common. It is supported by Kerm.
|
|
126
|
+
const content = document.body;
|
|
127
|
+
console.log("Suggesting resize to parent:", content.offsetWidth, content.offsetHeight);
|
|
143
128
|
|
|
144
|
-
|
|
129
|
+
window.parent.postMessage({
|
|
130
|
+
type: 'resize',
|
|
131
|
+
width: Math.max(content.offsetWidth, 600),
|
|
132
|
+
height: Math.max(content.offsetHeight, 100)
|
|
133
|
+
}, '*');
|
|
145
134
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
135
|
+
// Wrap tables within the main content area for horizontal scrolling.
|
|
136
|
+
const containers = [];
|
|
137
|
+
document.querySelectorAll('.long-text').forEach(el => {
|
|
138
|
+
const pane = el.querySelector('.tab-pane');
|
|
139
|
+
containers.push(pane || el);
|
|
140
|
+
});
|
|
141
|
+
containers.forEach(container => {
|
|
142
|
+
// Find all tables within the container.
|
|
143
|
+
const tables = Array.from(container.querySelectorAll('table'));
|
|
144
|
+
tables.forEach(table => {
|
|
145
|
+
// Skip tables already in table-container divs
|
|
146
|
+
if (table.parentNode.classList.contains('table-container')) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
// Create the wrapper.
|
|
151
|
+
const wrapper = document.createElement('div');
|
|
152
|
+
wrapper.className = 'table-container';
|
|
153
|
+
// Get the parent and insert the wrapper where the table is, then move the table into the wrapper.
|
|
154
|
+
const parent = table.parentNode;
|
|
155
|
+
parent.insertBefore(wrapper, table);
|
|
156
|
+
wrapper.appendChild(table);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
console.error("Error wrapping table:", e);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
153
163
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
console.log("Sending close message to parent");
|
|
164
|
+
// Double-click to expand (e.g. expand tooltip to popover).
|
|
165
|
+
document.addEventListener('dblclick', () => {
|
|
166
|
+
console.log("Sending expand message to parent");
|
|
158
167
|
window.parent.postMessage({
|
|
159
|
-
type: '
|
|
168
|
+
type: 'expand'
|
|
160
169
|
}, '*');
|
|
161
|
-
}
|
|
162
|
-
});
|
|
170
|
+
});
|
|
163
171
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
// Escape to close tooltip or popover.
|
|
173
|
+
document.addEventListener('keydown', (event) => {
|
|
174
|
+
if (event.key === 'Escape') {
|
|
175
|
+
console.log("Sending close message to parent");
|
|
176
|
+
window.parent.postMessage({
|
|
177
|
+
type: 'close'
|
|
178
|
+
}, '*');
|
|
179
|
+
}
|
|
180
|
+
});
|
|
168
181
|
|
|
182
|
+
{% block scripts_extra %}{% endblock scripts_extra %}
|
|
183
|
+
</script>
|
|
184
|
+
{% endblock scripts %}
|
|
185
|
+
|
|
186
|
+
{% block analytics %}{% endblock analytics %}
|
|
169
187
|
</body>
|
|
170
188
|
|
|
171
189
|
</html>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
{% block item_styles %}
|
|
1
2
|
<style>
|
|
2
3
|
.item-view {
|
|
3
4
|
font-family: var(--font-sans);
|
|
@@ -176,9 +177,11 @@ h1.item-title {
|
|
|
176
177
|
gap: 0.25rem;
|
|
177
178
|
}
|
|
178
179
|
</style>
|
|
180
|
+
{% endblock item_styles %}
|
|
179
181
|
|
|
182
|
+
{% block item_view %}
|
|
180
183
|
<div class="item-view">
|
|
181
|
-
|
|
184
|
+
{% block item_path %}
|
|
182
185
|
<div class="item-path">
|
|
183
186
|
<div class="path-group">
|
|
184
187
|
<div class="path-icon">
|
|
@@ -202,48 +205,52 @@ h1.item-title {
|
|
|
202
205
|
{% endif %}
|
|
203
206
|
</div>
|
|
204
207
|
</div>
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
{
|
|
213
|
-
<span class="item-label">Format:</span> <span class="item-format">{{ item.format.value }}</span>
|
|
214
|
-
{% endif %}
|
|
215
|
-
{% if item.state %}
|
|
216
|
-
<span class="item-label">State:</span> <span class="item-state">{{ item.state.value }}</span>
|
|
217
|
-
{% endif %}
|
|
218
|
-
</div>
|
|
219
|
-
|
|
220
|
-
{% if item.title %}
|
|
221
|
-
<h1 class="item-title">{{ item.title }}</h1>
|
|
208
|
+
{% endblock item_path %}
|
|
209
|
+
|
|
210
|
+
{% block item_header %}
|
|
211
|
+
<div class="item-header">
|
|
212
|
+
{% if not brief_header %}
|
|
213
|
+
<div class="item-meta">
|
|
214
|
+
{% if item.type %}
|
|
215
|
+
<span class="item-type">{{ item.type.value }}</span>
|
|
222
216
|
{% endif %}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
<div class="item-url">
|
|
226
|
-
<div class="url-container">
|
|
227
|
-
<a href="{{ item.url }}" target="_blank" rel="noopener noreferrer">{{ item.url }}</a>
|
|
228
|
-
<i data-feather="copy" class="action-icon" onclick="copyDataValue(this.parentElement)" title="Copy URL"></i>
|
|
229
|
-
</div>
|
|
230
|
-
</div>
|
|
217
|
+
{% if item.format %}
|
|
218
|
+
<span class="item-label">Format:</span> <span class="item-format">{{ item.format.value }}</span>
|
|
231
219
|
{% endif %}
|
|
232
|
-
|
|
220
|
+
{% if item.state %}
|
|
221
|
+
<span class="item-label">State:</span> <span class="item-state">{{ item.state.value }}</span>
|
|
222
|
+
{% endif %}
|
|
223
|
+
</div>
|
|
233
224
|
|
|
234
|
-
{% if
|
|
235
|
-
<
|
|
225
|
+
{% if item.title %}
|
|
226
|
+
<h1 class="item-title">{{ item.title }}</h1>
|
|
236
227
|
{% endif %}
|
|
237
228
|
|
|
238
|
-
{% if
|
|
239
|
-
|
|
240
|
-
<div class="
|
|
241
|
-
{{ item.
|
|
229
|
+
{% if item.url %}
|
|
230
|
+
<div class="item-url">
|
|
231
|
+
<div class="url-container">
|
|
232
|
+
<a href="{{ item.url }}" target="_blank" rel="noopener noreferrer">{{ item.url }}</a>
|
|
233
|
+
<i data-feather="copy" class="action-icon" onclick="copyDataValue(this.parentElement)" title="Copy URL"></i>
|
|
242
234
|
</div>
|
|
243
|
-
|
|
235
|
+
</div>
|
|
244
236
|
{% endif %}
|
|
245
|
-
|
|
237
|
+
{% endif %}
|
|
238
|
+
|
|
239
|
+
{% if file_info_html %}
|
|
240
|
+
<div class="item-file-info">{{ file_info_html | safe }}</div>
|
|
241
|
+
{% endif %}
|
|
242
|
+
|
|
243
|
+
{% if not brief_header %}
|
|
244
|
+
{% if item.description %}
|
|
245
|
+
<div class="item-description">
|
|
246
|
+
{{ item.description }}
|
|
247
|
+
</div>
|
|
248
|
+
{% endif %}
|
|
249
|
+
{% endif %}
|
|
250
|
+
</div>
|
|
251
|
+
{% endblock item_header %}
|
|
246
252
|
|
|
253
|
+
{% block item_content %}
|
|
247
254
|
<div class="item-content">
|
|
248
255
|
{% if body_text %}
|
|
249
256
|
<div class="item-body">{{ body_text }}</div>
|
|
@@ -261,9 +268,11 @@ h1.item-title {
|
|
|
261
268
|
{% endif %}
|
|
262
269
|
{% endif %}
|
|
263
270
|
</div>
|
|
264
|
-
|
|
271
|
+
{% endblock item_content %}
|
|
265
272
|
</div>
|
|
273
|
+
{% endblock item_view %}
|
|
266
274
|
|
|
275
|
+
{% block item_scripts %}
|
|
267
276
|
<script>
|
|
268
277
|
function copyText(element, text) {
|
|
269
278
|
navigator.clipboard.writeText(text).then(() => {
|
|
@@ -291,4 +300,5 @@ function copyDataValue(element) {
|
|
|
291
300
|
document.addEventListener('DOMContentLoaded', () => {
|
|
292
301
|
feather.replace();
|
|
293
302
|
});
|
|
294
|
-
</script>
|
|
303
|
+
</script>
|
|
304
|
+
{% endblock item_scripts %}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{% extends "base_webpage.html.jinja" %}
|
|
2
|
+
|
|
3
|
+
<!-- simple_webpage begin main_content block -->
|
|
4
|
+
{% block main_content %}
|
|
5
|
+
<div class="long-text container max-w-3xl mx-auto bg-white py-8 px-6 md:px-16 md:shadow-lg">
|
|
6
|
+
{% block page_title %}
|
|
7
|
+
{% if title and add_title_h1 %}
|
|
8
|
+
<h1 class="text-center text-4xl mt-6 mb-6">{{ title }}</h1>
|
|
9
|
+
{% endif %}
|
|
10
|
+
{% endblock page_title %}
|
|
11
|
+
|
|
12
|
+
<div>
|
|
13
|
+
{% block page_content %}
|
|
14
|
+
{% if thumbnail_url %}
|
|
15
|
+
<img class="thumbnail" src="{{ thumbnail_url }}" alt="{{ title }}" />
|
|
16
|
+
{% endif %}
|
|
17
|
+
<div class="content">
|
|
18
|
+
{{ content_html | safe }}
|
|
19
|
+
</div>
|
|
20
|
+
{% endblock page_content %}
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
{% endblock main_content %}
|
|
24
|
+
<!-- simple_webpage end main_content block -->
|
|
@@ -1,38 +1,47 @@
|
|
|
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
|
-
|
|
1
|
+
{% extends "simple_webpage.html.jinja" %}
|
|
2
|
+
|
|
3
|
+
<!-- tabbed_webpage begin page_content block -->
|
|
4
|
+
{% block page_content %}
|
|
5
|
+
{% block tab_navigation %}
|
|
6
|
+
<!-- tab_navigation block -->
|
|
7
|
+
{% if show_tabs %}
|
|
8
|
+
<nav>
|
|
9
|
+
{% for tab in tabs %}
|
|
10
|
+
<button
|
|
11
|
+
class="tab-button {% if loop.first %}tab-button-active{% else %}tab-button-inactive{% endif %}"
|
|
12
|
+
onclick="showTab('{{ tab.id }}', this)"
|
|
13
|
+
>
|
|
14
|
+
{{ tab.label }}
|
|
15
|
+
</button>
|
|
16
|
+
{% endfor %}
|
|
17
|
+
</nav>
|
|
18
|
+
{% endif %}
|
|
19
|
+
{% endblock tab_navigation %}
|
|
20
|
+
|
|
21
|
+
{% block tab_content_container %}
|
|
22
|
+
<!-- tab_content_container block -->
|
|
23
|
+
<div class="tab-content mt-8">
|
|
24
|
+
{% for tab in tabs %}
|
|
25
|
+
<div
|
|
26
|
+
id="{{ tab.id }}"
|
|
27
|
+
class="tab-pane {% if not loop.first %}hidden{% endif %}"
|
|
28
|
+
>
|
|
29
|
+
{% if tab.thumbnail_url %}
|
|
30
|
+
<img class="thumbnail" src="{{ tab.thumbnail_url }}" alt="{{ tab.label }}" />
|
|
31
|
+
{% endif %}
|
|
32
|
+
{% if show_tabs %} <h2 class="text-2xl">{{ tab.label }}</h2> {% endif %}
|
|
33
|
+
<div class="content">
|
|
34
|
+
{{ tab.content_html | safe }}
|
|
29
35
|
</div>
|
|
30
|
-
{% endfor %}
|
|
31
36
|
</div>
|
|
37
|
+
{% endfor %}
|
|
32
38
|
</div>
|
|
33
|
-
|
|
39
|
+
{% endblock tab_content_container %}
|
|
40
|
+
{% endblock page_content %}
|
|
41
|
+
<!-- tabbed_webpage end page_content block -->
|
|
34
42
|
|
|
35
|
-
|
|
43
|
+
<!-- tabbed_webpage begin scripts_extra block -->
|
|
44
|
+
{% block scripts_extra %}
|
|
36
45
|
function showTab(tabId, element) {
|
|
37
46
|
document.querySelectorAll(".tab-pane").forEach((tab) => {
|
|
38
47
|
tab.classList.add("hidden");
|
|
@@ -45,4 +54,5 @@
|
|
|
45
54
|
element.classList.add("tab-button-active");
|
|
46
55
|
element.classList.remove("tab-button-inactive");
|
|
47
56
|
}
|
|
48
|
-
|
|
57
|
+
{% endblock scripts_extra %}
|
|
58
|
+
<!-- tabbed_webpage end scripts_extra block -->
|
kash/workspaces/__init__.py
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
from kash.workspaces.selections import Selection, SelectionHistory
|
|
2
|
+
from kash.workspaces.workspace_dirs import (
|
|
3
|
+
enclosing_ws_dir,
|
|
4
|
+
global_ws_dir,
|
|
5
|
+
is_global_ws_dir,
|
|
6
|
+
is_ws_dir,
|
|
7
|
+
)
|
|
2
8
|
from kash.workspaces.workspaces import (
|
|
3
9
|
Workspace,
|
|
10
|
+
_switch_ws_settings,
|
|
4
11
|
current_ignore,
|
|
5
12
|
current_ws,
|
|
6
13
|
get_global_ws,
|
|
7
14
|
get_ws,
|
|
8
|
-
global_ws_dir,
|
|
9
15
|
resolve_ws,
|
|
10
|
-
switch_to_ws,
|
|
11
16
|
ws_param_value,
|
|
12
17
|
)
|
|
13
18
|
|
|
14
19
|
__all__ = [
|
|
15
20
|
"Selection",
|
|
16
21
|
"SelectionHistory",
|
|
22
|
+
"enclosing_ws_dir",
|
|
23
|
+
"global_ws_dir",
|
|
24
|
+
"is_global_ws_dir",
|
|
25
|
+
"is_ws_dir",
|
|
17
26
|
"Workspace",
|
|
18
27
|
"current_ignore",
|
|
19
28
|
"current_ws",
|
|
@@ -21,6 +30,6 @@ __all__ = [
|
|
|
21
30
|
"get_ws",
|
|
22
31
|
"global_ws_dir",
|
|
23
32
|
"resolve_ws",
|
|
33
|
+
"_switch_ws_settings",
|
|
24
34
|
"ws_param_value",
|
|
25
|
-
"switch_to_ws",
|
|
26
35
|
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from functools import cache
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from kash.config.logger import get_logger
|
|
8
|
+
from kash.config.settings import global_settings, resolve_and_create_dirs
|
|
9
|
+
from kash.file_storage.metadata_dirs import MetadataDirs
|
|
10
|
+
from kash.utils.errors import InvalidInput
|
|
11
|
+
|
|
12
|
+
log = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@cache
|
|
16
|
+
def global_ws_dir() -> Path:
|
|
17
|
+
kb_path = resolve_and_create_dirs(global_settings().global_ws_dir, is_dir=True)
|
|
18
|
+
log.debug("Global workspace path: %s", kb_path)
|
|
19
|
+
return kb_path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def is_global_ws_dir(path: Path) -> bool:
|
|
23
|
+
return path.resolve() == global_settings().global_ws_dir
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_ws_dir(path: Path) -> bool:
|
|
27
|
+
dirs = MetadataDirs(path, False)
|
|
28
|
+
return dirs.is_initialized()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def enclosing_ws_dir(path: Path | None = None) -> Path | None:
|
|
32
|
+
"""
|
|
33
|
+
Get the workspace directory enclosing the given path, or of the current
|
|
34
|
+
working directory if no path is given.
|
|
35
|
+
"""
|
|
36
|
+
if not path:
|
|
37
|
+
path = Path(".")
|
|
38
|
+
|
|
39
|
+
path = path.absolute()
|
|
40
|
+
while path != Path("/"):
|
|
41
|
+
if is_ws_dir(path):
|
|
42
|
+
return path
|
|
43
|
+
path = path.parent
|
|
44
|
+
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def normalize_workspace_name(ws_name: str) -> str:
|
|
49
|
+
return str(ws_name).strip().rstrip("/")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def check_strict_workspace_name(ws_name: str) -> str:
|
|
53
|
+
ws_name = normalize_workspace_name(ws_name)
|
|
54
|
+
if not re.match(r"^[\w.-]+$", ws_name):
|
|
55
|
+
raise InvalidInput(
|
|
56
|
+
f"Use an alphanumeric name (- and . also allowed) for the workspace name: `{ws_name}`"
|
|
57
|
+
)
|
|
58
|
+
return ws_name
|
|
@@ -45,7 +45,7 @@ def import_and_load(ws: FileStore, locator: Locator | str) -> Item:
|
|
|
45
45
|
# It's already a StorePath.
|
|
46
46
|
item = ws.load(locator)
|
|
47
47
|
else:
|
|
48
|
-
log.
|
|
48
|
+
log.info("Importing locator as local path: %r", locator)
|
|
49
49
|
path = Path(locator)
|
|
50
50
|
if not path.exists():
|
|
51
51
|
raise InvalidInput(f"File not found: {path}")
|