Skip to content

Commit

Permalink
- Max decimal points for auto_float extended from (2) to (max-2).
Browse files Browse the repository at this point in the history
  • Loading branch information
delameter committed May 20, 2022
1 parent 4e421b3 commit f5e69fa
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 57 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ GREEN := $(shell tput -Txterm setaf 2)
YELLOW := $(shell tput -Txterm setaf 3)
RESET := $(shell tput -Txterm sgr0)

help:
help: ## Show this help
@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v @fgrep | sed -Ee 's/^##\s*([^#]+)#*\s*(.*)/${YELLOW}\1${RESET}#\2/' -e 's/(.+):(#|\s)+(.+)/## ${GREEN}\1${RESET}#\3/' | column -t -s '#'

cleanup:
Expand Down
56 changes: 32 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ print(orig_text, '\n', updated_text)
As you can see, the update went well — we kept all the previously applied formatting. Of course, this method cannot be 100% applicable — for example, imagine that original text was colored blue. After the update "string" word won't be blue anymore, as we used `COLOR_OFF` escape sequence to neutralize our own yellow color. But it still can be helpful for a majority of cases (especially when text is generated and formatted by the same program and in one go).


## API: module
## API: pytermor

### autof
### > `autof`

Signature: `autof(*params str|int|SequenceSGR) -> Format`

Expand All @@ -161,21 +161,21 @@ Each sequence param can be specified as:
- integer param value
- existing _SequenceSGR_ instance (params will be extracted)

### build
### > `build`

Signature: `build(*params str|int|SequenceSGR) -> SequenceSGR`

Create new _SequenceSGR_ with specified params. Resulting sequence params order is the same as argument order. Parameter specification is the same as for `autof`.

_SequenceSGR_ with zero params was specifically implemented to translate into empty string and not into `\e[m`, which wolud make sense, but also would be very entangling, as it's equivavlent of `\e[0m` — **hard reset** sequence.

### build_c256
### > `build_c256`

Signature:`build_c256(color: int, bg: bool = False) -> SequenceSGR`

Create new _SequenceSGR_ that sets foreground color or background color, depending on `bg` value, in 256-color mode. Valid values for `color` are [0; 255], see more at [↗ xterm-256 colors](https://www.ditig.com/256-colors-cheat-sheet) page.

### build_rgb
### > `build_rgb`

Signature:`build_rgb(r: int, g: int, b: int, bg: bool = False) -> SequenceSGR`

Expand All @@ -184,7 +184,7 @@ Create new _SequenceSGR_ that sets foreground color or background color, dependi

## API: SGR sequences

Class representing SGR-mode ANSI escape sequence with varying amount of parameters.
Class representing SGR-type ANSI escape sequence with varying amount of parameters.

<details>
<summary><b>Details</b> <i>(click)</i></summary>
Expand Down Expand Up @@ -293,7 +293,7 @@ print(formatted, '\n', replaced)

### Usage with helper

Helper function `apply_filters` accepts both `StringFilter` implementation instances and types, but latter is not configurable and will be invoked using default settings.
Helper function `apply_filters` accepts both `StringFilter` instances and types, but latter is not configurable and will be invoked using default settings.

```python3
from pytermor import apply_filters, ReplaceNonAsciiBytes
Expand All @@ -311,9 +311,9 @@ print(ascii_and_binary, '\n', result)

Set of methods to make working with SGR sequences a bit easier.

- `ljust_fmtd` SGR-formatting-aware implementation of str.ljust()
- `rjust_fmtd` same, but for _str.rjust()_
- `center_fmtd` same, but for _str.center()_
- `ljust_fmtd()` SGR-formatting-aware implementation of str.ljust()
- `rjust_fmtd()` same, but for _str.rjust()_
- `center_fmtd()` same, but for _str.center()_


## API: numf.*
Expand All @@ -323,7 +323,7 @@ Set of methods to make working with SGR sequences a bit easier.
<details>
<summary><b>Details</b> <i>(click)</i></summary>

### format_auto_float
### &gt; `format_auto_float`

Dynamically adjust decimal digit amount to fill the output string up with significant digits as much as possible. Universal solution for situations when you don't know exaclty what values will be displayed, but have fixed output width. Invocation: `format_auto_float(value, 4)`.

Expand All @@ -335,19 +335,15 @@ Dynamically adjust decimal digit amount to fill the output string up with signif
| **1.56** | `"1.56"` |

### format_prefixed_unit
### &gt; `format_prefixed_unit`

Similar to previous method, but this one also supports metric prefixes and is highly customizable. Invocation: `format_prefixed_unit(value)`.

| value | **631** | **1&nbsp;080** | **45&nbsp;200** | **1&nbsp;257&nbsp;800** | 4,31×10⁷ | 7,00×10⁸ | 2,50×10⁹ |
| :------: | :--------: | :--------: | :--------: | :--------: | :--------: | :--------: | :--------: |
| result | <code>631&nbsp;b</code> | <code>1.05&nbsp;kb</code> | <code>44.14&nbsp;kb</code> | <code>1.20&nbsp;Mb</code> | <code>41.11&nbsp;Mb</code> | <code>668.0&nbsp;Mb</code> | <code>2.33&nbsp;Gb</code> |

| value | **1** | **0.1** | ... |
| :------: | :--------: | :--------: | :---: |
| result | <code>1.00&nbsp;m</code> | <code>0.10&nbsp;m</code> | @TODO |

Settings example:
Settings:
```python
PrefixedUnitPreset(
max_value_len=5, integer_input=True,
Expand All @@ -356,16 +352,25 @@ PrefixedUnitPreset(
prefixes=[None, 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'],
prefix_zero_idx=0,
)
```

Example #2 illustrating small numbers:

| value | **-1.2345×10⁻¹¹** | **1.2345×10⁻⁸** | **1.2345×10⁻⁴** | **0.01234** | **1.23456** | **123.456** | **−12 345** |
| :------: | :--------: | :--------: | :---: | :---: | :---: | :---: | :---: |
| result | <code>-0.012nm</code> | <code>0.0123μm</code> | <code>0.1235mm</code> | <code>0.0123m</code> | <code>1.2346m</code> | <code>123.46m</code> | <code>-12.35km</code>

```python
PrefixedUnitPreset(
max_value_len=7, integer_input=False,
max_value_len=6, integer_input=False,
unit='m', unit_separator='',
mcoef=1000.0,
prefixes=['y', 'z', 'a', 'f', 'p', 'n', 'μ', 'm', None],
prefix_zero_idx=8,
)
```

### format_time_delta
### &gt; `format_time_delta`

Formats time interval in 4 different variants - 3-char, 4-char, 6-char and 10-char width output. Usage: `format_time_delta(seconds, max_len)`.

Expand Down Expand Up @@ -916,11 +921,14 @@ You can of course create your own sequences and formats, but with one limitation

### v1.8.0

- `format_prefixed_unit` extended for working with decimal and binary metric prefixes.
- `format_time_delta` extended with new settings.
- Value rounding transferred from `format_auto_float` to `format_prefixed_unit`
- Utility classes reorganization.

- `format_prefixed_unit` extended for working with decimal and binary metric prefixes;
- `format_time_delta` extended with new settings;
- Value rounding transferred from `format_auto_float` to `format_prefixed_unit`;
- Utility classes reorganization;
- Unit tests output formatting;
- `noop` SGR sequence and `noop` format;
- Max decimal points for `auto_float` extended from (2) to (max-2).
-
### v1.7.4

- Added 3 formatters: `fmt_prefixed_unit`, `fmt_time_delta`, `fmt_auto_float`.
Expand Down
24 changes: 15 additions & 9 deletions dev/readme/README.tpl.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ Can be applied using `.apply()` method or with direct call.

### Usage with helper

Helper function `apply_filters` accepts both `StringFilter` implementation instances and types, but latter is not configurable and will be invoked using default settings.
Helper function `apply_filters` accepts both `StringFilter` instances and types, but latter is not configurable and will be invoked using default settings.

@{dev/readme/examples/example11.py}
> ![image](https://user-images.githubusercontent.com/50381946/161387889-a1920f13-f5fc-4d10-b535-93f1a1b1aa5c.png)
Expand Down Expand Up @@ -248,11 +248,7 @@ Similar to previous method, but this one also supports metric prefixes and is hi
| :------: | :--------: | :--------: | :--------: | :--------: | :--------: | :--------: | :--------: |
| result | <code>631&nbsp;b</code> | <code>1.05&nbsp;kb</code> | <code>44.14&nbsp;kb</code> | <code>1.20&nbsp;Mb</code> | <code>41.11&nbsp;Mb</code> | <code>668.0&nbsp;Mb</code> | <code>2.33&nbsp;Gb</code> |

| value | **1** | **0.1** | ... |
| :------: | :--------: | :--------: | :---: |
| result | <code>1.00&nbsp;m</code> | <code>0.10&nbsp;m</code> | @TODO |

Settings example:
Settings:
```python
PrefixedUnitPreset(
max_value_len=5, integer_input=True,
Expand All @@ -261,8 +257,17 @@ PrefixedUnitPreset(
prefixes=[None, 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'],
prefix_zero_idx=0,
)
```

Example #2 illustrating small numbers:

| value | **-1.2345×10⁻¹¹** | **1.2345×10⁻⁸** | **1.2345×10⁻⁴** | **0.01234** | **1.23456** | **123.456** | **−12 345** |
| :------: | :--------: | :--------: | :---: | :---: | :---: | :---: | :---: |
| result | <code>-0.012nm</code> | <code>0.0123μm</code> | <code>0.1235mm</code> | <code>0.0123m</code> | <code>1.2346m</code> | <code>123.46m</code> | <code>-12.35km</code>

```python
PrefixedUnitPreset(
max_value_len=7, integer_input=False,
max_value_len=6, integer_input=False,
unit='m', unit_separator='',
mcoef=1000.0,
prefixes=['y', 'z', 'a', 'f', 'p', 'n', 'μ', 'm', None],
Expand Down Expand Up @@ -826,8 +831,9 @@ You can of course create your own sequences and formats, but with one limitation
- Value rounding transferred from `format_auto_float` to `format_prefixed_unit`;
- Utility classes reorganization;
- Unit tests output formatting;
- `noop` SGR sequence and `noop` format.

- `noop` SGR sequence and `noop` format;
- Max decimal points for `auto_float` extended from (2) to (max-2).
-
### v1.7.4

- Added 3 formatters: `fmt_prefixed_unit`, `fmt_time_delta`, `fmt_auto_float`.
Expand Down
2 changes: 1 addition & 1 deletion dev/runConfigurations/run-debug.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<option name="ADD_SOURCE_ROOTS" value="false" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/tests/run.py" />
<option name="PARAMETERS" value="-v" />
<option name="PARAMETERS" value="-vv" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
Expand Down
24 changes: 24 additions & 0 deletions dev/runConfigurations/run-verbose.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="run-verbose" type="PythonConfigurationType" factoryName="Python">
<module name="pytermor" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="false" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/tests/run.py" />
<option name="PARAMETERS" value="-v" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
</component>
4 changes: 3 additions & 1 deletion pytermor/numf/auto_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ def format_auto_float(value: float, max_len: int) -> str:
:param max_len: maximum output string length (total)
:return: formatted value
"""
max_decimals_len = 2
max_decimals_len = max_len - 2
if value < 0:
max_decimals_len -= 1 # minus sign
integer_len = len(str(trunc(value)))
decimals_and_point_len = min(max_decimals_len + 1, max_len - integer_len)

Expand Down
16 changes: 15 additions & 1 deletion pytermor/numf/prefixed_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ class PrefixedUnitPreset:
prefixes: List[str|None]|None
prefix_zero_idx: int|None

@property
def max_len(self) -> int:
result = self.max_value_len
result += len(self.unit_separator or '')
result += len(self.unit or '')
result += max([len(p) for p in self.prefixes if p])
return result


PREFIXES_SI = ['y', 'z', 'a', 'f', 'p', 'n', 'μ', 'm', None, 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
PREFIX_ZERO_SI = 8
Expand All @@ -41,6 +49,9 @@ class PrefixedUnitPreset:
``max_value_len`` must be at least **4**, because it's a
minimum requirement for displaying values from `999` to `-999`.
Next number to `999` is `1000`, which will be displayed as ``1k``.
Total maximum length is ``max_value_len + 3 =`` **7** (+3 is from separator,
unit and prefix, assuming all of them have 1-char width).
"""

PRESET_SI_BINARY = PrefixedUnitPreset(
Expand All @@ -64,6 +75,9 @@ class PrefixedUnitPreset:
So, in this case``max_value_len`` must be at least **5** (not 4),
because it's a minimum requirement for displaying values from `1023`
to `-1023`.
Total maximum length is ``max_value_len + 3 =`` **8** (+3 is from separator,
unit and prefix, assuming all of them have 1-char width).
"""


Expand Down Expand Up @@ -110,5 +124,5 @@ def format_prefixed_unit(value: float, preset: PrefixedUnitPreset = None) -> str

# no more prefixes left
return f'{value!r:{preset.max_value_len}.{preset.max_value_len}}{preset.unit_separator or ""}' + \
'?' * max([len(p) for p in prefixes]) + \
'?' * max([len(p) for p in prefixes if p]) + \
(preset.unit or "")
20 changes: 10 additions & 10 deletions tests/numf/test_auto_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@

class TestAutoFloat(unittest.TestCase):
expected_format_dataset = [
['0.0', [0, 3]],
['6.0', [6, 3]],
['146', [145.66, 3]],
['300.0', [300, 5]],
['30.00', [30, 5]],
[' 3.00', [3, 5]],
[' 0.30', [.3, 5]],
[' 0.03', [.03, 5]],
[' 0.00', [.003, 5]],
['3.000', [3, 5]],
['0.300', [.3, 5]],
['0.030', [.03, 5]],
['0.003', [.003, 5]],
['-5.00', [-5, 5]],
[' -512', [-512, 5]],
[' 1.20', [1.2, 6]],
['1.2000', [1.2, 6]],
['123456', [123456, 6]],
[' 0.00', [0.00012, 6]],
[' 0.01', [0.012, 6]],
['0.0', [0, 3]],
['6.0', [6, 3]],
['146', [145.66, 3]],
['0.0001', [0.00012, 6]],
['0.0120', [0.012, 6]],
]

def test_output_has_expected_format(self):
Expand Down
24 changes: 14 additions & 10 deletions tests/numf/test_prefixed_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ class TestPrefixedUnit(unittest.TestCase):
['1 b', 1], ['10 b', 10],
['43 b', 43], ['180 b', 180],
['631 b', 631], ['1010 b', 1010],
['1.00 kb', 1024], ['1.05 kb', 1080],
['6.08 kb', 6230], ['14.65 kb', 15000],
['1.000 kb', 1024], ['1.055 kb', 1080],
['6.084 kb', 6230], ['14.65 kb', 15000],
['44.14 kb', 45200], ['130.2 kb', 133300],
['1.20 Mb', 1257800], ['41.11 Mb', 43106100],
['668.0 Mb', 700500000], ['2.33 Gb', 2501234567],
['1.200 Mb', 1257800], ['41.11 Mb', 43106100],
['668.0 Mb', 700500000], ['2.329 Gb', 2501234567],
['13.53 Gb', 14530231500], ['142.4 Tb', 156530231500223],
]], [PRESET_SI_METRIC, [
['1.23 m', 1.23456789],
Expand Down Expand Up @@ -59,16 +59,16 @@ def test_output_has_expected_format(self):
""" ----------------------------------------------------------------------------------------------------------- """

req_len_dataset = [
[8, PRESET_SI_METRIC],
[7, PRESET_SI_METRIC],
[8, PRESET_SI_BINARY],
[5, PrefixedUnitPreset(
max_value_len=3, integer_input=True, mcoef=1000.0,
max_value_len=4, integer_input=False, mcoef=1000.0,
prefixes=PRESET_SI_METRIC.prefixes,
prefix_zero_idx=PRESET_SI_METRIC.prefix_zero_idx,
unit=None, unit_separator=None,
)],
[6, PrefixedUnitPreset(
max_value_len=4, integer_input=True, mcoef=1000.0,
[10, PrefixedUnitPreset(
max_value_len=9, integer_input=False, mcoef=1000.0,
prefixes=PRESET_SI_METRIC.prefixes,
prefix_zero_idx=PRESET_SI_METRIC.prefix_zero_idx,
unit=None, unit_separator=None,
Expand All @@ -81,14 +81,18 @@ def test_output_fits_in_required_length(self):
subtest_count = 0

for preset_idx, (expected_max_len, preset) in enumerate(self.req_len_dataset):
verb_print_header(f'expected_max_len={expected_max_len:d}: ')
verb_print_header(f'expected_max_len={expected_max_len}: ')
self.assertEqual(expected_max_len,
preset.max_len,
f'Expected max len {expected_max_len} doesn\'t correspond to preset property ({preset.max_len})'
)

for input_idx, input_num in enumerate(self.req_len_input_num_list):
subtest_msg = f'prefixed/len P{preset_idx} #{input_idx} "{input_num:.2e}" -> (len {expected_max_len})'

with self.subTest(msg=subtest_msg):
actual_output = format_prefixed_unit(input_num, preset)
verb_print_info(subtest_msg + f' => (len {actual_output}) "{actual_output}"')
verb_print_info(subtest_msg + f' => (len {len(actual_output)}) "{actual_output}"')
subtest_count += 1
self.assertGreaterEqual(expected_max_len,
len(actual_output),
Expand Down

0 comments on commit f5e69fa

Please sign in to comment.