pywebexec 2.3.8__py3-none-any.whl → 2.3.9__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.
pywebexec/cmdscript.py ADDED
@@ -0,0 +1,150 @@
1
+ import os
2
+ import sys
3
+ import msvcrt
4
+ import shutil
5
+ import time
6
+ import signal
7
+ from datetime import datetime
8
+ from winpty import PTY, WinptyError
9
+
10
+ class CmdInteractive:
11
+ def __init__(self, logfile=None):
12
+ self.logfile = logfile
13
+ cols, rows = shutil.get_terminal_size()
14
+ self.pty = PTY(cols, rows)
15
+ # Set up Ctrl+C handling
16
+ signal.signal(signal.SIGINT, self._handle_sigint)
17
+ self.pid = self.pty.spawn('cmd.exe')
18
+ if not self.pid:
19
+ raise RuntimeError("Failed to spawn cmd.exe")
20
+ self.command_buffer = []
21
+ self.history = []
22
+ self.history_index = 0
23
+ self.cursor_pos = 0 # Track cursor position in command buffer
24
+
25
+ def _handle_sigint(self, signum, frame):
26
+ """Handle Ctrl+C by forwarding it to the child process"""
27
+ try:
28
+ self.pty.write('\x03')
29
+ except:
30
+ pass
31
+
32
+ def log(self, text, direction='>>'):
33
+ if self.logfile:
34
+ timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
35
+ with open(self.logfile, 'a', encoding='utf-8') as f:
36
+ f.write(f'{timestamp} {direction} {text}')
37
+
38
+ def handle_special_key(self, char):
39
+ """Handle special key sequences"""
40
+ if char == b'\xe0': # Extended key
41
+ key = msvcrt.getch()
42
+ if key == b'H': # Up arrow
43
+ if self.history and self.history_index > 0:
44
+ self.history_index -= 1
45
+ cmd = self.history[self.history_index]
46
+ self._replace_line(cmd)
47
+ elif key == b'P': # Down arrow
48
+ if self.history_index < len(self.history) - 1:
49
+ self.history_index += 1
50
+ cmd = self.history[self.history_index]
51
+ self._replace_line(cmd)
52
+ elif key == b'K': # Left arrow
53
+ if self.cursor_pos > 0:
54
+ self.cursor_pos -= 1
55
+ self.pty.write('\x1b[D')
56
+ elif key == b'M': # Right arrow
57
+ if self.cursor_pos < len(self.command_buffer):
58
+ self.cursor_pos += 1
59
+ self.pty.write('\x1b[C')
60
+ return True
61
+ return False
62
+
63
+ def _replace_line(self, new_text):
64
+ """Replace current line with new text"""
65
+ # Clear current line
66
+ self.pty.write('\r' + ' ' * len(self.command_buffer) + '\r')
67
+ # Write new line
68
+ self.command_buffer = list(new_text)
69
+ self.cursor_pos = len(self.command_buffer)
70
+ self.pty.write(''.join(self.command_buffer))
71
+
72
+ def interact(self):
73
+ try:
74
+ while True:
75
+ try:
76
+ try:
77
+ output = self.pty.read()
78
+ if output:
79
+ if isinstance(output, str):
80
+ output = output.encode('utf-8')
81
+ sys.stdout.buffer.write(output)
82
+ sys.stdout.buffer.flush()
83
+ self.log(output.decode('utf-8', errors='replace'), '>>')
84
+ except (EOFError, WinptyError):
85
+ break
86
+
87
+ if msvcrt.kbhit():
88
+ char = msvcrt.getch()
89
+ if self.handle_special_key(char):
90
+ continue
91
+
92
+ if char == b'\r': # Enter
93
+ self.pty.write('\r\n')
94
+ if self.command_buffer:
95
+ cmd = ''.join(self.command_buffer)
96
+ self.history.append(cmd)
97
+ self.history_index = len(self.history)
98
+ self.command_buffer = []
99
+ self.cursor_pos = 0
100
+ elif char == b'\x08': # Backspace
101
+ if self.cursor_pos > 0:
102
+ # Remove character at cursor position
103
+ self.command_buffer.pop(self.cursor_pos - 1)
104
+ self.cursor_pos -= 1
105
+ # Rewrite the line from cursor position
106
+ remain = ''.join(self.command_buffer[self.cursor_pos:])
107
+ self.pty.write('\x08' + remain + ' ')
108
+ # Move cursor back to position
109
+ if remain:
110
+ self.pty.write('\x1b[' + str(len(remain)) + 'D')
111
+ elif char == b'\x03': # Ctrl+C
112
+ self.pty.write('\x03')
113
+ self.command_buffer = []
114
+ self.cursor_pos = 0
115
+ continue
116
+ else:
117
+ # Insert character at cursor position
118
+ if isinstance(char, bytes):
119
+ char = char.decode('cp437', errors='replace')
120
+ self.command_buffer.insert(self.cursor_pos, char)
121
+ self.cursor_pos += 1
122
+ # Write new char and remaining text
123
+ remain = ''.join(self.command_buffer[self.cursor_pos-1:])
124
+ self.pty.write(remain)
125
+ # Move cursor back if needed
126
+ if self.cursor_pos < len(self.command_buffer):
127
+ self.pty.write('\x1b[' + str(len(remain)-1) + 'D')
128
+ self.log(char, '<<')
129
+
130
+ except (IOError, OSError) as e:
131
+ if "handle is closed" in str(e):
132
+ break
133
+ raise
134
+
135
+ except KeyboardInterrupt:
136
+ pass
137
+ finally:
138
+ self.close()
139
+
140
+ def close(self):
141
+ if hasattr(self, 'pty'):
142
+ del self.pty
143
+
144
+ if __name__ == '__main__':
145
+ log_file = 'cmd_session.log'
146
+ try:
147
+ cmd = CmdInteractive(log_file)
148
+ cmd.interact()
149
+ except ImportError:
150
+ print("Please install pywinpty: pip install pywinpty")
@@ -0,0 +1,85 @@
1
+ import os
2
+ import sys
3
+ import msvcrt
4
+ import shutil
5
+ import time
6
+ import signal
7
+ from datetime import datetime
8
+ from winpty import PTY, WinptyError
9
+
10
+ class CmdInteractive:
11
+ def __init__(self, logfile=None):
12
+ self.logfile = logfile
13
+ cols, rows = shutil.get_terminal_size()
14
+ self.pty = PTY(cols, rows)
15
+ # Set up Ctrl+C handling
16
+ signal.signal(signal.SIGINT, self._handle_sigint)
17
+ self.pid = self.pty.spawn('cmd.exe')
18
+ if not self.pid:
19
+ raise RuntimeError("Failed to spawn cmd.exe")
20
+
21
+ def _handle_sigint(self, signum, frame):
22
+ """Handle Ctrl+C by forwarding it to the child process"""
23
+ try:
24
+ self.pty.write('\x03')
25
+ except:
26
+ pass
27
+
28
+ def log(self, text, direction='>>'):
29
+ if self.logfile:
30
+ timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
31
+ with open(self.logfile, 'a', encoding='utf-8') as f:
32
+ f.write(f'{timestamp} {direction} {text}')
33
+
34
+ def interact(self):
35
+ try:
36
+ while True:
37
+ try:
38
+ try:
39
+ output = self.pty.read()
40
+ if output:
41
+ if isinstance(output, str):
42
+ output = output.encode('utf-8')
43
+ sys.stdout.buffer.write(output)
44
+ sys.stdout.buffer.flush()
45
+ self.log(output.decode('utf-8', errors='replace'), '>>')
46
+ except (EOFError, WinptyError):
47
+ break
48
+
49
+ if msvcrt.kbhit():
50
+ char = msvcrt.getch()
51
+ if char == b'\x03': # Ctrl+C
52
+ # Forward Ctrl+C to child process
53
+ self.pty.write('\x03')
54
+ continue
55
+ if char == b'\x1d': # Ctrl+]
56
+ break
57
+ if isinstance(char, bytes):
58
+ char = char.decode('cp437', errors='replace')
59
+ try:
60
+ self.pty.write(char)
61
+ self.log(char, '<<')
62
+ except (EOFError, WinptyError):
63
+ break
64
+
65
+ except (IOError, OSError) as e:
66
+ if "handle is closed" in str(e):
67
+ break
68
+ raise
69
+
70
+ except KeyboardInterrupt:
71
+ pass
72
+ finally:
73
+ self.close()
74
+
75
+ def close(self):
76
+ if hasattr(self, 'pty'):
77
+ del self.pty
78
+
79
+ if __name__ == '__main__':
80
+ log_file = 'cmd_session.log'
81
+ try:
82
+ cmd = CmdInteractive(log_file)
83
+ cmd.interact()
84
+ except ImportError:
85
+ print("Please install pywinpty: pip install pywinpty")
@@ -277,11 +277,22 @@
277
277
  color: #333;
278
278
  padding: 3px 13px;
279
279
  }
280
+ legend {
281
+ font-weight: bold;
282
+ }
283
+ .expandable > legend::before {
284
+ content: '\25B8';
285
+ padding-right: 5px;
286
+ }
287
+ .expanded > legend::before {
288
+ content: '\25BE';
289
+ }
290
+
280
291
  fieldset {
281
292
  border: 1px solid #ccc;
282
293
  border-radius: 5px;
283
294
  padding-top: 9px;
284
- width: 100%;
295
+ /* width: 100%; */
285
296
  label {
286
297
  padding-right: 5px;
287
298
  }
@@ -290,6 +301,11 @@
290
301
  /* display: block ruby; */
291
302
  padding-bottom: 5px;
292
303
  }
304
+ fieldset.expandable > div {
305
+ display: inline-flex;
306
+ gap: 0px 5px;
307
+ padding-bottom: 5px;
308
+ }
293
309
  }
294
310
 
295
311
  .swagger-ui textarea {
@@ -8,7 +8,12 @@ function adjustInputWidth(input) {
8
8
  } else {
9
9
  delta = 3;
10
10
  }
11
- input.style.width = `${input.scrollWidth + delta}px`;
11
+ if (input.scrollWidth > 0) {
12
+ input.style.width = `${input.scrollWidth + delta}px`;
13
+ } else {
14
+ input.style.width = `${input.value.length * 11 + delta}px`;
15
+ }
16
+
12
17
  }
13
18
 
14
19
  function formInputHandle() {
@@ -30,16 +35,19 @@ function formInputHandle() {
30
35
  });
31
36
  }
32
37
 
33
- function extractKeysAndPlaceholders(obj, formoptions, prefix = '') {
38
+ function extractKeysAndPlaceholders(obj, formkeys, formoptions, prefix = '') {
34
39
  let result = [];
35
40
 
36
41
  for (let key in obj.properties) {
37
42
  k = prefix ? `${prefix}.${key}` : key;
43
+ if (formoptions[k]) {
44
+ continue;
45
+ }
38
46
  if (obj.properties[key].type === 'object' && obj.properties[key].properties) {
39
- result = result.concat(extractKeysAndPlaceholders(obj.properties[key], formoptions, k));
47
+ result = result.concat(extractKeysAndPlaceholders(obj.properties[key], formkeys, formoptions, k));
40
48
  } else {
41
- if (formoptions[k]) {
42
- foptions = formoptions[k];
49
+ if (formkeys[k]) {
50
+ foptions = formkeys[k];
43
51
  } else {
44
52
  foptions = {};
45
53
  }
@@ -147,15 +155,25 @@ function createSchemaForm($form, schema, onSubmit, schemaName) {
147
155
  schema_params_options = {};
148
156
  }
149
157
 
158
+ formkeys = {};
150
159
  formoptions = {};
151
- if (schema_options && schema_options.form) {
152
- formoptions = schema.schema_options.form;
153
- } else if (schema_params_options && schema_params_options.form) {
154
- for (let key in schema_params_options.form) {
155
- formoptions[`params.${key}`] = schema_params_options.form[key];
160
+ if (schema_options) {
161
+ formkeys = schema.schema_options.form || {};
162
+ formoptions = schema.schema_options.formoptions || {};
163
+ } else {
164
+ if (schema_params_options) {
165
+ let fkeys = schema_params_options.form || {};
166
+ let foptions = schema_params_options.formoptions || {};
167
+ for (let key in fkeys) {
168
+ formkeys[`params.${key}`] = fkeys[key];
169
+ }
170
+ for (let key in foptions) {
171
+ formoptions[`params.${key}`] = foptions[key];
172
+ }
156
173
  }
157
174
  }
158
- formDesc = extractKeysAndPlaceholders(schema, formoptions);
175
+
176
+ formDesc = extractKeysAndPlaceholders(schema, formkeys, formoptions);
159
177
  if (schemaValues[schemaName]) {
160
178
  value = schemaValues[schemaName];
161
179
  // convert array for textarea formDesc type to string separated by newlines
@@ -233,6 +251,22 @@ function createSchemaForm($form, schema, onSubmit, schemaName) {
233
251
  }
234
252
  }
235
253
  }
254
+ if (formoptions) {
255
+ items = [];
256
+ for (let key in formoptions) {
257
+ items.push({
258
+ key: key,
259
+ ... formoptions[key],
260
+ });
261
+ }
262
+ formDesc.push({
263
+ type: 'fieldset',
264
+ title: 'Options',
265
+ fieldHtmlClass: 'fieldsetoptions',
266
+ expandable: true,
267
+ items: items,
268
+ });
269
+ }
236
270
  formDesc.push({
237
271
  type: 'actions',
238
272
  items: [
@@ -246,7 +280,6 @@ function createSchemaForm($form, schema, onSubmit, schemaName) {
246
280
  title: 'Reset',
247
281
  id: 'reset-form',
248
282
  onClick: function (evt) {
249
- console.log('reset');
250
283
  evt.preventDefault();
251
284
  evt.stopPropagation();
252
285
  evt.stopImmediatePropagation();
@@ -312,6 +345,7 @@ function createSchemaForm($form, schema, onSubmit, schemaName) {
312
345
  adjustInputWidth(e.target);
313
346
  }
314
347
  });
348
+ divopt = schemaForm.querySelector("fieldset.expandable > div");
315
349
  formInputHandle();
316
350
  return jsform;
317
351
  }
pywebexec/version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '2.3.8'
21
- __version_tuple__ = version_tuple = (2, 3, 8)
20
+ __version__ = version = '2.3.9'
21
+ __version_tuple__ = version_tuple = (2, 3, 9)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pywebexec
3
- Version: 2.3.8
3
+ Version: 2.3.9
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Home-page: https://github.com/joknarf/pywebexec
6
6
  Author: Franck Jouvanceau
@@ -1,9 +1,11 @@
1
1
  pywebexec/__init__.py,sha256=197fHJy0UDBwTTpGCGortZRr-w2kTaD7MxqdbVmTEi0,61
2
+ pywebexec/cmdscript.py,sha256=TCYfWTPYn5LEWiMPm0zc9iyHm2AV4uH5E-gOL3hGoDc,6182
3
+ pywebexec/cmdscript.py.ok,sha256=nWB11LXjQahCUwJ8jK0XuU1ekoMyN9Zu_FaOdVTpOwk,2859
2
4
  pywebexec/host_ip.py,sha256=oiCMlo2o3AkkgXDarUSx8T3FWXKI0vk1-EPnx5FGBd8,1332
3
5
  pywebexec/pywebexec.py,sha256=1M5CtxKr5YkEMBhuVSrwnMNcVRuf7iQf-uxF4Lf0ouQ,48375
4
6
  pywebexec/swagger.yaml,sha256=I_oLpp7Hqel8SDEEykvpmCT-Gv3ytGlziq9bvQOrtZY,7598
5
- pywebexec/version.py,sha256=3Oc3thyKkSSxKGqu1ruTNsjrUTMaCkmwZoi_w-3OL9M,511
6
- pywebexec/static/css/form.css,sha256=qzBSq95h3s4qPZEbUD8l2bMBfdWgSWTpk4TEJKZeFxg,6872
7
+ pywebexec/version.py,sha256=xD7sq7tXCC-QKVs2SPi2bsTgQhH6dVLkP6dvZaxMKcA,511
8
+ pywebexec/static/css/form.css,sha256=jScx_gUwW0RyrYZJsn6z6vBsBAAgV-zva63folDPNtA,7160
7
9
  pywebexec/static/css/markdown.css,sha256=br4-iK9wigTs54N2KHtjgZ4KLH0THVSvJo-XZAdMHiE,1970
8
10
  pywebexec/static/css/style.css,sha256=pUmylXwbFIoXrdaJRVOUohlKIhOIilapH97NyIlgGV4,10343
9
11
  pywebexec/static/css/swagger-ui.css,sha256=xhXN8fnUaIACGHuPIEIr9-qmyYr6Zx0k2wv4Qy7Bg1Y,154985
@@ -35,7 +37,7 @@ pywebexec/static/images/success.svg,sha256=NVwezvVMplt46ElW798vqGfrL21Mw_DWHUp_q
35
37
  pywebexec/static/images/swagger-ui.svg,sha256=FR0yeOVwe4zCYKZAjCGcT_m0Mf25NexIVaSXifIkoU0,2117
36
38
  pywebexec/static/js/executables.js,sha256=cTgCFHr_F9bFCirtfG_uR32vOY3vNUr4Ih3Wglj5lFc,11988
37
39
  pywebexec/static/js/popup.js,sha256=IaKmk2U2hEn-Nv6krf_PPW6LaG8NcpCkJKb7lUX0qZo,11457
38
- pywebexec/static/js/schemaform.js,sha256=57FeWNWJT1zW9sG3ujZEOG7vLoiHif5ROQZori2QNMg,11131
40
+ pywebexec/static/js/schemaform.js,sha256=oo4G8XqtL-iDCuq_HULgmYx5YOwY-c9166Wi7C1iAWQ,11919
39
41
  pywebexec/static/js/script.js,sha256=TI3TSylgBxh_a6QvYWlg4CyJ6LMPxnhFl8WRtRDGD0Y,20810
40
42
  pywebexec/static/js/swagger-form.js,sha256=CLcSHMhk5P4-_2MIRBoJLgEnIj_9keDDSzUugXHZjio,4565
41
43
  pywebexec/static/js/js-yaml/LICENSE,sha256=oHvCRGi5ZUznalR9R6LbKC0HcztxXbTHOpi9Y5YflVA,1084
@@ -67,9 +69,9 @@ pywebexec/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
67
69
  pywebexec/templates/index.html,sha256=w18O2plH_yS8bqlPsu5hwFFmCj9H2hWLSV8B6ADcSwU,3900
68
70
  pywebexec/templates/popup.html,sha256=3kpMccKD_OLLhJ4Y9KRw6Ny8wQWjVaRrUfV9y5-bDiQ,1580
69
71
  pywebexec/templates/swagger_ui.html,sha256=MAPr-z96VERAecDvX37V8q2Nxph-O0fNDBul1x2w9SI,1147
70
- pywebexec-2.3.8.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
71
- pywebexec-2.3.8.dist-info/METADATA,sha256=3Gotnq8hyz6QPF1A6XwAdQX1lRCtTRjXVayFLglXZIo,13015
72
- pywebexec-2.3.8.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
73
- pywebexec-2.3.8.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
74
- pywebexec-2.3.8.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
75
- pywebexec-2.3.8.dist-info/RECORD,,
72
+ pywebexec-2.3.9.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
73
+ pywebexec-2.3.9.dist-info/METADATA,sha256=mapKSx2-T9t0PuRG5sCWF0yaGGLzg081Q8FD75-5hDQ,13015
74
+ pywebexec-2.3.9.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
75
+ pywebexec-2.3.9.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
76
+ pywebexec-2.3.9.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
77
+ pywebexec-2.3.9.dist-info/RECORD,,