fpptools 0.1.1__tar.gz → 0.2.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.
- fpptools-0.2.0/.gitignore +119 -0
- fpptools-0.2.0/CHANGELOG.md +29 -0
- fpptools-0.2.0/PKG-INFO +129 -0
- fpptools-0.2.0/README.md +107 -0
- fpptools-0.2.0/fpptools/__init__.py +3 -0
- fpptools-0.2.0/fpptools/__main__.py +6 -0
- fpptools-0.2.0/fpptools/app.py +29 -0
- fpptools-0.2.0/fpptools/cli.py +38 -0
- fpptools-0.2.0/fpptools/commands/__init__.py +37 -0
- fpptools-0.2.0/fpptools/commands/config_cmd.py +36 -0
- fpptools-0.2.0/fpptools/commands/fit.py +140 -0
- fpptools-0.2.0/fpptools/commands/str.py +55 -0
- fpptools-0.2.0/fpptools/commands/wipe.py +122 -0
- fpptools-0.2.0/fpptools/config.py +148 -0
- fpptools-0.2.0/fpptools/db.py +85 -0
- fpptools-0.2.0/fpptools/errors.py +5 -0
- fpptools-0.2.0/fpptools/fpptools.py +5 -0
- fpptools-0.2.0/fpptools/output.py +45 -0
- fpptools-0.2.0/fpptools/quick.py +70 -0
- fpptools-0.2.0/fpptools/safe_write.py +76 -0
- fpptools-0.2.0/fpptools/strfile.py +202 -0
- fpptools-0.2.0/pyproject.toml +52 -0
- fpptools-0.2.0/scripts/build_exe.ps1 +11 -0
- fpptools-0.2.0/scripts/exe_estructuras.py +4 -0
- fpptools-0.2.0/scripts/exe_fpptools.py +4 -0
- fpptools-0.2.0/tests/__init__.py +0 -0
- fpptools-0.2.0/tests/conftest.py +80 -0
- fpptools-0.2.0/tests/test_fpptools.py +247 -0
- fpptools-0.1.1/PKG-INFO +0 -23
- fpptools-0.1.1/README.md +0 -35
- fpptools-0.1.1/fpptools/__init__.py +0 -1
- fpptools-0.1.1/fpptools/fittinglist_str.txt +0 -2956
- fpptools-0.1.1/fpptools/fpptools.py +0 -207
- fpptools-0.1.1/pyproject.toml +0 -31
- fpptools-0.1.1/setup.py +0 -37
- {fpptools-0.1.1 → fpptools-0.2.0}/LICENSE +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
MANIFEST
|
|
27
|
+
|
|
28
|
+
# PyInstaller
|
|
29
|
+
# Usually these files are written by a python script from a template
|
|
30
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
31
|
+
*.manifest
|
|
32
|
+
*.spec
|
|
33
|
+
|
|
34
|
+
# Installer logs
|
|
35
|
+
pip-log.txt
|
|
36
|
+
pip-delete-this-directory.txt
|
|
37
|
+
|
|
38
|
+
# Unit test / coverage reports
|
|
39
|
+
htmlcov/
|
|
40
|
+
.tox/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
.cache
|
|
44
|
+
nosetests.xml
|
|
45
|
+
coverage.xml
|
|
46
|
+
*.cover
|
|
47
|
+
.hypothesis/
|
|
48
|
+
.pytest_cache/
|
|
49
|
+
|
|
50
|
+
# Translations
|
|
51
|
+
*.mo
|
|
52
|
+
*.pot
|
|
53
|
+
|
|
54
|
+
# Django stuff:
|
|
55
|
+
*.log
|
|
56
|
+
local_settings.py
|
|
57
|
+
db.sqlite3
|
|
58
|
+
|
|
59
|
+
# Flask stuff:
|
|
60
|
+
instance/
|
|
61
|
+
.webassets-cache
|
|
62
|
+
|
|
63
|
+
# Scrapy stuff:
|
|
64
|
+
.scrapy
|
|
65
|
+
|
|
66
|
+
# Sphinx documentation
|
|
67
|
+
docs/_build/
|
|
68
|
+
|
|
69
|
+
# PyBuilder
|
|
70
|
+
target/
|
|
71
|
+
|
|
72
|
+
# Jupyter Notebook
|
|
73
|
+
.ipynb_checkpoints
|
|
74
|
+
|
|
75
|
+
# pyenv
|
|
76
|
+
.python-version
|
|
77
|
+
|
|
78
|
+
# celery beat schedule file
|
|
79
|
+
celerybeat-schedule
|
|
80
|
+
|
|
81
|
+
# SageMath parsed files
|
|
82
|
+
*.sage.py
|
|
83
|
+
|
|
84
|
+
# Environments
|
|
85
|
+
.env
|
|
86
|
+
.venv
|
|
87
|
+
env/
|
|
88
|
+
venv/
|
|
89
|
+
ENV/
|
|
90
|
+
env.bak/
|
|
91
|
+
venv.bak/
|
|
92
|
+
|
|
93
|
+
# Spyder project settings
|
|
94
|
+
.spyderproject
|
|
95
|
+
.spyproject
|
|
96
|
+
|
|
97
|
+
# Rope project settings
|
|
98
|
+
.ropeproject
|
|
99
|
+
|
|
100
|
+
# mkdocs documentation
|
|
101
|
+
/site
|
|
102
|
+
|
|
103
|
+
# mypy
|
|
104
|
+
.mypy_cache/
|
|
105
|
+
|
|
106
|
+
# fpptools runtime output
|
|
107
|
+
fpptools_backups/
|
|
108
|
+
*.fpptools.tmp
|
|
109
|
+
|
|
110
|
+
# standalone exes (scripts/build_exe.ps1); dist/ holds only the PyPI wheel/sdist
|
|
111
|
+
dist_exe/
|
|
112
|
+
|
|
113
|
+
# outputs fpptools writes in the working folder, and archives
|
|
114
|
+
*.7z
|
|
115
|
+
estructuras*.xlsx
|
|
116
|
+
structures.csv
|
|
117
|
+
structures.xlsx
|
|
118
|
+
fittings_*.csv
|
|
119
|
+
fittinglist_*.csv
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
Rewrite for current Python and FP Pro 11.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `fpptools str list`: structures with folder, name, unique code (NameStr) and description (decoded from Base64). CSV (`;`, UTF-8 BOM) or `.xlsx`, `--lang es`.
|
|
9
|
+
- `fpptools_estructuras.exe` / `fpptools-estructuras`: double-click export of structures to `estructuras.xlsx` next to the exe.
|
|
10
|
+
- `fpptools config`: show the active configuration (password hidden).
|
|
11
|
+
- `fpptools wipe panedefs` and `fpptools wipe kits`: simulate by default; `--apply` backs up every file first and writes a report.
|
|
12
|
+
- `--under`, `--match`, `--root` on every folder-scanning command; `--ini` / `FPPTOOLS_INI` on all commands.
|
|
13
|
+
- Tests with generic fixtures.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `list_fittings` is now `fpptools fit list` (old syntax kept as a hidden, deprecated alias). Output is CSV with `;`; columns Folder, File, Fitting, Source, Note.
|
|
17
|
+
- Job selection uses `--job NAME` (prompted if missing).
|
|
18
|
+
- Rewritten files keep FP Pro's format: original XML declaration, windows-1252, CRLF.
|
|
19
|
+
- Packaging: uv + hatchling, Python >= 3.10, click 8, lxml 5+, PyMySQL 1.x, pyodbc 5 (Windows only).
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Importing fpptools no longer reads FP_PRO.INI or connects to the database (`--help` works without FP Pro).
|
|
23
|
+
- MySQL now uses `DB_PORT` and `DB_CHARSET` from FP_PRO.INI (defaults 3306 and latin1 when missing or empty).
|
|
24
|
+
- Access kits query no longer exhausts its cursor after the first file.
|
|
25
|
+
- Clean error messages instead of tracebacks when the INI, folders or database are missing.
|
|
26
|
+
|
|
27
|
+
## 0.1.1
|
|
28
|
+
|
|
29
|
+
- `list_fittings` for str/blk/job/jobs.
|
fpptools-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fpptools
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Administration and debugging CLI for emmegisoft FP Pro (structures, blocks, jobs)
|
|
5
|
+
Project-URL: Repository, https://github.com/andoresuperesu/fpptools
|
|
6
|
+
Author-email: Andres Perez <andresperezcera@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: click>=8.1
|
|
17
|
+
Requires-Dist: lxml>=5.0
|
|
18
|
+
Requires-Dist: openpyxl>=3.1
|
|
19
|
+
Requires-Dist: pymysql>=1.1
|
|
20
|
+
Requires-Dist: pyodbc>=5.0; sys_platform == 'win32'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# fpptools
|
|
24
|
+
|
|
25
|
+
Administration and debugging command line for emmegisoft **FP Pro**: structures (`.STR`), blocks (`.BLK`) and job records.
|
|
26
|
+
It works on the configuration that is **active in FP Pro right now** (read from `%APPDATA%\Emmegisoft\FP_PRO\FP_PRO.INI`).
|
|
27
|
+
|
|
28
|
+
*Guía rápida en español más abajo.*
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
pip install fpptools # or: uv tool install fpptools
|
|
34
|
+
fpptools --help
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Standalone exes (no Python needed) are built with `scripts\build_exe.ps1`:
|
|
38
|
+
|
|
39
|
+
- `fpptools.exe`: the full command line.
|
|
40
|
+
- `fpptools_estructuras.exe`: **double-click** to export the structures of the active configuration to `estructuras.xlsx` next to the exe (same as `fpptools str list --lang es -o estructuras.xlsx`). If that file is open in Excel, it writes `estructuras_<date_time>.xlsx` instead. Also installed as the `fpptools-estructuras` command.
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
| Command | What it does |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `fpptools config` | Show the active configuration: folders, database (password hidden) |
|
|
47
|
+
| `fpptools str list` | List structures: folder, name, unique code, description |
|
|
48
|
+
| `fpptools fit list` | List fittings used in structures, blocks, one job or all jobs (kits expanded from the database) |
|
|
49
|
+
| `fpptools wipe panedefs` | Remove glass fittings, glass DXF and pane table from every pane of the structures |
|
|
50
|
+
| `fpptools wipe kits` | Remove every kit from structures or blocks |
|
|
51
|
+
|
|
52
|
+
Every command has `--help`.
|
|
53
|
+
|
|
54
|
+
### Choosing files
|
|
55
|
+
|
|
56
|
+
Commands that read STR/BLK folders accept:
|
|
57
|
+
|
|
58
|
+
- `--under FOLDER`: only that folder of the tree, e.g. `--under 019_SERIE_22_PESADO`
|
|
59
|
+
- `--match PATTERN`: file name pattern without extension, e.g. `--match "VTA_*"` (case-insensitive)
|
|
60
|
+
- `--root FOLDER`: another folder instead of the one in FP_PRO.INI
|
|
61
|
+
|
|
62
|
+
### Output
|
|
63
|
+
|
|
64
|
+
`--out file.csv` (default), `--out file.xlsx`, or `--out -` for the console.
|
|
65
|
+
CSV files use `;` and UTF-8 with BOM, so Excel opens them correctly with accents. `--lang es` writes Spanish headers.
|
|
66
|
+
|
|
67
|
+
### Commands that modify files: simulate first
|
|
68
|
+
|
|
69
|
+
`wipe` commands **only simulate** by default: they report what would change and write nothing.
|
|
70
|
+
Add `--apply` to write. Before writing, every file is copied to `--backup-dir` (default `.\fpptools_backups\<date_time>\`, same sub-folders) together with a `report.txt`. To undo, copy the backed-up files back.
|
|
71
|
+
|
|
72
|
+
```powershell
|
|
73
|
+
fpptools wipe panedefs --under "LINEA X" # simulation
|
|
74
|
+
fpptools wipe panedefs --under "LINEA X" --apply # backup + write
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Close the structures in FP Pro before applying.
|
|
78
|
+
|
|
79
|
+
### Structure fields
|
|
80
|
+
|
|
81
|
+
| Column | Where it comes from |
|
|
82
|
+
|---|---|
|
|
83
|
+
| Folder | Folder of the file inside STR_PATH (the tree in FP Pro's structure browser) |
|
|
84
|
+
| Name | File name without `.STR` (the "Nombre" field in FP Pro) |
|
|
85
|
+
| Unique code | `FP_TREE/@NameStr` ("nombre unívoco" in FP Pro) |
|
|
86
|
+
| Description | `FP_TREE/FP_DESCRIPTION`, stored as Base64 inside the file |
|
|
87
|
+
|
|
88
|
+
### Database
|
|
89
|
+
|
|
90
|
+
`fit list` reads kits from the FP Pro database of the active configuration: MySQL (`CONN_STR_FP_PRO=1`, settings in `[MYSQL]`) or Access (`Fp_Pro32.MDB` in `MDB_PATH`). Queries are read-only.
|
|
91
|
+
Access needs the Microsoft Access ODBC driver with the same bitness (32/64-bit) as Python. Use `--no-db` to skip the database.
|
|
92
|
+
|
|
93
|
+
### Other options
|
|
94
|
+
|
|
95
|
+
- `--ini PATH` (or environment variable `FPPTOOLS_INI`): use another FP_PRO.INI.
|
|
96
|
+
- `fpptools list_fittings --searchtype str` (0.1 syntax) still works but is deprecated.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Guía rápida (español)
|
|
101
|
+
|
|
102
|
+
`fpptools` trabaja sobre la configuración **activa en FP Pro**.
|
|
103
|
+
|
|
104
|
+
**Exportar nombre y descripción de las estructuras:**
|
|
105
|
+
|
|
106
|
+
```powershell
|
|
107
|
+
fpptools str list --lang es -o estructuras.xlsx
|
|
108
|
+
fpptools str list --under 019_SERIE_22_PESADO --lang es -o serie22.csv
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Columnas: Carpeta, Nombre (nombre del archivo), Nombre único (nombre unívoco), Descripción.
|
|
112
|
+
|
|
113
|
+
**Sin línea de comandos:** doble clic en `fpptools_estructuras.exe`. Crea `estructuras.xlsx` en la misma carpeta del programa (si está abierto en Excel, crea `estructuras_<fecha_hora>.xlsx`). El programa debe estar en una carpeta donde se pueda escribir (no en `C:\Program Files`).
|
|
114
|
+
|
|
115
|
+
**Comandos que modifican archivos** (`wipe`): por defecto solo **simulan** y muestran qué cambiarían. Con `--apply` primero copian los originales a `fpptools_backups\<fecha_hora>\` y luego escriben. Cierre las estructuras en FP Pro antes de aplicar.
|
|
116
|
+
|
|
117
|
+
`fpptools config` muestra qué configuración, carpetas y base de datos está usando.
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```powershell
|
|
122
|
+
uv sync
|
|
123
|
+
uv run pytest
|
|
124
|
+
uv run fpptools --help
|
|
125
|
+
uv build ; uv publish # PyPI
|
|
126
|
+
.\scripts\build_exe.ps1 # dist_exe\fpptools.exe, dist_exe\fpptools_estructuras.exe (not in dist\: that is PyPI's)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
License: MIT.
|
fpptools-0.2.0/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# fpptools
|
|
2
|
+
|
|
3
|
+
Administration and debugging command line for emmegisoft **FP Pro**: structures (`.STR`), blocks (`.BLK`) and job records.
|
|
4
|
+
It works on the configuration that is **active in FP Pro right now** (read from `%APPDATA%\Emmegisoft\FP_PRO\FP_PRO.INI`).
|
|
5
|
+
|
|
6
|
+
*Guía rápida en español más abajo.*
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```powershell
|
|
11
|
+
pip install fpptools # or: uv tool install fpptools
|
|
12
|
+
fpptools --help
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Standalone exes (no Python needed) are built with `scripts\build_exe.ps1`:
|
|
16
|
+
|
|
17
|
+
- `fpptools.exe`: the full command line.
|
|
18
|
+
- `fpptools_estructuras.exe`: **double-click** to export the structures of the active configuration to `estructuras.xlsx` next to the exe (same as `fpptools str list --lang es -o estructuras.xlsx`). If that file is open in Excel, it writes `estructuras_<date_time>.xlsx` instead. Also installed as the `fpptools-estructuras` command.
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
| Command | What it does |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `fpptools config` | Show the active configuration: folders, database (password hidden) |
|
|
25
|
+
| `fpptools str list` | List structures: folder, name, unique code, description |
|
|
26
|
+
| `fpptools fit list` | List fittings used in structures, blocks, one job or all jobs (kits expanded from the database) |
|
|
27
|
+
| `fpptools wipe panedefs` | Remove glass fittings, glass DXF and pane table from every pane of the structures |
|
|
28
|
+
| `fpptools wipe kits` | Remove every kit from structures or blocks |
|
|
29
|
+
|
|
30
|
+
Every command has `--help`.
|
|
31
|
+
|
|
32
|
+
### Choosing files
|
|
33
|
+
|
|
34
|
+
Commands that read STR/BLK folders accept:
|
|
35
|
+
|
|
36
|
+
- `--under FOLDER`: only that folder of the tree, e.g. `--under 019_SERIE_22_PESADO`
|
|
37
|
+
- `--match PATTERN`: file name pattern without extension, e.g. `--match "VTA_*"` (case-insensitive)
|
|
38
|
+
- `--root FOLDER`: another folder instead of the one in FP_PRO.INI
|
|
39
|
+
|
|
40
|
+
### Output
|
|
41
|
+
|
|
42
|
+
`--out file.csv` (default), `--out file.xlsx`, or `--out -` for the console.
|
|
43
|
+
CSV files use `;` and UTF-8 with BOM, so Excel opens them correctly with accents. `--lang es` writes Spanish headers.
|
|
44
|
+
|
|
45
|
+
### Commands that modify files: simulate first
|
|
46
|
+
|
|
47
|
+
`wipe` commands **only simulate** by default: they report what would change and write nothing.
|
|
48
|
+
Add `--apply` to write. Before writing, every file is copied to `--backup-dir` (default `.\fpptools_backups\<date_time>\`, same sub-folders) together with a `report.txt`. To undo, copy the backed-up files back.
|
|
49
|
+
|
|
50
|
+
```powershell
|
|
51
|
+
fpptools wipe panedefs --under "LINEA X" # simulation
|
|
52
|
+
fpptools wipe panedefs --under "LINEA X" --apply # backup + write
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Close the structures in FP Pro before applying.
|
|
56
|
+
|
|
57
|
+
### Structure fields
|
|
58
|
+
|
|
59
|
+
| Column | Where it comes from |
|
|
60
|
+
|---|---|
|
|
61
|
+
| Folder | Folder of the file inside STR_PATH (the tree in FP Pro's structure browser) |
|
|
62
|
+
| Name | File name without `.STR` (the "Nombre" field in FP Pro) |
|
|
63
|
+
| Unique code | `FP_TREE/@NameStr` ("nombre unívoco" in FP Pro) |
|
|
64
|
+
| Description | `FP_TREE/FP_DESCRIPTION`, stored as Base64 inside the file |
|
|
65
|
+
|
|
66
|
+
### Database
|
|
67
|
+
|
|
68
|
+
`fit list` reads kits from the FP Pro database of the active configuration: MySQL (`CONN_STR_FP_PRO=1`, settings in `[MYSQL]`) or Access (`Fp_Pro32.MDB` in `MDB_PATH`). Queries are read-only.
|
|
69
|
+
Access needs the Microsoft Access ODBC driver with the same bitness (32/64-bit) as Python. Use `--no-db` to skip the database.
|
|
70
|
+
|
|
71
|
+
### Other options
|
|
72
|
+
|
|
73
|
+
- `--ini PATH` (or environment variable `FPPTOOLS_INI`): use another FP_PRO.INI.
|
|
74
|
+
- `fpptools list_fittings --searchtype str` (0.1 syntax) still works but is deprecated.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Guía rápida (español)
|
|
79
|
+
|
|
80
|
+
`fpptools` trabaja sobre la configuración **activa en FP Pro**.
|
|
81
|
+
|
|
82
|
+
**Exportar nombre y descripción de las estructuras:**
|
|
83
|
+
|
|
84
|
+
```powershell
|
|
85
|
+
fpptools str list --lang es -o estructuras.xlsx
|
|
86
|
+
fpptools str list --under 019_SERIE_22_PESADO --lang es -o serie22.csv
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Columnas: Carpeta, Nombre (nombre del archivo), Nombre único (nombre unívoco), Descripción.
|
|
90
|
+
|
|
91
|
+
**Sin línea de comandos:** doble clic en `fpptools_estructuras.exe`. Crea `estructuras.xlsx` en la misma carpeta del programa (si está abierto en Excel, crea `estructuras_<fecha_hora>.xlsx`). El programa debe estar en una carpeta donde se pueda escribir (no en `C:\Program Files`).
|
|
92
|
+
|
|
93
|
+
**Comandos que modifican archivos** (`wipe`): por defecto solo **simulan** y muestran qué cambiarían. Con `--apply` primero copian los originales a `fpptools_backups\<fecha_hora>\` y luego escriben. Cierre las estructuras en FP Pro antes de aplicar.
|
|
94
|
+
|
|
95
|
+
`fpptools config` muestra qué configuración, carpetas y base de datos está usando.
|
|
96
|
+
|
|
97
|
+
## Development
|
|
98
|
+
|
|
99
|
+
```powershell
|
|
100
|
+
uv sync
|
|
101
|
+
uv run pytest
|
|
102
|
+
uv run fpptools --help
|
|
103
|
+
uv build ; uv publish # PyPI
|
|
104
|
+
.\scripts\build_exe.ps1 # dist_exe\fpptools.exe, dist_exe\fpptools_estructuras.exe (not in dist\: that is PyPI's)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
License: MIT.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Shared CLI state passed to every command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from fpptools import config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class App:
|
|
13
|
+
"""The FP Pro configuration is loaded only when a command needs it."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, ini: Path | None):
|
|
16
|
+
self._ini = ini
|
|
17
|
+
self._cfg: config.FppConfig | None = None
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def cfg(self) -> config.FppConfig:
|
|
21
|
+
if self._cfg is None:
|
|
22
|
+
self._cfg = config.load(self._ini)
|
|
23
|
+
return self._cfg
|
|
24
|
+
|
|
25
|
+
def root(self, kind: str, override: Path | None) -> Path:
|
|
26
|
+
return Path(override) if override else self.cfg.root_for(kind)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
pass_app = click.make_pass_decorator(App)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""fpptools command line: `fpptools <group> <command>`.
|
|
2
|
+
|
|
3
|
+
Groups: config, str, fit, wipe. Run `fpptools <group> --help` for details.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from fpptools import __version__, config
|
|
13
|
+
from fpptools.app import App
|
|
14
|
+
from fpptools.commands import config_cmd, fit, str as str_cmd, wipe
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
|
|
18
|
+
@click.version_option(__version__, prog_name="fpptools")
|
|
19
|
+
@click.option("--ini", type=click.Path(dir_okay=False, path_type=Path), envvar=config.ENV_INI,
|
|
20
|
+
help="FP_PRO.INI to use. Default: the configuration FP Pro is using now.")
|
|
21
|
+
@click.pass_context
|
|
22
|
+
def cli(ctx: click.Context, ini: Path | None):
|
|
23
|
+
"""Administration and debugging tools for emmegisoft FP Pro.
|
|
24
|
+
|
|
25
|
+
Works on the configuration currently active in FP Pro (FP_PRO.INI).
|
|
26
|
+
"""
|
|
27
|
+
ctx.obj = App(ini)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
cli.add_command(config_cmd.config_show)
|
|
31
|
+
cli.add_command(str_cmd.str_group)
|
|
32
|
+
cli.add_command(fit.fit_group)
|
|
33
|
+
cli.add_command(fit.legacy_list_fittings)
|
|
34
|
+
cli.add_command(wipe.wipe_group)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main():
|
|
38
|
+
cli(prog_name="fpptools")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""CLI command groups. Shared options live here."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
HEADERS = {
|
|
10
|
+
"en": {"folder": "Folder", "name": "Name", "unique": "Unique code", "description": "Description",
|
|
11
|
+
"file": "File", "fitting": "Fitting", "source": "Source", "note": "Note", "error": "Error",
|
|
12
|
+
"job": "Job", "item": "Item"},
|
|
13
|
+
"es": {"folder": "Carpeta", "name": "Nombre", "unique": "Nombre único", "description": "Descripción",
|
|
14
|
+
"file": "Archivo", "fitting": "Accesorio", "source": "Origen", "note": "Nota", "error": "Error",
|
|
15
|
+
"job": "Trabajo", "item": "Ítem"},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def scope_options(f):
|
|
20
|
+
"""--root / --under / --match, shared by every command that walks STR/BLK folders."""
|
|
21
|
+
f = click.option("--match", metavar="PATTERN",
|
|
22
|
+
help="Only files whose name (no extension) matches, e.g. 'VTA_*'. Case-insensitive.")(f)
|
|
23
|
+
f = click.option("--under", metavar="FOLDER",
|
|
24
|
+
help="Only inside this folder, relative to the root, e.g. '019_SERIE_22_PESADO'.")(f)
|
|
25
|
+
f = click.option("--root", type=click.Path(file_okay=False, exists=True, path_type=Path),
|
|
26
|
+
help="Folder to scan instead of the one in FP_PRO.INI.")(f)
|
|
27
|
+
return f
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def lang_option(f):
|
|
31
|
+
return click.option("--lang", type=click.Choice(["en", "es"]), default="en", show_default=True,
|
|
32
|
+
help="Language of the column headers.")(f)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def out_option(default: str):
|
|
36
|
+
return click.option("--out", "-o", default=default, show_default=True,
|
|
37
|
+
help="Output file (.csv or .xlsx), or '-' for the console.")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""`fpptools config`: show which FP Pro configuration fpptools will use."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from fpptools.app import App, pass_app
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command("config")
|
|
11
|
+
@pass_app
|
|
12
|
+
def config_show(app: App):
|
|
13
|
+
"""Show the active FP Pro configuration (password hidden)."""
|
|
14
|
+
cfg = app.cfg
|
|
15
|
+
|
|
16
|
+
def line(label: str, value) -> None:
|
|
17
|
+
click.echo(f"{label:<16}{value}")
|
|
18
|
+
|
|
19
|
+
def folder(key: str) -> str:
|
|
20
|
+
value = cfg.get("SYSTEM", key)
|
|
21
|
+
if not value:
|
|
22
|
+
return "(not set)"
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
return f"{value}" + ("" if Path(value).is_dir() else " [not found]")
|
|
25
|
+
|
|
26
|
+
line("INI", cfg.ini_path)
|
|
27
|
+
line("Configuration", cfg.name or "(unknown)")
|
|
28
|
+
line("STR_PATH", folder("STR_PATH"))
|
|
29
|
+
line("BLK_PATH", folder("BLK_PATH"))
|
|
30
|
+
line("JOB_PATH", folder("JOB_PATH"))
|
|
31
|
+
line("CONFIGS_PATH", folder("CONFIGS_PATH"))
|
|
32
|
+
if cfg.db_kind == "mysql":
|
|
33
|
+
s = cfg.mysql
|
|
34
|
+
line("Database", f"MySQL {s.host}:{s.port}/{s.database} (user {s.user}, charset {s.charset})")
|
|
35
|
+
else:
|
|
36
|
+
line("Database", f"Access {cfg.mdb_file}" + ("" if cfg.mdb_file.is_file() else " [not found]"))
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""`fpptools fit ...`: fittings used in structures, blocks and jobs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from lxml import etree
|
|
10
|
+
|
|
11
|
+
from fpptools import db as dbmod
|
|
12
|
+
from fpptools import strfile
|
|
13
|
+
from fpptools.app import App, pass_app
|
|
14
|
+
from fpptools.commands import HEADERS, lang_option, scope_options
|
|
15
|
+
from fpptools.errors import FppToolsError
|
|
16
|
+
from fpptools.output import write_table
|
|
17
|
+
|
|
18
|
+
JOBRECORD = "_JOBRECORD"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _origin(el) -> str:
|
|
22
|
+
parent = el.getparent()
|
|
23
|
+
return f"{parent.tag}:{parent.get('Code', '')}" if parent is not None else ""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def fittings_in_design(path: Path, kits: dict[str, list[str]] | None) -> list[tuple[str, str, str]]:
|
|
27
|
+
"""(fitting code, source, note) for one STR/BLK. Kits are expanded from the database."""
|
|
28
|
+
tree = etree.parse(str(path), etree.XMLParser(resolve_entities=False, huge_tree=True))
|
|
29
|
+
rows = []
|
|
30
|
+
for fit in tree.iter("FP_FITTING"):
|
|
31
|
+
parent = fit.getparent()
|
|
32
|
+
if parent is not None and parent.tag == "FP_KIT":
|
|
33
|
+
continue # kit content comes from the database
|
|
34
|
+
rows.append((fit.get("Code", ""), _origin(fit), ""))
|
|
35
|
+
for kit in tree.iter("FP_KIT"):
|
|
36
|
+
name = kit.get("Code", "")
|
|
37
|
+
if kits is None:
|
|
38
|
+
rows.append(("", f"FP_KIT:{name}", "kit not expanded (--no-db)"))
|
|
39
|
+
elif name in kits:
|
|
40
|
+
rows.extend((code, f"FP_KIT:{name}", "") for code in kits[name])
|
|
41
|
+
else:
|
|
42
|
+
rows.append(("", f"FP_KIT:{name}", "kit not found in database"))
|
|
43
|
+
return rows
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def job_files(root: Path, job: str | None) -> list[Path]:
|
|
47
|
+
found = []
|
|
48
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
49
|
+
dirnames.sort(key=str.casefold)
|
|
50
|
+
for name in sorted(filenames, key=str.casefold):
|
|
51
|
+
upper = name.upper()
|
|
52
|
+
if not upper.endswith(".XML") or JOBRECORD not in upper:
|
|
53
|
+
continue
|
|
54
|
+
if job and not upper.startswith(job.upper() + JOBRECORD):
|
|
55
|
+
continue
|
|
56
|
+
found.append(Path(dirpath) / name)
|
|
57
|
+
return found
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def job_and_item(path: Path) -> tuple[str, str]:
|
|
61
|
+
stem = path.stem
|
|
62
|
+
job, _, rest = stem.upper().partition(JOBRECORD)
|
|
63
|
+
item = stem[len(job) + len(JOBRECORD):].lstrip("_") or stem[-2:]
|
|
64
|
+
return stem[: len(job)], item
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@click.group("fit")
|
|
68
|
+
def fit_group():
|
|
69
|
+
"""Fittings (accessories)."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@fit_group.command("list")
|
|
73
|
+
@click.option("--source", type=click.Choice(["str", "blk", "job", "jobs"]), default="str", show_default=True,
|
|
74
|
+
help="Structures, blocks, one job (--job) or all jobs.")
|
|
75
|
+
@click.option("--job", metavar="NAME", help="Job name for --source job (asked if missing).")
|
|
76
|
+
@click.option("--no-db", is_flag=True, help="Do not read the database; kits are listed but not expanded.")
|
|
77
|
+
@scope_options
|
|
78
|
+
@click.option("--out", "-o", default=None, help="Output file (.csv or .xlsx), or '-' for the console. "
|
|
79
|
+
"[default: fittings_<source>.csv]")
|
|
80
|
+
@lang_option
|
|
81
|
+
@pass_app
|
|
82
|
+
def fit_list(app: App, source: str, job: str | None, no_db: bool, root: Path | None, under: str | None,
|
|
83
|
+
match: str | None, out: str | None, lang: str):
|
|
84
|
+
"""List the fittings used in structures, blocks or job records.
|
|
85
|
+
|
|
86
|
+
Fittings inside kits are taken from the database (kit / dettagliokit).
|
|
87
|
+
"""
|
|
88
|
+
h = HEADERS[lang]
|
|
89
|
+
out = out or f"fittings_{source}.csv"
|
|
90
|
+
rows: list[list[str]] = []
|
|
91
|
+
if source in ("job", "jobs"):
|
|
92
|
+
if source == "job" and not job:
|
|
93
|
+
job = click.prompt("Job name")
|
|
94
|
+
base = app.root("job", root)
|
|
95
|
+
files = job_files(base, job if source == "job" else None)
|
|
96
|
+
if not files:
|
|
97
|
+
raise FppToolsError(f"No job records found in {base}" + (f" for job {job}" if job else ""))
|
|
98
|
+
headers = [h["job"], h["item"], h["fitting"], h["source"]]
|
|
99
|
+
for f in files:
|
|
100
|
+
j, item = job_and_item(f)
|
|
101
|
+
tree = etree.parse(str(f), etree.XMLParser(resolve_entities=False, huge_tree=True))
|
|
102
|
+
rows.extend([j, item, el.get("Code", ""), _origin(el)] for el in tree.iter("FP_FITTING"))
|
|
103
|
+
else:
|
|
104
|
+
base = app.root(source, root)
|
|
105
|
+
files = strfile.find_files(base, strfile.EXTENSIONS[source], under, match)
|
|
106
|
+
kits = None
|
|
107
|
+
if not no_db:
|
|
108
|
+
database = dbmod.ReadOnlyDB(app.cfg)
|
|
109
|
+
try:
|
|
110
|
+
kits = dbmod.kit_fittings(database)
|
|
111
|
+
finally:
|
|
112
|
+
database.close()
|
|
113
|
+
headers = [h["folder"], h["file"], h["fitting"], h["source"], h["note"]]
|
|
114
|
+
with click.progressbar(files, label=f"Reading {source.upper()} files",
|
|
115
|
+
file=click.get_text_stream("stderr"), show_pos=True) as bar:
|
|
116
|
+
for f in bar:
|
|
117
|
+
folder = strfile.rel_folder(f, base)
|
|
118
|
+
try:
|
|
119
|
+
found = fittings_in_design(f, kits)
|
|
120
|
+
except etree.XMLSyntaxError as exc:
|
|
121
|
+
click.echo(f"\nwarning: {f}: cannot read: {exc}", err=True)
|
|
122
|
+
rows.append([folder, f.name, "", "", f"cannot read: {exc}"])
|
|
123
|
+
continue
|
|
124
|
+
rows.extend([folder, f.name, code, src, note] for code, src, note in found)
|
|
125
|
+
where = write_table(headers, rows, out)
|
|
126
|
+
click.echo(f"{len(rows)} row(s) from {len(files)} file(s) -> {where}", err=True)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@click.command("list_fittings", hidden=True)
|
|
130
|
+
@click.option("--searchtype", type=click.Choice(["str", "blk", "job", "jobs"]), default="str")
|
|
131
|
+
@click.option("--tofile", type=bool, default=True)
|
|
132
|
+
@click.option("--verbose", type=bool, default=False)
|
|
133
|
+
@click.pass_context
|
|
134
|
+
def legacy_list_fittings(ctx: click.Context, searchtype: str, tofile: bool, verbose: bool):
|
|
135
|
+
"""fpptools 0.1 syntax; use `fpptools fit list`."""
|
|
136
|
+
click.echo("note: list_fittings is deprecated, use `fpptools fit list`.", err=True)
|
|
137
|
+
if tofile:
|
|
138
|
+
ctx.invoke(fit_list, source=searchtype, out=f"fittinglist_{searchtype}.csv")
|
|
139
|
+
if verbose or not tofile:
|
|
140
|
+
ctx.invoke(fit_list, source=searchtype, out="-")
|