harnice 0.3.0__tar.gz
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.
- harnice-0.3.0/LICENSE +19 -0
- harnice-0.3.0/PKG-INFO +32 -0
- harnice-0.3.0/README.md +10 -0
- harnice-0.3.0/pyproject.toml +40 -0
- harnice-0.3.0/setup.cfg +4 -0
- harnice-0.3.0/src/harnice/__init__.py +0 -0
- harnice-0.3.0/src/harnice/__main__.py +4 -0
- harnice-0.3.0/src/harnice/cli.py +234 -0
- harnice-0.3.0/src/harnice/fileio.py +295 -0
- harnice-0.3.0/src/harnice/gui/launcher.py +426 -0
- harnice-0.3.0/src/harnice/lists/channel_map.py +182 -0
- harnice-0.3.0/src/harnice/lists/circuits_list.py +302 -0
- harnice-0.3.0/src/harnice/lists/disconnect_map.py +237 -0
- harnice-0.3.0/src/harnice/lists/formboard_graph.py +63 -0
- harnice-0.3.0/src/harnice/lists/instances_list.py +280 -0
- harnice-0.3.0/src/harnice/lists/library_history.py +40 -0
- harnice-0.3.0/src/harnice/lists/manifest.py +93 -0
- harnice-0.3.0/src/harnice/lists/post_harness_instances_list.py +66 -0
- harnice-0.3.0/src/harnice/lists/rev_history.py +325 -0
- harnice-0.3.0/src/harnice/lists/signals_list.py +135 -0
- harnice-0.3.0/src/harnice/products/__init__.py +1 -0
- harnice-0.3.0/src/harnice/products/cable.py +152 -0
- harnice-0.3.0/src/harnice/products/chtype.py +80 -0
- harnice-0.3.0/src/harnice/products/device.py +844 -0
- harnice-0.3.0/src/harnice/products/disconnect.py +225 -0
- harnice-0.3.0/src/harnice/products/flagnote.py +139 -0
- harnice-0.3.0/src/harnice/products/harness.py +522 -0
- harnice-0.3.0/src/harnice/products/macro.py +10 -0
- harnice-0.3.0/src/harnice/products/part.py +640 -0
- harnice-0.3.0/src/harnice/products/system.py +125 -0
- harnice-0.3.0/src/harnice/products/tblock.py +270 -0
- harnice-0.3.0/src/harnice/state.py +57 -0
- harnice-0.3.0/src/harnice/utils/appearance.py +51 -0
- harnice-0.3.0/src/harnice/utils/circuit_utils.py +326 -0
- harnice-0.3.0/src/harnice/utils/feature_tree_utils.py +183 -0
- harnice-0.3.0/src/harnice/utils/formboard_utils.py +973 -0
- harnice-0.3.0/src/harnice/utils/library_utils.py +333 -0
- harnice-0.3.0/src/harnice/utils/note_utils.py +417 -0
- harnice-0.3.0/src/harnice/utils/svg_utils.py +819 -0
- harnice-0.3.0/src/harnice/utils/system_utils.py +563 -0
- harnice-0.3.0/src/harnice.egg-info/PKG-INFO +32 -0
- harnice-0.3.0/src/harnice.egg-info/SOURCES.txt +44 -0
- harnice-0.3.0/src/harnice.egg-info/dependency_links.txt +1 -0
- harnice-0.3.0/src/harnice.egg-info/entry_points.txt +3 -0
- harnice-0.3.0/src/harnice.egg-info/requires.txt +6 -0
- harnice-0.3.0/src/harnice.egg-info/top_level.txt +1 -0
harnice-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2018 The Python Packaging Authority
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
harnice-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: harnice
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Electrical System CAD
|
|
5
|
+
Author-email: Kenyon Shutt <harnice.io@gmail.com>
|
|
6
|
+
Project-URL: Documentation, https://harnice.io/
|
|
7
|
+
Project-URL: Homepage, https://github.com/harnice/Harnice
|
|
8
|
+
Project-URL: Issues, https://github.com/harnice/Harnice/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: PySide6>=6.6
|
|
16
|
+
Requires-Dist: sexpdata
|
|
17
|
+
Requires-Dist: Pillow
|
|
18
|
+
Requires-Dist: PyYAML
|
|
19
|
+
Requires-Dist: xlwt
|
|
20
|
+
Requires-Dist: webcolors
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
Harnice is a free, open source electrical system CAD tool.
|
|
24
|
+
|
|
25
|
+
It designs harnesses for you... and does so much more... *nicely!*
|
|
26
|
+
|
|
27
|
+
Please visit...
|
|
28
|
+
- [harnice.io](https://harnice.io/) for documentation
|
|
29
|
+
- https://github.com/harnice/harnice/issues to report a bug or feature request
|
|
30
|
+
- harnice.io@gmail.com to send me an email
|
|
31
|
+
|
|
32
|
+
Made by Kenyon Shutt
|
harnice-0.3.0/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Harnice is a free, open source electrical system CAD tool.
|
|
2
|
+
|
|
3
|
+
It designs harnesses for you... and does so much more... *nicely!*
|
|
4
|
+
|
|
5
|
+
Please visit...
|
|
6
|
+
- [harnice.io](https://harnice.io/) for documentation
|
|
7
|
+
- https://github.com/harnice/harnice/issues to report a bug or feature request
|
|
8
|
+
- harnice.io@gmail.com to send me an email
|
|
9
|
+
|
|
10
|
+
Made by Kenyon Shutt
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "harnice"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name="Kenyon Shutt", email="harnice.io@gmail.com" },
|
|
6
|
+
]
|
|
7
|
+
description = "Electrical System CAD"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.8"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
dependencies = [
|
|
17
|
+
"PySide6>=6.6",
|
|
18
|
+
"sexpdata",
|
|
19
|
+
"Pillow",
|
|
20
|
+
"PyYAML",
|
|
21
|
+
"xlwt",
|
|
22
|
+
"webcolors"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Documentation = "https://harnice.io/"
|
|
27
|
+
Homepage = "https://github.com/harnice/Harnice"
|
|
28
|
+
Issues = "https://github.com/harnice/Harnice/issues"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
harnice = "harnice.cli:main"
|
|
32
|
+
|
|
33
|
+
harnice-gui = "harnice.gui.launcher:main"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools]
|
|
36
|
+
package-dir = {"" = "src"}
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
40
|
+
build-backend = "setuptools.build_meta"
|
harnice-0.3.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import shutil
|
|
5
|
+
from harnice.lists import rev_history
|
|
6
|
+
from harnice import state
|
|
7
|
+
from harnice import fileio
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def print_import_status(
|
|
11
|
+
instance_name, item_type, library_status, import_state, called_from_base_directory
|
|
12
|
+
):
|
|
13
|
+
print(
|
|
14
|
+
f"{'':<4}"
|
|
15
|
+
f"{instance_name:<40}"
|
|
16
|
+
f"{item_type:<16}"
|
|
17
|
+
f"{library_status:<16}"
|
|
18
|
+
f"{import_state:<32}"
|
|
19
|
+
f"{called_from_base_directory:<32}"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def print_import_status_headers():
|
|
24
|
+
print_import_status(
|
|
25
|
+
"INSTANCE NAME",
|
|
26
|
+
"ITEM TYPE",
|
|
27
|
+
"LIBRARY STATUS",
|
|
28
|
+
"IMPORT STATE",
|
|
29
|
+
"CALLED FROM BASE DIRECTORY",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main():
|
|
34
|
+
# Ensure cwd exists
|
|
35
|
+
try:
|
|
36
|
+
cwd = os.getcwd()
|
|
37
|
+
except (FileNotFoundError, PermissionError):
|
|
38
|
+
sys.exit(
|
|
39
|
+
"Error: The current working directory is invalid "
|
|
40
|
+
"(it may have been deleted or you lack permission to access it)."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if not os.path.exists(cwd):
|
|
44
|
+
sys.exit(f"Error: The current working directory no longer exists: {cwd}")
|
|
45
|
+
|
|
46
|
+
# -----------------------------
|
|
47
|
+
# Argument parsing
|
|
48
|
+
# -----------------------------
|
|
49
|
+
parser = argparse.ArgumentParser(
|
|
50
|
+
prog="harnice",
|
|
51
|
+
description="Electrical system CAD",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
55
|
+
|
|
56
|
+
group.add_argument(
|
|
57
|
+
"-r", "--render", action="store_true", help="Render the product normally"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
group.add_argument(
|
|
61
|
+
"-l",
|
|
62
|
+
"--lightweight",
|
|
63
|
+
action="store_true",
|
|
64
|
+
help="Render the product quickly without performing all checks",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
group.add_argument(
|
|
68
|
+
"--newrev",
|
|
69
|
+
action="store_true",
|
|
70
|
+
help="Create a new revision in the current working directory",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
group.add_argument(
|
|
74
|
+
"--gui",
|
|
75
|
+
action="store_true",
|
|
76
|
+
help="Launch the Harnice GUI launcher",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
args = parser.parse_args()
|
|
80
|
+
|
|
81
|
+
if args.gui:
|
|
82
|
+
from harnice.gui.launcher import main as gui_main
|
|
83
|
+
|
|
84
|
+
gui_main()
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# -----------------------------
|
|
88
|
+
# Handle new revision creation and exit
|
|
89
|
+
# -----------------------------
|
|
90
|
+
if args.newrev:
|
|
91
|
+
newrev()
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
# -----------------------------
|
|
95
|
+
# Ensure we are inside a revision folder
|
|
96
|
+
# May change cwd if new PN created
|
|
97
|
+
# -----------------------------
|
|
98
|
+
fileio.verify_revision_structure()
|
|
99
|
+
item_type = rev_history.info(field="product")
|
|
100
|
+
|
|
101
|
+
# -----------------------------
|
|
102
|
+
# Load product module
|
|
103
|
+
# -----------------------------
|
|
104
|
+
try:
|
|
105
|
+
product_module = __import__(
|
|
106
|
+
f"harnice.products.{item_type}", fromlist=[item_type]
|
|
107
|
+
)
|
|
108
|
+
except ModuleNotFoundError:
|
|
109
|
+
sys.exit(f"Unknown product: '{item_type}'")
|
|
110
|
+
|
|
111
|
+
# -----------------------------
|
|
112
|
+
# Set the default fileio structure dict to the product's file_structure()
|
|
113
|
+
# -----------------------------
|
|
114
|
+
if hasattr(product_module, "file_structure"):
|
|
115
|
+
structure = product_module.file_structure()
|
|
116
|
+
state.set_file_structure(structure)
|
|
117
|
+
else:
|
|
118
|
+
sys.exit(f"Product '{item_type}' must define file_structure()")
|
|
119
|
+
|
|
120
|
+
# -----------------------------
|
|
121
|
+
# Generate product file structure
|
|
122
|
+
# -----------------------------
|
|
123
|
+
if hasattr(product_module, "generate_structure"):
|
|
124
|
+
product_module.generate_structure()
|
|
125
|
+
else:
|
|
126
|
+
sys.exit(f"Product '{item_type}' must define generate_structure()")
|
|
127
|
+
|
|
128
|
+
# -----------------------------
|
|
129
|
+
# Execute render logic
|
|
130
|
+
# -----------------------------
|
|
131
|
+
if args.lightweight:
|
|
132
|
+
try:
|
|
133
|
+
product_module.render(lightweight=True)
|
|
134
|
+
except TypeError:
|
|
135
|
+
sys.exit(f"Product '{item_type}' does not support lightweight rendering")
|
|
136
|
+
else:
|
|
137
|
+
product_module.render()
|
|
138
|
+
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def prompt(text, default=None):
|
|
143
|
+
p = f"{text}"
|
|
144
|
+
if default:
|
|
145
|
+
p += f" [{default}]"
|
|
146
|
+
p += ": "
|
|
147
|
+
return input(p).strip() or default
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def newrev():
|
|
151
|
+
from harnice import fileio
|
|
152
|
+
|
|
153
|
+
"""
|
|
154
|
+
Create a new revision directory by copying the current revision's contents
|
|
155
|
+
and updating filenames to reflect the new revision number.
|
|
156
|
+
"""
|
|
157
|
+
# Ensure revision structure is valid and get context
|
|
158
|
+
fileio.verify_revision_structure()
|
|
159
|
+
|
|
160
|
+
# Prompt user for new revision number
|
|
161
|
+
new_rev_number = prompt(
|
|
162
|
+
f"Current rev number: {state.partnumber('R')}. Enter new rev number:",
|
|
163
|
+
default=str(int(state.partnumber("R")) + 1),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Construct new revision directory path
|
|
167
|
+
new_rev_dir = os.path.join(
|
|
168
|
+
fileio.part_directory(), f"{state.partnumber('pn')}-rev{new_rev_number}"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Ensure target directory does not already exist
|
|
172
|
+
if os.path.exists(new_rev_dir):
|
|
173
|
+
raise FileExistsError(f"Revision directory already exists: {new_rev_dir}")
|
|
174
|
+
|
|
175
|
+
shutil.copytree(fileio.rev_directory(), new_rev_dir)
|
|
176
|
+
|
|
177
|
+
# Walk the new directory and rename all files containing the old rev number
|
|
178
|
+
for root, _, files in os.walk(new_rev_dir):
|
|
179
|
+
for filename in files:
|
|
180
|
+
new_suffix = f"rev{new_rev_number}"
|
|
181
|
+
|
|
182
|
+
if state.partnumber("rev") in filename:
|
|
183
|
+
old_path = os.path.join(root, filename)
|
|
184
|
+
new_name = filename.replace(state.partnumber("rev"), new_suffix)
|
|
185
|
+
new_path = os.path.join(root, new_name)
|
|
186
|
+
|
|
187
|
+
os.rename(old_path, new_path)
|
|
188
|
+
|
|
189
|
+
print(
|
|
190
|
+
f"Successfully created new revision: {state.partnumber('pn')}-rev{new_rev_number}. Please cd into it."
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def select_product_type():
|
|
195
|
+
from pathlib import Path
|
|
196
|
+
import harnice.products as products_pkg
|
|
197
|
+
from prompt_toolkit import prompt
|
|
198
|
+
from prompt_toolkit.completion import WordCompleter
|
|
199
|
+
|
|
200
|
+
def get_product_types():
|
|
201
|
+
products_dir = Path(products_pkg.__file__).parent
|
|
202
|
+
return sorted(
|
|
203
|
+
p.stem
|
|
204
|
+
for p in products_dir.glob("*.py")
|
|
205
|
+
if p.name != "__init__.py"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
product_types = get_product_types()
|
|
209
|
+
product_map = {p.lower(): p for p in product_types}
|
|
210
|
+
|
|
211
|
+
completer = WordCompleter(
|
|
212
|
+
product_types,
|
|
213
|
+
ignore_case=True,
|
|
214
|
+
sentence=True,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
while True:
|
|
218
|
+
value = prompt(
|
|
219
|
+
"What product type are you working on? ",
|
|
220
|
+
completer=completer,
|
|
221
|
+
default="harness",
|
|
222
|
+
).strip()
|
|
223
|
+
|
|
224
|
+
if not value:
|
|
225
|
+
value = "harness"
|
|
226
|
+
|
|
227
|
+
key = value.lower()
|
|
228
|
+
if key in product_map:
|
|
229
|
+
return product_map[key]
|
|
230
|
+
|
|
231
|
+
print(
|
|
232
|
+
f"Unrecognized product type '{value}'. "
|
|
233
|
+
f"Valid options: {', '.join(product_types)}"
|
|
234
|
+
)
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import os.path
|
|
3
|
+
import datetime
|
|
4
|
+
import shutil
|
|
5
|
+
import re
|
|
6
|
+
import csv
|
|
7
|
+
import json
|
|
8
|
+
import subprocess
|
|
9
|
+
from harnice import state
|
|
10
|
+
|
|
11
|
+
# standard punctuation:
|
|
12
|
+
# . separates between name hierarchy levels
|
|
13
|
+
# _ means nothing, basically a space character
|
|
14
|
+
# - if multiple instances are found at the same hierarchy level with the same name,
|
|
15
|
+
# this separates name from unique instance identifier
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def part_directory():
|
|
19
|
+
return os.path.dirname(os.getcwd())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def rev_directory():
|
|
23
|
+
return os.getcwd()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def silentremove(filepath):
|
|
27
|
+
"""
|
|
28
|
+
Removes a file or directory and its contents.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
filepath (str): The path to the file or directory to remove.
|
|
32
|
+
"""
|
|
33
|
+
if os.path.exists(filepath):
|
|
34
|
+
if os.path.isfile(filepath) or os.path.islink(filepath):
|
|
35
|
+
os.remove(filepath) # remove file or symlink
|
|
36
|
+
elif os.path.isdir(filepath):
|
|
37
|
+
shutil.rmtree(filepath) # remove directory and contents
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def path(target_value, structure_dict=None, base_directory=None):
|
|
41
|
+
|
|
42
|
+
# returns the filepath/filename of a filekey.
|
|
43
|
+
"""
|
|
44
|
+
Recursively searches for a value in a nested JSON structure and returns the path to the element containing that value.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
target_value (str): The value to search for.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
list: A list of container names leading to the element containing the target value, or None if not found.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
# FILES NOT DEPENDENT ON PRODUCT TYPE
|
|
54
|
+
if target_value == "revision history":
|
|
55
|
+
file_path = os.path.join(
|
|
56
|
+
part_directory(), f"{state.partnumber('pn')}-revision_history.tsv"
|
|
57
|
+
)
|
|
58
|
+
return file_path
|
|
59
|
+
|
|
60
|
+
# FILES DEPENDENT ON HARNICE ROOT
|
|
61
|
+
|
|
62
|
+
if target_value == "library locations":
|
|
63
|
+
import harnice
|
|
64
|
+
|
|
65
|
+
harnice_root = os.path.dirname(
|
|
66
|
+
os.path.dirname(os.path.dirname(harnice.__file__))
|
|
67
|
+
)
|
|
68
|
+
return os.path.join(harnice_root, "library_locations.csv")
|
|
69
|
+
|
|
70
|
+
if target_value == "project locations":
|
|
71
|
+
import harnice
|
|
72
|
+
|
|
73
|
+
harnice_root = os.path.dirname(
|
|
74
|
+
os.path.dirname(os.path.dirname(harnice.__file__))
|
|
75
|
+
)
|
|
76
|
+
return os.path.join(harnice_root, "project_locations.csv")
|
|
77
|
+
|
|
78
|
+
if target_value == "drawnby":
|
|
79
|
+
import harnice
|
|
80
|
+
|
|
81
|
+
harnice_root = os.path.dirname(
|
|
82
|
+
os.path.dirname(os.path.dirname(harnice.__file__))
|
|
83
|
+
)
|
|
84
|
+
return os.path.join(harnice_root, "drawnby.json")
|
|
85
|
+
|
|
86
|
+
# FILES INSIDE OF A STRUCURE DEFINED BY FILEIO
|
|
87
|
+
# look up from default structure state if not provided
|
|
88
|
+
if structure_dict is None:
|
|
89
|
+
structure_dict = state.file_structure
|
|
90
|
+
|
|
91
|
+
def recursive_search(data, path):
|
|
92
|
+
if isinstance(data, dict):
|
|
93
|
+
for key, value in data.items():
|
|
94
|
+
if value == target_value:
|
|
95
|
+
return path + [key]
|
|
96
|
+
result = recursive_search(value, path + [key])
|
|
97
|
+
if result:
|
|
98
|
+
return result
|
|
99
|
+
elif isinstance(data, list):
|
|
100
|
+
for index, item in enumerate(data):
|
|
101
|
+
if item == target_value:
|
|
102
|
+
return path + [f"[{index}]"]
|
|
103
|
+
result = recursive_search(item, path + [f"[{index}]"])
|
|
104
|
+
if result:
|
|
105
|
+
return result
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
path_value = recursive_search(structure_dict, [])
|
|
109
|
+
|
|
110
|
+
if not path_value:
|
|
111
|
+
raise TypeError(f"Could not find filepath of '{target_value}'.")
|
|
112
|
+
if base_directory in [None, ""]:
|
|
113
|
+
return os.path.join(rev_directory(), *path_value)
|
|
114
|
+
else:
|
|
115
|
+
return os.path.join(rev_directory(), base_directory, *path_value)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def dirpath(target_key, structure_dict=None, base_directory=None):
|
|
119
|
+
"""
|
|
120
|
+
Returns the absolute path to a directory identified by its key
|
|
121
|
+
within a dict hierarchy.
|
|
122
|
+
"""
|
|
123
|
+
if target_key is None:
|
|
124
|
+
if base_directory in [None, ""]:
|
|
125
|
+
return os.path.join(rev_directory())
|
|
126
|
+
else:
|
|
127
|
+
return os.path.join(rev_directory(), base_directory)
|
|
128
|
+
|
|
129
|
+
if structure_dict is None:
|
|
130
|
+
structure_dict = state.file_structure
|
|
131
|
+
|
|
132
|
+
def recursive_search(data, path):
|
|
133
|
+
if isinstance(data, dict):
|
|
134
|
+
for key, value in data.items():
|
|
135
|
+
# if the current key matches, return its path immediately
|
|
136
|
+
if key == target_key:
|
|
137
|
+
return path + [key]
|
|
138
|
+
# otherwise, keep descending
|
|
139
|
+
result = recursive_search(value, path + [key])
|
|
140
|
+
if result:
|
|
141
|
+
return result
|
|
142
|
+
elif isinstance(data, list):
|
|
143
|
+
for index, item in enumerate(data):
|
|
144
|
+
result = recursive_search(item, path + [f"[{index}]"])
|
|
145
|
+
if result:
|
|
146
|
+
return result
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
path_key = recursive_search(structure_dict, [])
|
|
150
|
+
if not path_key:
|
|
151
|
+
raise TypeError(f"Could not find directory '{target_key}'.")
|
|
152
|
+
if base_directory in [None, ""]:
|
|
153
|
+
return os.path.join(rev_directory(), *path_key)
|
|
154
|
+
else:
|
|
155
|
+
return os.path.join(rev_directory(), base_directory, *path_key)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def verify_revision_structure():
|
|
159
|
+
from harnice import cli
|
|
160
|
+
from harnice.lists import rev_history
|
|
161
|
+
|
|
162
|
+
cwd = os.getcwd()
|
|
163
|
+
cwd_name = os.path.basename(cwd)
|
|
164
|
+
parent = os.path.basename(os.path.dirname(cwd))
|
|
165
|
+
|
|
166
|
+
# --- 1) Already in a <PN>-revN folder? ---
|
|
167
|
+
if cwd_name.startswith(f"{parent}-rev") and cwd_name.split("-rev")[-1].isdigit():
|
|
168
|
+
state.set_pn(parent)
|
|
169
|
+
state.set_rev(int(cwd_name.split("-rev")[-1]))
|
|
170
|
+
|
|
171
|
+
# --- 2) In a part folder that contains revision folders? ---
|
|
172
|
+
elif any(
|
|
173
|
+
re.fullmatch(rf"{re.escape(cwd_name)}-rev\d+", d) for d in os.listdir(cwd)
|
|
174
|
+
):
|
|
175
|
+
print(f"This is a part folder ({cwd_name}).")
|
|
176
|
+
print(f"Please `cd` into a revision folder (e.g. `{cwd_name}-rev1`) and rerun.")
|
|
177
|
+
exit()
|
|
178
|
+
|
|
179
|
+
# --- 3) No revision structure → initialize new PN here ---
|
|
180
|
+
else:
|
|
181
|
+
answer = cli.prompt(
|
|
182
|
+
f"No valid Harnice file structure detected in '{cwd_name}'. Create new PN here?",
|
|
183
|
+
default="y",
|
|
184
|
+
)
|
|
185
|
+
if answer.lower() not in ("y", "yes", ""):
|
|
186
|
+
exit()
|
|
187
|
+
|
|
188
|
+
state.set_pn(cwd_name)
|
|
189
|
+
|
|
190
|
+
# inline prompt_new_rev
|
|
191
|
+
rev = int(cli.prompt("Enter revision number", default="1"))
|
|
192
|
+
state.set_rev(rev)
|
|
193
|
+
folder = os.path.join(cwd, f"{state.pn}-rev{state.rev}")
|
|
194
|
+
os.makedirs(folder, exist_ok=True)
|
|
195
|
+
os.chdir(folder)
|
|
196
|
+
|
|
197
|
+
# --- Ensure revision_history entry exists ---
|
|
198
|
+
try:
|
|
199
|
+
rev_history.info()
|
|
200
|
+
except ValueError:
|
|
201
|
+
rev_history.append(next_rev=state.rev)
|
|
202
|
+
except FileNotFoundError:
|
|
203
|
+
rev_history.append(next_rev=state.rev)
|
|
204
|
+
|
|
205
|
+
# --- Status must be blank to proceed ---
|
|
206
|
+
if rev_history.info(field="status") != "":
|
|
207
|
+
raise RuntimeError(
|
|
208
|
+
f"Revision {state.rev} status is not clear. "
|
|
209
|
+
f"Harnice only renders revisions with a blank status."
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
print(f"Working on PN: {state.pn}, Rev: {state.rev}")
|
|
213
|
+
rev_history.update_datemodified()
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def today():
|
|
217
|
+
return datetime.date.today().strftime("%-m/%-d/%y")
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def get_git_hash_of_harnice_src():
|
|
221
|
+
try:
|
|
222
|
+
# get path to harnice package directory
|
|
223
|
+
import harnice
|
|
224
|
+
|
|
225
|
+
repo_dir = os.path.dirname(os.path.dirname(harnice.__file__))
|
|
226
|
+
# ask git for commit hash
|
|
227
|
+
return (
|
|
228
|
+
subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repo_dir)
|
|
229
|
+
.decode("utf-8")
|
|
230
|
+
.strip()
|
|
231
|
+
)
|
|
232
|
+
except Exception:
|
|
233
|
+
return "UNKNOWN"
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def get_path_to_project(traceable_key):
|
|
237
|
+
"""
|
|
238
|
+
Given a traceable identifier for a project (PN, URL, etc),
|
|
239
|
+
return the expanded local filesystem path.
|
|
240
|
+
|
|
241
|
+
Expects a CSV at the root of the repo named:
|
|
242
|
+
project_locations.csv
|
|
243
|
+
|
|
244
|
+
Format (no headers):
|
|
245
|
+
traceable_key,local_path
|
|
246
|
+
"""
|
|
247
|
+
from harnice import fileio
|
|
248
|
+
|
|
249
|
+
path = fileio.path("project locations") # resolves to project_locations.csv
|
|
250
|
+
|
|
251
|
+
if not os.path.exists(path):
|
|
252
|
+
raise FileNotFoundError(
|
|
253
|
+
"Make a CSV at the root of your Harnice repo called project_locations.csv "
|
|
254
|
+
"with the following format (no headers):\n\n"
|
|
255
|
+
" traceable_key,local_path\n"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
traceable_key = traceable_key.strip()
|
|
259
|
+
|
|
260
|
+
with open(path, newline="", encoding="utf-8") as f:
|
|
261
|
+
reader = csv.reader(f, delimiter=",")
|
|
262
|
+
for row in reader:
|
|
263
|
+
# skip blank or comment lines
|
|
264
|
+
if not row or len(row) < 2 or row[0].strip().startswith("#"):
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
key, local = row[0].strip(), row[1].strip()
|
|
268
|
+
|
|
269
|
+
if key == traceable_key:
|
|
270
|
+
if not local:
|
|
271
|
+
raise ValueError(
|
|
272
|
+
f"No project local path found for '{traceable_key}'"
|
|
273
|
+
)
|
|
274
|
+
return os.path.expanduser(local)
|
|
275
|
+
|
|
276
|
+
raise ValueError(f"Could not find project traceable key '{traceable_key}'")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def read_tsv(filepath, delimiter="\t"):
|
|
280
|
+
try:
|
|
281
|
+
with open(filepath, newline="", encoding="utf-8") as f:
|
|
282
|
+
return list(csv.DictReader(f, delimiter=delimiter))
|
|
283
|
+
except FileNotFoundError:
|
|
284
|
+
filepath = path(filepath)
|
|
285
|
+
try:
|
|
286
|
+
with open(filepath, newline="", encoding="utf-8") as f:
|
|
287
|
+
return list(csv.DictReader(f, delimiter=delimiter))
|
|
288
|
+
except FileNotFoundError:
|
|
289
|
+
raise FileNotFoundError(
|
|
290
|
+
f"Expected csv or tsv file with delimiter '{delimiter}' at path or key {filepath}"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def drawnby():
|
|
295
|
+
return json.load(open(path("drawnby")))
|