vortex-cli 3.0.0__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.
Files changed (134) hide show
  1. vortex/__init__.py +0 -0
  2. vortex/__main__.py +6 -0
  3. vortex/colour.py +18 -0
  4. vortex/commands/__init__.py +0 -0
  5. vortex/commands/clean.py +21 -0
  6. vortex/commands/clone.py +219 -0
  7. vortex/commands/code.py +22 -0
  8. vortex/commands/config.py +34 -0
  9. vortex/commands/copy.py +158 -0
  10. vortex/commands/delete.py +84 -0
  11. vortex/commands/find.py +46 -0
  12. vortex/commands/grep.py +68 -0
  13. vortex/commands/list.py +72 -0
  14. vortex/commands/log.py +32 -0
  15. vortex/commands/new.py +126 -0
  16. vortex/commands/watch.py +339 -0
  17. vortex/constants.py +6 -0
  18. vortex/main.py +483 -0
  19. vortex/models.py +529 -0
  20. vortex/puakma/lib/XmlSchema-1.4.2.jar +0 -0
  21. vortex/puakma/lib/activation-1.1.jar +0 -0
  22. vortex/puakma/lib/annogen-0.1.0.jar +0 -0
  23. vortex/puakma/lib/axiom-api-1.2.7.jar +0 -0
  24. vortex/puakma/lib/axiom-dom-1.2.7.jar +0 -0
  25. vortex/puakma/lib/axiom-impl-1.2.7.jar +0 -0
  26. vortex/puakma/lib/axis2-adb-1.4.1.jar +0 -0
  27. vortex/puakma/lib/axis2-adb-codegen-1.4.1.jar +0 -0
  28. vortex/puakma/lib/axis2-ant-plugin-1.4.1.jar +0 -0
  29. vortex/puakma/lib/axis2-clustering-1.4.1.jar +0 -0
  30. vortex/puakma/lib/axis2-codegen-1.4.1.jar +0 -0
  31. vortex/puakma/lib/axis2-corba-1.4.1.jar +0 -0
  32. vortex/puakma/lib/axis2-fastinfoset-1.4.1.jar +0 -0
  33. vortex/puakma/lib/axis2-java2wsdl-1.4.1.jar +0 -0
  34. vortex/puakma/lib/axis2-jaxbri-1.4.1.jar +0 -0
  35. vortex/puakma/lib/axis2-jaxws-1.4.1.jar +0 -0
  36. vortex/puakma/lib/axis2-jaxws-api-1.4.1.jar +0 -0
  37. vortex/puakma/lib/axis2-jibx-1.4.1.jar +0 -0
  38. vortex/puakma/lib/axis2-json-1.4.1.jar +0 -0
  39. vortex/puakma/lib/axis2-jws-api-1.4.1.jar +0 -0
  40. vortex/puakma/lib/axis2-kernel-1.4.1.jar +0 -0
  41. vortex/puakma/lib/axis2-metadata-1.4.1.jar +0 -0
  42. vortex/puakma/lib/axis2-mtompolicy-1.4.1.jar +0 -0
  43. vortex/puakma/lib/axis2-saaj-1.4.1.jar +0 -0
  44. vortex/puakma/lib/axis2-saaj-api-1.4.1.jar +0 -0
  45. vortex/puakma/lib/axis2-spring-1.4.1.jar +0 -0
  46. vortex/puakma/lib/axis2-xmlbeans-1.4.1.jar +0 -0
  47. vortex/puakma/lib/backport-util-concurrent-3.1.jar +0 -0
  48. vortex/puakma/lib/bcprov-jdk14-131.jar +0 -0
  49. vortex/puakma/lib/commons-codec-1.10.jar +0 -0
  50. vortex/puakma/lib/commons-codec-1.3.jar +0 -0
  51. vortex/puakma/lib/commons-collections4-4.1.jar +0 -0
  52. vortex/puakma/lib/commons-fileupload-1.2.jar +0 -0
  53. vortex/puakma/lib/commons-httpclient-3.1.jar +0 -0
  54. vortex/puakma/lib/commons-io-1.4.jar +0 -0
  55. vortex/puakma/lib/commons-logging-1.1.1.jar +0 -0
  56. vortex/puakma/lib/commons-logging-1.2.jar +0 -0
  57. vortex/puakma/lib/curvesapi-1.04.jar +0 -0
  58. vortex/puakma/lib/dom4j-1.6.1.jar +0 -0
  59. vortex/puakma/lib/geronimo-annotation_1.0_spec-1.1.jar +0 -0
  60. vortex/puakma/lib/geronimo-stax-api_1.0_spec-1.0.1.jar +0 -0
  61. vortex/puakma/lib/httpcore-4.0-beta1.jar +0 -0
  62. vortex/puakma/lib/httpcore-nio-4.0-beta1.jar +0 -0
  63. vortex/puakma/lib/iText-rtf-2.1.3.jar +0 -0
  64. vortex/puakma/lib/itext-2.1.7.jar +0 -0
  65. vortex/puakma/lib/itext-pdfa-5.5.1-javadoc.jar +0 -0
  66. vortex/puakma/lib/itext-pdfa-5.5.1-sources.jar +0 -0
  67. vortex/puakma/lib/itext-pdfa-5.5.1.jar +0 -0
  68. vortex/puakma/lib/itext-xtra-5.5.1-javadoc.jar +0 -0
  69. vortex/puakma/lib/itext-xtra-5.5.1-sources.jar +0 -0
  70. vortex/puakma/lib/itext-xtra-5.5.1.jar +0 -0
  71. vortex/puakma/lib/itextpdf-5.5.1-javadoc.jar +0 -0
  72. vortex/puakma/lib/itextpdf-5.5.1-sources.jar +0 -0
  73. vortex/puakma/lib/itextpdf-5.5.1.jar +0 -0
  74. vortex/puakma/lib/jalopy-1.5rc3.jar +0 -0
  75. vortex/puakma/lib/jaxb-api-2.1.jar +0 -0
  76. vortex/puakma/lib/jaxb-impl-2.1.6.jar +0 -0
  77. vortex/puakma/lib/jaxb-xjc-2.1.6.jar +0 -0
  78. vortex/puakma/lib/jaxen-1.1.1.jar +0 -0
  79. vortex/puakma/lib/jcifs-1.2.13.jar +0 -0
  80. vortex/puakma/lib/jcifs-1.3.3.jar +0 -0
  81. vortex/puakma/lib/jcifs-ext-0.9.4.jar +0 -0
  82. vortex/puakma/lib/jcommon-1.0.12.jar +0 -0
  83. vortex/puakma/lib/jettison-1.0-RC2.jar +0 -0
  84. vortex/puakma/lib/jfreechart-1.0.13.jar +0 -0
  85. vortex/puakma/lib/jfreechart-1.0.17.jar +0 -0
  86. vortex/puakma/lib/jibx-bind-1.1.5.jar +0 -0
  87. vortex/puakma/lib/jibx-run-1.1.5.jar +0 -0
  88. vortex/puakma/lib/json-20101028.jar +0 -0
  89. vortex/puakma/lib/json-20110712.jar +0 -0
  90. vortex/puakma/lib/junit-4.12.jar +0 -0
  91. vortex/puakma/lib/log4j-1.2.15.jar +0 -0
  92. vortex/puakma/lib/log4j-1.2.17.jar +0 -0
  93. vortex/puakma/lib/mail-1.4.jar +0 -0
  94. vortex/puakma/lib/mex-1.4.1.jar +0 -0
  95. vortex/puakma/lib/neethi-2.0.4.jar +0 -0
  96. vortex/puakma/lib/poi_3.10.1/poi-3.10.1-20140818.jar +0 -0
  97. vortex/puakma/lib/poi_3.10.1/poi-examples-3.10.1-20140818.jar +0 -0
  98. vortex/puakma/lib/poi_3.10.1/poi-excelant-3.10.1-20140818.jar +0 -0
  99. vortex/puakma/lib/poi_3.10.1/poi-ooxml-3.10.1-20140818.jar +0 -0
  100. vortex/puakma/lib/poi_3.10.1/poi-ooxml-schemas-3.10.1-20140818.jar +0 -0
  101. vortex/puakma/lib/poi_3.10.1/poi-scratchpad-3.10.1-20140818.jar +0 -0
  102. vortex/puakma/lib/poi_3.17/poi-3.17.jar +0 -0
  103. vortex/puakma/lib/poi_3.17/poi-examples-3.17.jar +0 -0
  104. vortex/puakma/lib/poi_3.17/poi-excelant-3.17.jar +0 -0
  105. vortex/puakma/lib/poi_3.17/poi-ooxml-3.17.jar +0 -0
  106. vortex/puakma/lib/poi_3.17/poi-ooxml-schemas-3.17.jar +0 -0
  107. vortex/puakma/lib/poi_3.17/poi-scratchpad-3.17.jar +0 -0
  108. vortex/puakma/lib/shaj-0.5.jar +0 -0
  109. vortex/puakma/lib/soapmonitor-1.4.1.jar +0 -0
  110. vortex/puakma/lib/stax-api-1.0.1.jar +0 -0
  111. vortex/puakma/lib/woden-api-1.0M8.jar +0 -0
  112. vortex/puakma/lib/woden-impl-dom-1.0M8.jar +0 -0
  113. vortex/puakma/lib/wsdl4j-1.6.2.jar +0 -0
  114. vortex/puakma/lib/wstx-asl-3.2.4.jar +0 -0
  115. vortex/puakma/lib/xalan-2.7.0.jar +0 -0
  116. vortex/puakma/lib/xercesImpl-2.8.1.jar +0 -0
  117. vortex/puakma/lib/xml-apis-1.3.04.jar +0 -0
  118. vortex/puakma/lib/xml-resolver-1.2.jar +0 -0
  119. vortex/puakma/lib/xmlbeans-2.3.0.jar +0 -0
  120. vortex/puakma/lib/xmlbeans-2.6.0.jar +0 -0
  121. vortex/puakma/lib-int/puakma.jar +0 -0
  122. vortex/puakma/lib-int/puakma.vortex.wst_1.1.0.jar +0 -0
  123. vortex/puakma/lib-int/retroweaver-rt-1.2.4.jar +0 -0
  124. vortex/puakma/lib-int/velocity-1.5.jar +0 -0
  125. vortex/soap.py +332 -0
  126. vortex/spinner.py +53 -0
  127. vortex/util.py +192 -0
  128. vortex/workspace.py +271 -0
  129. vortex_cli-3.0.0.dist-info/LICENSE +21 -0
  130. vortex_cli-3.0.0.dist-info/METADATA +179 -0
  131. vortex_cli-3.0.0.dist-info/RECORD +134 -0
  132. vortex_cli-3.0.0.dist-info/WHEEL +5 -0
  133. vortex_cli-3.0.0.dist-info/entry_points.txt +2 -0
  134. vortex_cli-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+
5
+ import tabulate
6
+
7
+ from vortex import util
8
+ from vortex.models import PuakmaApplication
9
+ from vortex.models import PuakmaServer
10
+ from vortex.workspace import Workspace
11
+
12
+ logger = logging.getLogger("vortex")
13
+
14
+
15
+ def list_(
16
+ workspace: Workspace,
17
+ server: PuakmaServer,
18
+ *,
19
+ group_filter: list[str],
20
+ name_filter: list[str],
21
+ template_filter: list[str],
22
+ show_ids_only: bool = False,
23
+ show_inherited: bool = False,
24
+ show_inactive: bool = False,
25
+ show_local_only: bool = False,
26
+ open_urls: bool = False,
27
+ open_dev_urls: bool = False,
28
+ ) -> int:
29
+ if show_local_only:
30
+ apps = workspace.listapps()
31
+ else:
32
+ with server as s:
33
+ apps = s.fetch_all_apps(
34
+ name_filter,
35
+ group_filter,
36
+ template_filter,
37
+ show_inherited,
38
+ show_inactive,
39
+ )
40
+ if open_urls or open_dev_urls:
41
+ util.open_app_urls(*apps, open_dev_url=open_dev_urls)
42
+ else:
43
+ if show_ids_only:
44
+ for app in apps:
45
+ print(app.id)
46
+ else:
47
+ _render_app_list(apps, show_inherited=show_inherited)
48
+ return 0
49
+
50
+
51
+ def _render_app_list(
52
+ apps: list[PuakmaApplication],
53
+ *,
54
+ show_inherited: bool,
55
+ ) -> None:
56
+ row_headers = [
57
+ "ID",
58
+ "Name",
59
+ "Group",
60
+ "Template Name",
61
+ ]
62
+ row_data = []
63
+
64
+ if show_inherited:
65
+ row_headers.append("Inherits From")
66
+
67
+ for app in sorted(apps, key=lambda x: (x.group.casefold(), x.name.casefold())):
68
+ row = [app.id, app.name, app.group, app.template_name]
69
+ if show_inherited:
70
+ row.append(app.inherit_from)
71
+ row_data.append(row)
72
+ print(tabulate.tabulate(row_data, headers=row_headers))
vortex/commands/log.py ADDED
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+
5
+ import tabulate
6
+
7
+ from vortex import util
8
+ from vortex.models import PuakmaServer
9
+
10
+
11
+ def log(server: PuakmaServer, limit: int) -> int:
12
+ with server as s:
13
+ logs = s.get_last_log_items(limit)
14
+
15
+ row_headers = ("Time", "Type", "Source", "Message")
16
+ row_data = []
17
+ for log in sorted(logs, key=lambda x: (x.id)):
18
+ row = [
19
+ datetime.strftime(log.date, "%H:%M:%S"),
20
+ log.type,
21
+ util.shorten_text(log.item_source),
22
+ log.msg,
23
+ ]
24
+ row_data.append(row)
25
+
26
+ print(
27
+ tabulate.tabulate(
28
+ row_data, headers=row_headers, maxcolwidths=[None, None, 30, 80]
29
+ )
30
+ )
31
+
32
+ return 0
vortex/commands/new.py ADDED
@@ -0,0 +1,126 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import base64
5
+ import logging
6
+ import mimetypes
7
+
8
+ from vortex.models import DesignObject
9
+ from vortex.models import DesignObjectAmbiguousError
10
+ from vortex.models import DesignObjectNotFound
11
+ from vortex.models import DesignType
12
+ from vortex.models import PuakmaServer
13
+ from vortex.soap import AppDesigner
14
+ from vortex.util import render_objects
15
+ from vortex.workspace import Workspace
16
+
17
+
18
+ logger = logging.getLogger("vortex")
19
+
20
+
21
+ def new(
22
+ workspace: Workspace,
23
+ server: PuakmaServer,
24
+ names: list[str],
25
+ app_id: int,
26
+ design_type: DesignType,
27
+ content_type: str | None = None,
28
+ comment: str = "",
29
+ inherit_from: str = "",
30
+ parent_page: str | None = None,
31
+ open_action: str | None = None,
32
+ save_action: str | None = None,
33
+ ) -> int:
34
+ apps = [app for app in workspace.listapps(server) if app.id == app_id]
35
+ if not apps:
36
+ logger.error(f"No cloned application found with ID {app_id}")
37
+ return 1
38
+
39
+ if design_type.is_java_type:
40
+ content_type = "application/java"
41
+ elif design_type == DesignType.PAGE:
42
+ content_type = "text/html"
43
+ _ext = mimetypes.guess_extension(content_type) if content_type else None
44
+ if _ext is None or content_type is None:
45
+ logger.error(f"Unable to determine file type for '{content_type}'")
46
+ return 1
47
+
48
+ app = apps.pop()
49
+ objs: list[DesignObject] = []
50
+ for name in names:
51
+ try:
52
+ _, _obj = app.lookup_design_obj(name)
53
+ except DesignObjectNotFound:
54
+ # OK
55
+ pass
56
+ except DesignObjectAmbiguousError as e:
57
+ logger.error(e)
58
+ return 1
59
+ else:
60
+ logger.error(f"Design Object {_obj} already exists in {app}")
61
+ return 1
62
+
63
+ design_source = base64.b64encode(design_type.source_template(name)).decode()
64
+ objs.append(
65
+ DesignObject(
66
+ -1,
67
+ name,
68
+ app,
69
+ design_type,
70
+ content_type,
71
+ "",
72
+ design_source,
73
+ comment=comment,
74
+ inherit_from=inherit_from,
75
+ parent_page=parent_page,
76
+ open_action=open_action,
77
+ save_action=save_action,
78
+ )
79
+ )
80
+ if not objs:
81
+ return 1
82
+
83
+ _show_params = True if parent_page or open_action or save_action else False
84
+ print("The following Design Objects will be created:\n")
85
+ render_objects(objs, show_params=_show_params)
86
+ if input("\n[Y/y] to continue:") not in ["Y", "y"]:
87
+ return 1
88
+
89
+ with workspace.exclusive_lock():
90
+ ret = asyncio.run(_acreate_objects(workspace, server, objs))
91
+ workspace.mkdir(app)
92
+ return ret
93
+
94
+
95
+ async def _acreate_objects(
96
+ workspace: Workspace, server: PuakmaServer, objs: list[DesignObject]
97
+ ) -> int:
98
+ async with server as s:
99
+ await s.server_designer.ainitiate_connection()
100
+ logger.info(f"Connected to {s.host}")
101
+ tasks = []
102
+ for obj in objs:
103
+ task = asyncio.create_task(_acreate(workspace, s.app_designer, obj))
104
+ tasks.append(task)
105
+ ret = 0
106
+ for done in asyncio.as_completed(tasks):
107
+ try:
108
+ ret |= await done
109
+ except (asyncio.CancelledError, Exception) as e:
110
+ for task in tasks:
111
+ task.cancel()
112
+ raise e
113
+ return ret
114
+
115
+
116
+ async def _acreate(
117
+ workspace: Workspace,
118
+ app_designer: AppDesigner,
119
+ obj: DesignObject,
120
+ ) -> int:
121
+ ok = await obj.acreate(app_designer)
122
+ if ok:
123
+ await obj.acreate_params(app_designer)
124
+ obj.app.design_objects.append(obj)
125
+ await asyncio.to_thread(obj.save, workspace)
126
+ return 0 if ok else 1
@@ -0,0 +1,339 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ import mimetypes
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Literal
9
+
10
+ from watchfiles import awatch
11
+ from watchfiles import BaseFilter
12
+ from watchfiles import Change
13
+
14
+ from vortex.colour import Colour
15
+ from vortex.models import DesignObject
16
+ from vortex.models import DesignObjectAmbiguousError
17
+ from vortex.models import DesignObjectNotFound
18
+ from vortex.models import DesignPath
19
+ from vortex.models import DesignType
20
+ from vortex.models import InvalidDesignPathError
21
+ from vortex.models import JavaClassVersion
22
+ from vortex.models import PuakmaApplication
23
+ from vortex.models import PuakmaServer
24
+ from vortex.spinner import Spinner
25
+ from vortex.workspace import Workspace
26
+
27
+ logger = logging.getLogger("vortex")
28
+
29
+ _CLASS_EXT = ".class"
30
+
31
+ WorkspaceChange = tuple[Change, DesignPath, PuakmaApplication]
32
+
33
+
34
+ def _error(change: Change, name: str, msg: str) -> Literal[False]:
35
+ msg = f"Failed to process '{name}': {msg}"
36
+ logger.warning(f"({change.raw_str()}) {msg}")
37
+ return False
38
+
39
+
40
+ class _WorkspaceFilter(BaseFilter):
41
+ ignore_files: tuple[str, ...] = (
42
+ ".DS_Store",
43
+ PuakmaApplication.PICKLE_FILE,
44
+ )
45
+
46
+ def __init__(self, workspace: Workspace, server: PuakmaServer) -> None:
47
+ self.workspace = workspace
48
+ self.server = server
49
+
50
+ def __call__(self, change: Change, _path: str) -> bool:
51
+ def _err(msg: str) -> bool:
52
+ return _error(change, path.name, msg)
53
+
54
+ path = Path(_path)
55
+ _is_class_file = path.suffix == _CLASS_EXT
56
+
57
+ do_event = (change in (Change.modified, Change.added) and path.is_file()) or (
58
+ change == Change.deleted and not _is_class_file
59
+ )
60
+
61
+ if not do_event or path.name in self.ignore_files:
62
+ return False
63
+ try:
64
+ design_path = DesignPath(self.workspace, path)
65
+ except InvalidDesignPathError as e:
66
+ return _err(str(e))
67
+ else:
68
+ design_server_host = design_path.app.host
69
+ if design_server_host != self.server.host:
70
+ msg = f"({design_server_host}) does not match ({self.server.host})"
71
+ return _err(msg)
72
+ return True
73
+
74
+
75
+ class WorkspaceWatcher:
76
+ def __init__(
77
+ self, workspace: Workspace, server: PuakmaServer, spinner: Spinner | None = None
78
+ ) -> None:
79
+ self.workspace = workspace
80
+ self.server = server
81
+ self.spinner = spinner
82
+ self.filter = _WorkspaceFilter(workspace, server)
83
+ self._func_dispatch = {
84
+ Change.modified: self._update_design,
85
+ Change.added: self._create_design,
86
+ Change.deleted: self._delete_design,
87
+ }
88
+
89
+ async def watch(self) -> int:
90
+ async with self.server as s:
91
+ msg = await s.server_designer.ainitiate_connection()
92
+ logger.info(msg)
93
+
94
+ _error = False
95
+ changes = None
96
+ dirs = self.workspace.listdir(s, strict=True)
97
+
98
+ while True:
99
+ try:
100
+ async for changes in awatch(*dirs, watch_filter=self.filter):
101
+ try:
102
+ await self._handle_changes(changes)
103
+ except (Exception, asyncio.CancelledError) as e:
104
+ logger.critical(e, stack_info=True)
105
+ _error = True
106
+ break
107
+ except Exception as e:
108
+ logger.error(f"Error getting changes: {e}")
109
+ if changes:
110
+ try:
111
+ await self._handle_changes(changes)
112
+ except (Exception, asyncio.CancelledError) as e:
113
+ logger.critical(e, stack_info=True)
114
+ _error = True
115
+ if _error:
116
+ break
117
+ return 1 if _error else 0
118
+
119
+ async def _handle_changes(self, changes: set[tuple[Change, str]]) -> None:
120
+ apps: dict[int, PuakmaApplication] = {}
121
+ modified: list[WorkspaceChange] = []
122
+ added_or_deleted: list[WorkspaceChange] = []
123
+
124
+ for change, _path in changes:
125
+ path = Path(_path)
126
+ _is_class_file = path.suffix == _CLASS_EXT
127
+ if _is_class_file and change == Change.added:
128
+ change = Change.modified
129
+ design_path = DesignPath(self.workspace, path)
130
+
131
+ app_id = design_path.app.id
132
+ if app_id not in apps.keys():
133
+ apps[app_id] = design_path.app
134
+ app = apps[app_id]
135
+ val = change, design_path, app
136
+
137
+ if change == Change.modified:
138
+ modified.append(val)
139
+ elif change in [Change.added, Change.deleted]:
140
+ added_or_deleted.append(val)
141
+
142
+ if modified:
143
+ await self._dispatch_modified(modified)
144
+ if added_or_deleted:
145
+ await self._dispatch_added_deleted(added_or_deleted)
146
+ # now update the apps since design objects will have changed
147
+ for app in apps.values():
148
+ self.workspace.mkdir(app)
149
+
150
+ async def _dispatch_modified(self, changes: list[WorkspaceChange]) -> None:
151
+ tasks = [asyncio.create_task(self._update_design(*args)) for args in changes]
152
+ for result in asyncio.as_completed(tasks):
153
+ try:
154
+ await result
155
+ except asyncio.CancelledError as e:
156
+ logger.error(f"Operation Cancelled. {e}")
157
+
158
+ async def _dispatch_added_deleted(self, changes: list[WorkspaceChange]) -> None:
159
+ # Do this synchronously since we will be prompting the user for confirmation
160
+ for args in changes:
161
+ change, design_path, _ = args
162
+ task = asyncio.create_task(self._func_dispatch[change](*args))
163
+ try:
164
+ await task
165
+ except asyncio.CancelledError as e:
166
+ _error(change, design_path.fname, f"Operation Cancelled. {e}")
167
+
168
+ async def _update_design(
169
+ self, change: Change, design_path: DesignPath, app: PuakmaApplication
170
+ ) -> bool:
171
+ def _err_result(msg: str) -> Literal[False]:
172
+ return _error(change, design_path.fname, msg)
173
+
174
+ try:
175
+ file_bytes = await asyncio.to_thread(design_path.path.read_bytes)
176
+ except OSError as e:
177
+ return _err_result(e.strerror)
178
+ # If we are uploading a class file, lets verify
179
+ # if it has been compiled correctly
180
+ if design_path.ext == _CLASS_EXT:
181
+ is_valid, msg = _validate_java_class_file(
182
+ file_bytes, app.java_class_version
183
+ )
184
+ if not is_valid:
185
+ return _err_result(msg)
186
+
187
+ try:
188
+ indx, obj = app.lookup_design_obj(design_path.design_name)
189
+ except (DesignObjectNotFound, DesignObjectAmbiguousError) as e:
190
+ return _err_result(str(e))
191
+
192
+ upload_source = obj.design_type.is_java_type and design_path.ext == ".java"
193
+ if upload_source:
194
+ obj.design_source = file_bytes
195
+ else:
196
+ obj.design_data = file_bytes
197
+
198
+ ok = await obj.aupload(self.server.download_designer, upload_source)
199
+ if ok:
200
+ app.design_objects[indx] = obj
201
+ return ok
202
+
203
+ async def _create_design(
204
+ self, change: Change, design_path: DesignPath, app: PuakmaApplication
205
+ ) -> bool:
206
+ def _err_result(msg: str) -> Literal[False]:
207
+ return _error(change, design_path.fname, msg)
208
+
209
+ name = design_path.design_name
210
+
211
+ try:
212
+ _, _obj = app.lookup_design_obj(design_path.design_name)
213
+ except DesignObjectAmbiguousError as e:
214
+ return _err_result(str(e))
215
+ except DesignObjectNotFound:
216
+ # OK
217
+ pass
218
+ else:
219
+ return _err_result(f"Design Object {_obj} already exists in {app}")
220
+
221
+ try:
222
+ design_type = DesignType.from_name(design_path.design_dir)
223
+ except ValueError:
224
+ return _err_result(f"Invalid Design Type '{design_path.design_dir}'")
225
+
226
+ content_type, _ = mimetypes.guess_type(design_path)
227
+ if design_type.is_java_type:
228
+ content_type = "application/java"
229
+ elif design_type == DesignType.PAGE:
230
+ content_type = "text/html"
231
+ if content_type is None:
232
+ return _err_result("Unable to determine content type")
233
+
234
+ obj = DesignObject(-1, name, app, design_type, content_type, "", "")
235
+
236
+ upload_source = obj.design_type.is_java_type
237
+ try:
238
+ file_bytes = design_path.path.read_bytes()
239
+ except OSError as e:
240
+ file_bytes = b""
241
+ msg = f"Couldn't set the design contents from '{design_path}': {e}"
242
+ logger.warning(msg)
243
+ else:
244
+ if upload_source:
245
+ obj.design_source = file_bytes
246
+ else:
247
+ obj.design_data = file_bytes
248
+
249
+ if not self._confirm_change(change, obj):
250
+ return _err_result("Operation Cancelled")
251
+
252
+ create_ok = await obj.acreate(self.server.app_designer)
253
+ if create_ok:
254
+ app.design_objects.append(obj)
255
+
256
+ upload_design_ok = True
257
+ if len(file_bytes) > 0:
258
+ upload_design_ok = await obj.aupload(
259
+ self.server.download_designer, upload_source
260
+ )
261
+
262
+ return upload_design_ok and create_ok
263
+
264
+ async def _delete_design(
265
+ self, change: Change, design_path: DesignPath, app: PuakmaApplication
266
+ ) -> bool:
267
+ def _err_result(msg: str) -> Literal[False]:
268
+ return _error(change, design_path.fname, msg)
269
+
270
+ try:
271
+ _, obj = app.lookup_design_obj(design_path.design_name)
272
+ except (DesignObjectNotFound, DesignObjectAmbiguousError) as e:
273
+ return _err_result(str(e))
274
+
275
+ if not self._confirm_change(change, obj):
276
+ return _err_result("Operation Cancelled")
277
+
278
+ await obj.adelete(self.server.app_designer)
279
+ app.design_objects.remove(obj)
280
+ return True
281
+
282
+ def _confirm_change(self, change: Change, obj: DesignObject) -> bool:
283
+ action = {
284
+ change.deleted: Colour.highlight("delete", Colour.RED),
285
+ change.modified: "update",
286
+ change.added: "create",
287
+ }[change]
288
+
289
+ if self.spinner and self.spinner.running:
290
+ self.spinner.stop()
291
+ try:
292
+ sys.stdout.write("\n")
293
+ prompt = (
294
+ f"Are you sure you wish to {action} {obj.design_type.name} {obj}? "
295
+ "[Y/y] to confirm:"
296
+ )
297
+ sys.stdout.write(prompt)
298
+ sys.stdout.flush()
299
+ res = sys.stdin.readline().strip()
300
+ except (KeyboardInterrupt, Exception) as e:
301
+ raise asyncio.CancelledError(e)
302
+ else:
303
+ return res in ["Y", "y"]
304
+ finally:
305
+ sys.stdout.write("\n")
306
+ sys.stdout.flush()
307
+ if self.spinner and not self.spinner.running:
308
+ self.spinner.start()
309
+
310
+
311
+ def _validate_java_class_file(
312
+ class_file_bytes: bytes, expected_version: JavaClassVersion | None = None
313
+ ) -> tuple[bool, str]:
314
+ # https://en.wikipedia.org/wiki/Java_class_file#General_layout
315
+ bytes_header = class_file_bytes[:8]
316
+ if bytes_header[:4] != b"\xca\xfe\xba\xbe":
317
+ return False, "Not a valid Java Class File"
318
+ major_version = int.from_bytes(bytes_header[6:8], byteorder="big")
319
+ minor_version = int.from_bytes(bytes_header[4:6], byteorder="big")
320
+ compiled_version: JavaClassVersion = (major_version, minor_version)
321
+ if expected_version and compiled_version != expected_version:
322
+ return (
323
+ False,
324
+ f"File has been compiled with Java Class Version"
325
+ f"{compiled_version} but expected {expected_version}",
326
+ )
327
+ return True, ""
328
+
329
+
330
+ def watch(workspace: Workspace, server: PuakmaServer) -> int:
331
+ if not workspace.listdir():
332
+ logger.error(f"No application directories to watch in workspace '{workspace}'")
333
+ return 1
334
+ with (
335
+ workspace.exclusive_lock(),
336
+ Spinner("Watching workspace, ^C to stop") as spinner,
337
+ ):
338
+ watcher = WorkspaceWatcher(workspace, server, spinner)
339
+ return asyncio.run(watcher.watch())
vortex/constants.py ADDED
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib.metadata
4
+
5
+
6
+ VERSION = importlib.metadata.version("vortex_cli")