Skip to content

Commit ae10760

Browse files
authored
Improved helpers. Improved ColumnsWidth. Added word_wrap option. Coverage is now 87% (#195)
* Adding * Trying * Trying * Improving columnsWidth * Adding more modes * Complete * Adding validations * Trying * Trying refactorizing * Trying refactorizing * Fixing bugs * FROM_SHEET_CELLS work * All test work * Added fast lor sheet * Improving ods method * Works * Works * Works * Improving * Refactorizado totalccolumns * Propuesta de mensaje de commit 1 refactor: improve aesthetics, performance and ODS architecture 2 3 - Set word_wrap=True and vertical alignment to CENTER by default for better readability. 4 - Refactor ODS class to be template-agnostic using configurable defaults. 5 - Move Normal style and fixed height (452) logic specifically to ODS_Standard. 6 - Optimize massive data insertion by skipping style loops for default formatting. 7 - Implement a row-wrap memory system to prevent property overrides. 8 - Enhance block_from_lod_with_headers with integrated totals and freeze panes. 9 - Update standard.ods template with fixed row heights and optimal height disabled. * Mejorada hoja de stylos * Refactorizado y mejorado codigo * I dont know * Improved doc * Corregido Sheet * Añadido resto de helpers * Added helpers * Fixed errors * Reordering tests * Add test for unogenerator_cleaner * Added tests form monitor y translation * Removed showing logic * Fixing action errors
1 parent e5e53ce commit ae10760

17 files changed

Lines changed: 2120 additions & 639 deletions

.github/workflows/python-app.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- name: Install LibreOffice and uno dependencies
2525
run: |
2626
sudo apt-get update
27-
sudo apt-get install -y libreoffice python3-uno imagemagick # Removed explicit python3.10 and python3.10-venv
27+
sudo apt-get install -y libreoffice python3-uno imagemagick gettext # Removed explicit python3.10 and python3.10-venv
2828
- name: Install python environment
2929
run: |
3030
python -m venv .venv --system-site-packages # Use 'python' and remove 'sudo'

HELPERS.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# UnoGenerator Helpers Specification
2+
3+
Detailed reference of all helper functions in `unogenerator.helpers`.
4+
5+
## Table of Contents
6+
1. [Totals Generation (Basic)](#1-totals-generation-basic)
7+
2. [Totals Generation (Titled)](#2-totals-generation-titled)
8+
3. [Advanced Totals (Cross-Calculations)](#3-advanced-totals-cross-calculations)
9+
4. [Data Block Helpers](#4-data-block-helpers)
10+
5. [Complete Sheet Helpers](#5-complete-sheet-helpers)
11+
6. [Utility Helpers](#6-utility-helpers)
12+
13+
---
14+
15+
## 1. Totals Generation (Basic)
16+
17+
### `row_totals`
18+
Generates a horizontal row of formulas.
19+
- `doc` (ODS): The ODS document object.
20+
- `coord` (Coord or str): Starting coordinate.
21+
- `list_of_totals` (list): List of formula keys (e.g. `["#SUM", "#AVG"]`).
22+
- `color` (int, default=ColorsNamed.GrayLight): Background color.
23+
- `styles` (list or str, default=None): Cell style(s).
24+
- `row_from` (str, default="2"): Start row for calculation range.
25+
- `row_to` (str, default=None): End row for calculation range.
26+
27+
### `column_totals`
28+
Generates a vertical column of formulas.
29+
- `doc` (ODS): The ODS document object.
30+
- `coord` (Coord or str): Starting coordinate.
31+
- `list_of_totals` (list): List of formula keys.
32+
- `color` (int, default=ColorsNamed.GrayLight): Background color.
33+
- `styles` (list or str, default=None): Cell style(s).
34+
- `column_from` (str, default="B"): Start column for calculation range.
35+
- `column_to` (str, default=None): End column for calculation range.
36+
37+
---
38+
39+
## 2. Totals Generation (Titled)
40+
41+
### `row_title_values_total`
42+
Creates a row with a title, a sequence of values, and their sum.
43+
- `doc` (ODS): The ODS document object.
44+
- `coord` (Coord or str): Starting coordinate.
45+
- `title` (str): Title for the row.
46+
- `values` (list): List of numerical values.
47+
- `style_title` (str, default="Bold"): Style for the title.
48+
- `color_title` (int, default=ColorsNamed.Orange): Title color.
49+
- `style_values` (str, default=None): Style for the values.
50+
- `color_values` (int, default=ColorsNamed.White): Values color.
51+
- `style_total` (str, default=None): Style for the total.
52+
- `color_total` (int, default=ColorsNamed.GrayLight): Total color.
53+
54+
### `column_title_values_total`
55+
Creates a column with a title, a sequence of values, and their sum.
56+
- `doc` (ODS): The ODS document object.
57+
- `coord` (Coord or str): Starting coordinate.
58+
- `title` (str): Title for the column.
59+
- `values` (list): List of numerical values.
60+
- `style_title` (str, default="Bold"): Style for the title.
61+
- `color_title` (int, default=ColorsNamed.Orange): Title color.
62+
- `style_values` (str, default=None): Style for the values.
63+
- `color_values` (int, default=ColorsNamed.White): Values color.
64+
- `style_total` (str, default=None): Style for the total.
65+
- `color_total` (int, default=ColorsNamed.GrayLight): Total color.
66+
67+
---
68+
69+
## 3. Advanced Totals (Cross-Calculations)
70+
71+
### `cross_totals_from_range`
72+
Generates optimized vertical and horizontal totals for a data range.
73+
- `doc` (ODS): The ODS document object.
74+
- `range_of_data` (Range or str): The data range to calculate from.
75+
- `key` (str, default="#SUM"): Formula alias, function name, or template (e.g. `"=SUM({}*1.21)"`).
76+
- `column_of_totals` (bool, default=True): If True, adds column at the right.
77+
- `row_of_totals` (bool, default=True): If True, adds row at the bottom.
78+
- `vertical_total_title_style` (str, default="BoldCenter"): Style for right label.
79+
- `horizontal_total_title_style` (str, default="BoldCenter"): Style for bottom label.
80+
- `showing` (bool, default=False): Legacy. If True, adds extra "Sum of totals" block.
81+
- `label_column` (str, default="Total"): Text for the vertical total label.
82+
- `label_row` (str, default="Total"): Text for the horizontal total label.
83+
- `skip_columns` (int, default=0): Number of columns to skip for bottom row totals.
84+
85+
---
86+
87+
## 4. Data Block Helpers
88+
89+
### `block_from_lod`
90+
Inserts data from a List of Ordered Dictionaries.
91+
- `doc` (ODS): The ODS document object.
92+
- `coord_start` (Coord or str): Starting coordinate.
93+
- `lod_` (list): The list of dictionaries.
94+
- `keys` (list, default=None): Specific keys to write.
95+
- `columns_header` (int, default=0): Number of columns to color as headers.
96+
- `color_row_header` (int, default=ColorsNamed.Orange): Color for the keys row.
97+
- `color_column_header` (int, default=ColorsNamed.Green): Color for the header columns.
98+
- `color` (int, default=ColorsNamed.White): Background for data cells.
99+
- `styles` (list or str, default=None): Styles for data cells.
100+
- `column_of_totals` (bool, default=False): Generate totals on the right.
101+
- `row_of_totals` (bool, default=False): Generate totals at the bottom.
102+
- `key` (str, default="#SUM"): Formula key (see `cross_totals_from_range`).
103+
- `title` (str, default=None): Merged title for the block.
104+
- `word_wrap` (bool, default=True): Enable text wrapping.
105+
106+
### `block_from_lol`
107+
Inserts data from a List of Lists.
108+
- `doc` (ODS): The ODS document object.
109+
- `coord_start` (Coord or str): Starting coordinate.
110+
- `lor` (list): The list of lists.
111+
- `headers` (list, default=None): Column header names.
112+
- `colors` (list or int, default=ColorsNamed.White): Column colors.
113+
- `styles` (list or str, default=None): Column styles.
114+
- `column_of_totals` (bool, default=False): Generate totals on the right.
115+
- `row_of_totals` (bool, default=False): Generate totals at the bottom.
116+
- `key` (str, default="#SUM"): Formula key.
117+
- `title` (str, default=None): Merged title for the block.
118+
- `word_wrap` (bool, default=True): Enable text wrapping.
119+
120+
### `block_from_lod_with_headers`
121+
LOD writer with hierarchical sub-headers.
122+
- `doc` (ODS): The ODS document object.
123+
- `lod_` (list): List of dictionaries.
124+
- `coord` (Coord or str): Starting coordinate.
125+
- `subtitles` (list, default=[]): Groups of columns. List of `[title, first_key]`.
126+
- `titulo` (str, default=None): Main title.
127+
- `column_of_totals` (bool, default=False): Generate totals on the right.
128+
- `row_of_totals` (bool, default=False): Generate totals at the bottom.
129+
- `freezeandselect` (Coord or str, default=None): Auto-freeze coordinate.
130+
- `key` (str, default="#SUM"): Formula key.
131+
- `word_wrap` (bool, default=True): Enable text wrapping.
132+
133+
---
134+
135+
## 5. Complete Sheet Helpers
136+
137+
### `sheet_from_lod`
138+
Creates a new sheet and populates it from an LOD.
139+
- `doc` (ODS): The ODS document object.
140+
- `sheetname` (str): Name of the new sheet.
141+
- `lod_` (list): List of dictionaries.
142+
- `column_of_totals` (bool, default=False): Right totals.
143+
- `row_of_totals` (bool, default=False): Bottom totals.
144+
- `freezeandselect` (str, default=None): Coordination to freeze.
145+
- `title` (str, default=None): Main title.
146+
- `word_wrap` (bool, default=True): Text wrap.
147+
- `styles` (list or str, default=None): Data styles.
148+
- `**kwargs_columnswidth`: Extra params for column width calculation.
149+
150+
### `sheet_from_lol`
151+
Creates a new sheet and populates it from an LOL.
152+
- `doc` (ODS): The ODS document object.
153+
- `sheetname` (str): Name of the new sheet.
154+
- `lor` (list): List of lists.
155+
- `headers` (list): Header names.
156+
- `column_of_totals` (bool, default=False): Right totals.
157+
- `row_of_totals` (bool, default=False): Bottom totals.
158+
- `freezeandselect` (str, default=None): Coordination to freeze.
159+
- `titulo` (str, default=None): Main title.
160+
- `word_wrap` (bool, default=True): Text wrap.
161+
- `**kwargs_columnswidth`: Extra params for column width calculation.
162+
163+
### `sheet_split_with_big_lol`
164+
Creates multiple sheets for massive datasets.
165+
- `doc` (ODS): The ODS document object.
166+
- `sheet_name` (str): Base name for sheets.
167+
- `lor` (list): Massive list of lists.
168+
- `headers` (list): Header names.
169+
- `headers_colors` (int, default=ColorsNamed.Orange): Header background.
170+
- `coord_to_freeze` (Coord or str, default="A2"): Freeze coordinate.
171+
- `max_rows` (int, default=1048575): Max rows per sheet.
172+
- `word_wrap` (bool, default=True): Text wrap.
173+
174+
---
175+
176+
## 6. Utility Helpers
177+
178+
### `sheet_stylenames`
179+
Generates a reference sheet with available document styles.
180+
- `doc` (ODS): The ODS document object.

INSTALL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Only Linux is supported
44

5-
You need LibreOffice installed in your system. Imagemagick is recommended for some image operations.
5+
You need LibreOffice installed in your system. **Imagemagick** is recommended for some image operations and **psutil** is recommended for robust process management.
66

77
In some distros you need to install Python LibreOffice bindings too (python3-uno)
88

README.md

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,71 +7,87 @@
77

88
## Description
99

10-
Python module to generate Libreoffice documents (ODT and ODS) programatically.
10+
**UnoGenerator** is a powerful Python module designed to generate LibreOffice documents (ODT and ODS) programmatically with high performance and professional aesthetics.
1111

12-
Morever, you can export them to (.xlsx, .docx, .pdf) easyly.
12+
Key features include:
13+
- **Professional Defaults**: Automatic text wrapping (`word_wrap=True`) and vertical centering for professional-looking reports out of the box.
14+
- **High Performance**: Optimized for large datasets. Inserting 10,000 rows with default formatting takes less than 0.3 seconds.
15+
- **Rich Exports**: Easy export to `.xlsx`, `.docx`, and `.pdf`.
16+
- **Advanced Helpers**: Flexible totals generation, automatic column width calculation, and complex data block handling.
17+
- **Multilingual Support**: Built-in support for translations and localized document generation.
1318

14-
It uses Libreoffice uno module, so you need Libreoffice to be installed in your system.
19+
It uses the LibreOffice UNO module, requiring a LibreOffice installation on your system.
1520

1621
## Installation
1722

18-
Only Linux is supported. I'm going to write unogenerator [installation methods](INSTALL.md) for some main Linux Distributions
23+
Only Linux is supported. See the [installation guide](INSTALL.md) for detailed instructions on main Linux distributions.
1924

25+
## Architecture
26+
27+
UnoGenerator follows a clean, template-based architecture:
28+
- **ODS/ODT**: Generic base classes for document manipulation.
29+
- **ODS_Standard / ODT_Standard**: Optimized subclasses that use the built-in professional templates, providing specific styles (like "Normal", "BoldCenter") and optimized row heights.
2030

2131
## ODT 'Hello World' example
2232

23-
This is a Hello World example. You get the example in odt, docx and pdf formats:
33+
Create a professional ODT document with just a few lines:
2434

2535
```python
2636
from unogenerator import ODT_Standard
37+
2738
with ODT_Standard() as doc:
28-
doc.addParagraph("Hello World", "Heading 1")
29-
doc.addParagraph("Easy, isn't it","Standard")
30-
doc.save("hello_world.odt")
31-
doc.export_docx("hello_world.docx")
32-
doc.export_pdf("hello_world.pdf")
39+
doc.addParagraph("Hello World", "Heading 1")
40+
doc.addParagraph("Easy, isn't it", "Standard")
41+
doc.save("hello_world.odt")
42+
doc.export_docx("hello_world.docx")
43+
doc.export_pdf("hello_world.pdf")
3344
```
3445

3546
## ODS 'Hello World' example
3647

37-
This is a Hello World example. You'll get example files in ods, xlsx and pdf formats:
48+
Generate a styled spreadsheet with automatic wrapping and alignment:
3849

3950
```python
4051
from unogenerator import ODS_Standard
52+
4153
with ODS_Standard() as doc:
42-
doc.addCellMergedWithStyle("A1:E1", "Hello world", style="BoldCenter")
43-
doc.save("hello_world.ods")
44-
doc.export_xlsx("hello_world.xlsx")
45-
doc.export_pdf("hello_world.pdf")
54+
# word_wrap and vertical alignment are enabled by default
55+
doc.addCellMergedWithStyle("A1:E1", "Sales Report 2026", style="BoldCenter")
56+
doc.addRowWithStyle("A2", ["Product", "Quantity", "Price", "Total"])
57+
doc.save("sales_report.ods")
58+
doc.export_xlsx("sales_report.xlsx")
4659
```
4760

48-
## Unogenerator scripts
61+
## Advanced Features: Totals and Formulas
4962

50-
Python unogenerator package has the following scripts:
63+
UnoGenerator provides advanced helpers to generate calculations quickly:
5164

52-
### unogenerator_monitor
65+
```python
66+
from unogenerator import helpers
5367

54-
Monitors your libreoffice server instances
68+
# Generates both row and column totals with a custom formula template
69+
helpers.cross_totals_from_range(doc, "B2:D10", key="=SUM({}*1.21)")
70+
```
5571

56-
### unogenerator_translation
72+
## Unogenerator scripts
5773

58-
With this tool you can translate several odt files with one command. It generates .pot and .po files, where you can set your translations. Then run your command again and you'll get your files translated
74+
The package includes several useful CLI tools:
5975

60-
`unogenerator_translation --from_language es --to_language en --input original.odt --input original2.odt --output_directory "translation_original"`
76+
### unogenerator_monitor
77+
Monitors your LibreOffice server instances and their status.
6178

62-
You can use --fake to see simulation of your translation
79+
### unogenerator_translation
80+
Translate multiple ODT files using standard `.pot` and `.po` files.
81+
`unogenerator_translation --from_language es --to_language en --input original.odt --output_directory "translated"`
6382

6483
### unogenerator_demo
65-
66-
With this tool you can generate a demo, remove its result files and make benchmark comparations in your system
84+
Generate comprehensive example files and perform performance benchmarks on your system.
6785

6886
## Documentation
69-
You can read [documentation](https://github.com/turulomio/unogenerator/blob/main/doc/unogenerator_documentation_en.odt?raw=true) in doc directory. It has been created with unogenerator.
87+
Full technical [documentation](https://github.com/turulomio/unogenerator/blob/main/doc/unogenerator_documentation_en.odt?raw=true) is available in the `doc` directory, created using UnoGenerator itself.
7088

7189
## Development links
7290

7391
- [LibreOffice code](https://github.com/LibreOffice/core)
7492
- [LibreOffice API](https://api.libreoffice.org/docs/idl/ref/index.html)
75-
- [OpenOffice Forums](https://forum.openoffice.org/en/forum/viewforum.php?f=20)
76-
- [LibreOffice Forums](https://ask.libreoffice.org/)
77-
- [UnoGenerator API](https://coolnewton.mooo.com/doxygen/unogenerator/)
93+
- [UnoGenerator API (Doxygen)](https://coolnewton.mooo.com/doxygen/unogenerator/)

tests/test_cleaner.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
import pytest
3+
from unittest.mock import patch, MagicMock
4+
from unogenerator.monitor import command_cleaner
5+
import os
6+
import shutil
7+
8+
def test_command_cleaner(tmp_path):
9+
# Mocking scandir to return our test entries
10+
# But wait, command_cleaner uses hardcoded "/tmp"
11+
# To test it without actually touching /tmp, we would need to mock scandir or change the function to accept a path.
12+
# However, since it's a "cleaner", we can test it by creating specific files in /tmp if we are careful.
13+
14+
test_dir = "/tmp/unogenerator_pytest_dir"
15+
test_file = "/tmp/unogenerator_pytest_file"
16+
17+
os.makedirs(test_dir, exist_ok=True)
18+
with open(test_file, "w") as f:
19+
f.write("test")
20+
21+
assert os.path.exists(test_dir)
22+
assert os.path.exists(test_file)
23+
24+
with patch("unogenerator.monitor.run") as mock_run:
25+
command_cleaner()
26+
27+
# Verify killall was called
28+
mock_run.assert_called_with(['killall', '-9', 'soffice.bin'], check=False)
29+
30+
# Verify files are gone
31+
assert not os.path.exists(test_dir)
32+
assert not os.path.exists(test_file)
33+
34+
import sys
35+
36+
def test_cleaner_entry_point():
37+
# Test the cleaner() function which parses args
38+
with patch("unogenerator.monitor.command_cleaner") as mock_command:
39+
with patch.object(sys, 'argv', ['unogenerator_cleaner']):
40+
from unogenerator.monitor import cleaner
41+
cleaner()
42+
mock_command.assert_called_once()
43+
44+
def test_cleaner_no_files():
45+
# Test that it doesn't crash if no files exist
46+
with patch("unogenerator.monitor.scandir", return_value=[]):
47+
with patch("unogenerator.monitor.run"):
48+
command_cleaner()
49+
# Should not raise exception

0 commit comments

Comments
 (0)