harnice 0.3.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.
- harnice/__init__.py +0 -0
- harnice/__main__.py +4 -0
- harnice/cli.py +234 -0
- harnice/fileio.py +295 -0
- harnice/gui/launcher.py +426 -0
- harnice/lists/channel_map.py +182 -0
- harnice/lists/circuits_list.py +302 -0
- harnice/lists/disconnect_map.py +237 -0
- harnice/lists/formboard_graph.py +63 -0
- harnice/lists/instances_list.py +280 -0
- harnice/lists/library_history.py +40 -0
- harnice/lists/manifest.py +93 -0
- harnice/lists/post_harness_instances_list.py +66 -0
- harnice/lists/rev_history.py +325 -0
- harnice/lists/signals_list.py +135 -0
- harnice/products/__init__.py +1 -0
- harnice/products/cable.py +152 -0
- harnice/products/chtype.py +80 -0
- harnice/products/device.py +844 -0
- harnice/products/disconnect.py +225 -0
- harnice/products/flagnote.py +139 -0
- harnice/products/harness.py +522 -0
- harnice/products/macro.py +10 -0
- harnice/products/part.py +640 -0
- harnice/products/system.py +125 -0
- harnice/products/tblock.py +270 -0
- harnice/state.py +57 -0
- harnice/utils/appearance.py +51 -0
- harnice/utils/circuit_utils.py +326 -0
- harnice/utils/feature_tree_utils.py +183 -0
- harnice/utils/formboard_utils.py +973 -0
- harnice/utils/library_utils.py +333 -0
- harnice/utils/note_utils.py +417 -0
- harnice/utils/svg_utils.py +819 -0
- harnice/utils/system_utils.py +563 -0
- harnice-0.3.0.dist-info/METADATA +32 -0
- harnice-0.3.0.dist-info/RECORD +41 -0
- harnice-0.3.0.dist-info/WHEEL +5 -0
- harnice-0.3.0.dist-info/entry_points.txt +3 -0
- harnice-0.3.0.dist-info/licenses/LICENSE +19 -0
- harnice-0.3.0.dist-info/top_level.txt +1 -0
harnice/__init__.py
ADDED
|
File without changes
|
harnice/__main__.py
ADDED
harnice/cli.py
ADDED
|
@@ -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
|
+
)
|
harnice/fileio.py
ADDED
|
@@ -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")))
|