kisstdlib 0.0.7__tar.gz → 0.0.9__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.
Files changed (55) hide show
  1. kisstdlib-0.0.9/PKG-INFO +195 -0
  2. kisstdlib-0.0.9/README.md +176 -0
  3. kisstdlib-0.0.9/kisstdlib/__init__.py +125 -0
  4. kisstdlib-0.0.9/kisstdlib/argparse_ext.py +372 -0
  5. kisstdlib-0.0.9/kisstdlib/base.py +393 -0
  6. kisstdlib-0.0.7/kisstdlib/util.py → kisstdlib-0.0.9/kisstdlib/compression.py +23 -20
  7. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/kisstdlib/failure.py +7 -0
  8. kisstdlib-0.0.9/kisstdlib/fs.py +1067 -0
  9. kisstdlib-0.0.9/kisstdlib/getpass_ext.py +63 -0
  10. kisstdlib-0.0.9/kisstdlib/io/base.py +251 -0
  11. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/kisstdlib/io/encoder.py +3 -2
  12. kisstdlib-0.0.9/kisstdlib/io/stdio.py +128 -0
  13. kisstdlib-0.0.9/kisstdlib/io/wrapper.py +713 -0
  14. kisstdlib-0.0.9/kisstdlib/itertools_ext.py +197 -0
  15. kisstdlib-0.0.9/kisstdlib/logging_ext.py +341 -0
  16. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/kisstdlib/pyrepr.py +6 -4
  17. kisstdlib-0.0.9/kisstdlib/signal_ext.py +320 -0
  18. kisstdlib-0.0.9/kisstdlib/sorted.py +488 -0
  19. kisstdlib-0.0.9/kisstdlib/sqlite3_ext.py +552 -0
  20. kisstdlib-0.0.9/kisstdlib/string_ext.py +585 -0
  21. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/kisstdlib/time.py +17 -132
  22. kisstdlib-0.0.9/kisstdlib.egg-info/PKG-INFO +195 -0
  23. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/kisstdlib.egg-info/SOURCES.txt +16 -6
  24. kisstdlib-0.0.9/kisstdlib.egg-info/entry_points.txt +2 -0
  25. kisstdlib-0.0.9/kisstdlib_bin/describe_forest.py +179 -0
  26. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/pyproject.toml +8 -5
  27. kisstdlib-0.0.9/test/test_basics.py +202 -0
  28. kisstdlib-0.0.9/test/test_fs.py +343 -0
  29. kisstdlib-0.0.9/test/test_io.py +83 -0
  30. kisstdlib-0.0.9/test/test_sorted.py +153 -0
  31. kisstdlib-0.0.9/test/test_time.py +154 -0
  32. kisstdlib-0.0.7/PKG-INFO +0 -24
  33. kisstdlib-0.0.7/README.md +0 -5
  34. kisstdlib-0.0.7/kisstdlib/__init__.py +0 -21
  35. kisstdlib-0.0.7/kisstdlib/argparse/__init__.py +0 -25
  36. kisstdlib-0.0.7/kisstdlib/argparse/better.py +0 -230
  37. kisstdlib-0.0.7/kisstdlib/io/base.py +0 -142
  38. kisstdlib-0.0.7/kisstdlib/io/stdio.py +0 -32
  39. kisstdlib-0.0.7/kisstdlib/io/wrapper.py +0 -141
  40. kisstdlib-0.0.7/kisstdlib/logging.py +0 -50
  41. kisstdlib-0.0.7/kisstdlib/os.py +0 -529
  42. kisstdlib-0.0.7/kisstdlib/sorted.py +0 -215
  43. kisstdlib-0.0.7/kisstdlib.egg-info/PKG-INFO +0 -24
  44. kisstdlib-0.0.7/kisstdlib.egg-info/entry_points.txt +0 -2
  45. kisstdlib-0.0.7/kisstdlib_bin/describe_dir.py +0 -157
  46. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/LICENSE.txt +0 -0
  47. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/kisstdlib/io/__init__.py +0 -0
  48. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/kisstdlib/parsing.py +0 -0
  49. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/kisstdlib/py.typed +0 -0
  50. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/kisstdlib/tariter.py +0 -0
  51. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/kisstdlib.egg-info/dependency_links.txt +0 -0
  52. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/kisstdlib.egg-info/requires.txt +0 -0
  53. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/kisstdlib.egg-info/top_level.txt +0 -0
  54. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/setup.cfg +0 -0
  55. {kisstdlib-0.0.7 → kisstdlib-0.0.9}/setup.py +0 -0
@@ -0,0 +1,195 @@
1
+ Metadata-Version: 2.1
2
+ Name: kisstdlib
3
+ Version: 0.0.9
4
+ Summary: System programming with conceptually and algebraically simple primitives.
5
+ Author-email: Jan Malakhovski <oxij@oxij.org>
6
+ Project-URL: Homepage, https://oxij.org/software/kisstdlib/
7
+ Project-URL: GitHub, https://github.com/oxij/kisstdlib
8
+ Project-URL: Support Development, https://oxij.org/#support
9
+ Keywords: sys,stdlib
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: License :: OSI Approved :: Python Software Foundation License
14
+ Classifier: License :: OSI Approved :: BSD License
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.txt
18
+ Requires-Dist: sortedcontainers==2.4.*
19
+
20
+ # Table of Contents
21
+ <details><summary>(Click me to see it.)</summary>
22
+ <ul>
23
+ <li><a href="#what-is-kisstdlib" id="toc-what-is-kisstdlib">What is <code>kisstdlib</code>?</a></li>
24
+ <li><a href="#parts-and-pieces" id="toc-parts-and-pieces">Parts and pieces</a></li>
25
+ <li><a href="#usage" id="toc-usage">Usage</a>
26
+ <ul>
27
+ <li><a href="#describe-forest" id="toc-describe-forest">describe-forest</a></li>
28
+ </ul></li>
29
+ <li><a href="#development-.test-example.sh---help---wine" id="toc-development-.test-example.sh---help---wine">Development: <code>./test-example.sh [--help] [--wine]</code></a></li>
30
+ </ul>
31
+ </details>
32
+
33
+ # What is `kisstdlib`?
34
+
35
+ `kisstdlib` is a set of modules for the [Python](https://www.python.org/) programming language, designed mostly for system programming with a touch of everything else, that aims to enhance the standard experience while keeping everything it does conceptually and algebraically simple.
36
+
37
+ This library borrows heavily from the [Haskell](https://www.haskell.org/) programming language, where appropriate.
38
+ (The Python's bundled modules do that too in many places, but `kisstdlib` borrows more.)
39
+
40
+ In short, `kisstdlib` mostly implements useful extensions for Python's bundled modules, keeping the APIs backward compatible and naming things in the same style.
41
+ The only exception to this is, yet unpublished, `io.loop` module, which implements Free-Monad-based IO.
42
+
43
+ At the moment, `kisstdlib` is an alpha work in progress software with an unstable API.
44
+
45
+ # <span id="pieces"/>Parts and pieces
46
+
47
+ `kisstdlib` consists of:
48
+
49
+ - a bunch of Python modules under [./kisstdlib](./kisstdlib);
50
+
51
+ they are pretty well-documented there, though there's no generated `sphinx` docs yet;
52
+
53
+ - a bunch of human-readable [examples](./example) using those modules;
54
+
55
+ - some useful thin-wrapper programs over `kisstdlib` functions, useful for writing whole-program and/or fixed-output/extensional-equality tests;
56
+
57
+ the documentation of which gets rendered into the following "Usage" section;
58
+
59
+ - as well as some testing-related infrastructure built on top of this, in [./devscript](./devscript).
60
+
61
+ # Usage
62
+
63
+ ## describe-forest
64
+
65
+ Produce a plain-text recursive deterministic `find`/`ls`/`stat`-like description of given file and/or directory inputs.
66
+
67
+ The output format is designed to be descriptive and easily `diff`able while also producing minimally dissimilar outputs for similar inputs, even when those inputs contain lots of symlinks and/or hardlinks.
68
+ I.e., essentially, this is an alternative to `ls -lR` and/or `find . -exec ls -l {} \;` which generates outputs that change very little when files with multiple symlinks and/or hardlinks change.
69
+
70
+ This is most useful for testing code that produces filesystem trees.
71
+
72
+ The most verbose output format this program can produce, for a single input file
73
+
74
+ ```bash
75
+ describe-forest --full path/to/README.md
76
+ ```
77
+
78
+ looks as follows:
79
+
80
+ ```
81
+ . reg mode 644 mtime [2025-01-01 00:01:00] size 4096 sha256 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
82
+ ```
83
+
84
+ Note how both the path to and the name of the file do not appear in the output.
85
+ This is what you would want for doing things like
86
+
87
+ ```bash
88
+ if ! diff -U 0 <(describe-forest --full v1/path/to/README.md) <(describe-forest --full v2/path/to/README.md) ; then
89
+ echo "output changed between versions!" >&2
90
+ exit 1
91
+ fi
92
+ ```
93
+
94
+ which this program is designed for.
95
+
96
+ For a single input directory
97
+
98
+ ```bash
99
+ describe-forest --full path/to/dir
100
+ ```
101
+
102
+ the output looks similar to this:
103
+
104
+ ```
105
+ . dir mode 700 mtime [2025-01-01 00:00:00]
106
+ afile.jpg reg mode 600 mtime [2025-01-01 00:01:00] size 4096 sha256 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
107
+ sub dir mode 700 mtime [2025-01-01 00:03:00]
108
+ sub/afile-hardlink.jpg ref ==> afile.jpg
109
+ sub/afile-symlink.jpg sym mode 777 mtime [2025-01-01 00:59:59] -> ../afile.jpg
110
+ sub/zfile-hardlink.jpg reg mode 600 mtime [2025-01-01 00:02:00] size 256 sha256 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
111
+ unix-socket ??? mode 600 mtime [2025-01-01 01:00:00] size 0
112
+ zfile.jpg ref ==> sub/zfile-hardlink.jpg
113
+ ```
114
+
115
+ Hardlinks, which are denoted by `ref`s above, are processed as follows:
116
+
117
+ - each new file encountered in lexicographic walk is rendered fully,
118
+ - files with repeated dev+inode numbers are rendered by emitting `ref ==> ` followed by the full path (or `ref => ` followed by the relative path, with `--relative-hardlink`) to the previously encountered element.
119
+
120
+ This way, renaming a file in the input changes at most two lines.
121
+
122
+ Symlinks are rendered by simply emitting the path they store, unless `--follow-symlinks` is given, in which case the targets they point to get rendered instead.
123
+
124
+ Multiple inputs get named by numbering them starting from "0".
125
+ Thus, for instance, running this program with the same input file given twice
126
+
127
+ ```bash
128
+ describe-forest --full path/to/README.md path/to/README.md
129
+ ```
130
+
131
+ produces something like:
132
+
133
+ ```
134
+ 0 reg mode 600 mtime [2025-01-01 00:01:00] size 4096 sha256 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
135
+ 1 ref ==> 0
136
+ ```
137
+
138
+ And giving the same directory with that file inside twice produces:
139
+
140
+ ```
141
+ 0 dir mode 700 mtime [2025-01-01 00:00:00]
142
+ 0/afile.jpg reg mode 600 mtime [2025-01-01 00:01:00] size 4096 sha256 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
143
+ 1 dir mode 700 mtime [2025-01-01 00:00:00]
144
+ 1/afile.jpg ref ==> 0/afile.jpg
145
+ ```
146
+
147
+ In its default output format, though, the program emits only `size`s and `sha256`s, when appropriate:
148
+
149
+ ```
150
+ . dir
151
+ afile.jpg reg size 4096 sha256 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
152
+ ```
153
+
154
+ which is what you would usually want for writing tests.
155
+ Though, if you are testing `rsync` or some such, feel free to use other options described below.
156
+
157
+ See `devscript` directory in `kisstdlib`'s repository for examples of some shell machinery that uses this to implement arbitrary-program fixed-output tests, which is a nice and simple way to test programs by testing their outputs against outputs of different versions of themselves.
158
+
159
+ Also, internally, this programs is actually a thin wrapper over `describe_forest` function of `kisstdlib.fs` Python module, which can be used with `pytest` or some such.
160
+
161
+ - positional arguments:
162
+ - `PATH`
163
+ : input directories
164
+
165
+ - options:
166
+ - `--version`
167
+ : show program's version number and exit
168
+ - `-h, --help`
169
+ : show this help message and exit
170
+ - `--markdown`
171
+ : show `--help` formatted in Markdown
172
+ - `--numbers`
173
+ : emit number prefixes even with a single input `PATH`
174
+ - `--literal`
175
+ : emit paths without escaping them even when they contain special symbols
176
+ - `--modes`
177
+ : emit file modes
178
+ - `--mtimes`
179
+ : emit file mtimes
180
+ - `--no-sizes`
181
+ : do not emit file sizes
182
+ - `--full`
183
+ : an alias for `--mtimes --modes`
184
+ - `--relative, --relative-hardlinks`
185
+ : emit relative paths when emitting `ref`s
186
+ - `-L, --dereference, --follow-symlinks`
187
+ : follow all symbolic links; replaces all `sym` elements of the output with description of symlink targets
188
+ - `--time-precision INT`
189
+ : time precision (as a negative power of 10); default: `0`, which means seconds, set to `9` for nanosecond precision
190
+ - `--hash-length INT`
191
+ : cut hashes by taking their prefixes of this many characters; default: print them whole
192
+
193
+ # Development: `./test-example.sh [--help] [--wine]`
194
+
195
+ Check that all `kisstdlib/example/*.py` are fixed-output.
@@ -0,0 +1,176 @@
1
+ # Table of Contents
2
+ <details><summary>(Click me to see it.)</summary>
3
+ <ul>
4
+ <li><a href="#what-is-kisstdlib" id="toc-what-is-kisstdlib">What is <code>kisstdlib</code>?</a></li>
5
+ <li><a href="#parts-and-pieces" id="toc-parts-and-pieces">Parts and pieces</a></li>
6
+ <li><a href="#usage" id="toc-usage">Usage</a>
7
+ <ul>
8
+ <li><a href="#describe-forest" id="toc-describe-forest">describe-forest</a></li>
9
+ </ul></li>
10
+ <li><a href="#development-.test-example.sh---help---wine" id="toc-development-.test-example.sh---help---wine">Development: <code>./test-example.sh [--help] [--wine]</code></a></li>
11
+ </ul>
12
+ </details>
13
+
14
+ # What is `kisstdlib`?
15
+
16
+ `kisstdlib` is a set of modules for the [Python](https://www.python.org/) programming language, designed mostly for system programming with a touch of everything else, that aims to enhance the standard experience while keeping everything it does conceptually and algebraically simple.
17
+
18
+ This library borrows heavily from the [Haskell](https://www.haskell.org/) programming language, where appropriate.
19
+ (The Python's bundled modules do that too in many places, but `kisstdlib` borrows more.)
20
+
21
+ In short, `kisstdlib` mostly implements useful extensions for Python's bundled modules, keeping the APIs backward compatible and naming things in the same style.
22
+ The only exception to this is, yet unpublished, `io.loop` module, which implements Free-Monad-based IO.
23
+
24
+ At the moment, `kisstdlib` is an alpha work in progress software with an unstable API.
25
+
26
+ # <span id="pieces"/>Parts and pieces
27
+
28
+ `kisstdlib` consists of:
29
+
30
+ - a bunch of Python modules under [./kisstdlib](./kisstdlib);
31
+
32
+ they are pretty well-documented there, though there's no generated `sphinx` docs yet;
33
+
34
+ - a bunch of human-readable [examples](./example) using those modules;
35
+
36
+ - some useful thin-wrapper programs over `kisstdlib` functions, useful for writing whole-program and/or fixed-output/extensional-equality tests;
37
+
38
+ the documentation of which gets rendered into the following "Usage" section;
39
+
40
+ - as well as some testing-related infrastructure built on top of this, in [./devscript](./devscript).
41
+
42
+ # Usage
43
+
44
+ ## describe-forest
45
+
46
+ Produce a plain-text recursive deterministic `find`/`ls`/`stat`-like description of given file and/or directory inputs.
47
+
48
+ The output format is designed to be descriptive and easily `diff`able while also producing minimally dissimilar outputs for similar inputs, even when those inputs contain lots of symlinks and/or hardlinks.
49
+ I.e., essentially, this is an alternative to `ls -lR` and/or `find . -exec ls -l {} \;` which generates outputs that change very little when files with multiple symlinks and/or hardlinks change.
50
+
51
+ This is most useful for testing code that produces filesystem trees.
52
+
53
+ The most verbose output format this program can produce, for a single input file
54
+
55
+ ```bash
56
+ describe-forest --full path/to/README.md
57
+ ```
58
+
59
+ looks as follows:
60
+
61
+ ```
62
+ . reg mode 644 mtime [2025-01-01 00:01:00] size 4096 sha256 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
63
+ ```
64
+
65
+ Note how both the path to and the name of the file do not appear in the output.
66
+ This is what you would want for doing things like
67
+
68
+ ```bash
69
+ if ! diff -U 0 <(describe-forest --full v1/path/to/README.md) <(describe-forest --full v2/path/to/README.md) ; then
70
+ echo "output changed between versions!" >&2
71
+ exit 1
72
+ fi
73
+ ```
74
+
75
+ which this program is designed for.
76
+
77
+ For a single input directory
78
+
79
+ ```bash
80
+ describe-forest --full path/to/dir
81
+ ```
82
+
83
+ the output looks similar to this:
84
+
85
+ ```
86
+ . dir mode 700 mtime [2025-01-01 00:00:00]
87
+ afile.jpg reg mode 600 mtime [2025-01-01 00:01:00] size 4096 sha256 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
88
+ sub dir mode 700 mtime [2025-01-01 00:03:00]
89
+ sub/afile-hardlink.jpg ref ==> afile.jpg
90
+ sub/afile-symlink.jpg sym mode 777 mtime [2025-01-01 00:59:59] -> ../afile.jpg
91
+ sub/zfile-hardlink.jpg reg mode 600 mtime [2025-01-01 00:02:00] size 256 sha256 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
92
+ unix-socket ??? mode 600 mtime [2025-01-01 01:00:00] size 0
93
+ zfile.jpg ref ==> sub/zfile-hardlink.jpg
94
+ ```
95
+
96
+ Hardlinks, which are denoted by `ref`s above, are processed as follows:
97
+
98
+ - each new file encountered in lexicographic walk is rendered fully,
99
+ - files with repeated dev+inode numbers are rendered by emitting `ref ==> ` followed by the full path (or `ref => ` followed by the relative path, with `--relative-hardlink`) to the previously encountered element.
100
+
101
+ This way, renaming a file in the input changes at most two lines.
102
+
103
+ Symlinks are rendered by simply emitting the path they store, unless `--follow-symlinks` is given, in which case the targets they point to get rendered instead.
104
+
105
+ Multiple inputs get named by numbering them starting from "0".
106
+ Thus, for instance, running this program with the same input file given twice
107
+
108
+ ```bash
109
+ describe-forest --full path/to/README.md path/to/README.md
110
+ ```
111
+
112
+ produces something like:
113
+
114
+ ```
115
+ 0 reg mode 600 mtime [2025-01-01 00:01:00] size 4096 sha256 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
116
+ 1 ref ==> 0
117
+ ```
118
+
119
+ And giving the same directory with that file inside twice produces:
120
+
121
+ ```
122
+ 0 dir mode 700 mtime [2025-01-01 00:00:00]
123
+ 0/afile.jpg reg mode 600 mtime [2025-01-01 00:01:00] size 4096 sha256 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
124
+ 1 dir mode 700 mtime [2025-01-01 00:00:00]
125
+ 1/afile.jpg ref ==> 0/afile.jpg
126
+ ```
127
+
128
+ In its default output format, though, the program emits only `size`s and `sha256`s, when appropriate:
129
+
130
+ ```
131
+ . dir
132
+ afile.jpg reg size 4096 sha256 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
133
+ ```
134
+
135
+ which is what you would usually want for writing tests.
136
+ Though, if you are testing `rsync` or some such, feel free to use other options described below.
137
+
138
+ See `devscript` directory in `kisstdlib`'s repository for examples of some shell machinery that uses this to implement arbitrary-program fixed-output tests, which is a nice and simple way to test programs by testing their outputs against outputs of different versions of themselves.
139
+
140
+ Also, internally, this programs is actually a thin wrapper over `describe_forest` function of `kisstdlib.fs` Python module, which can be used with `pytest` or some such.
141
+
142
+ - positional arguments:
143
+ - `PATH`
144
+ : input directories
145
+
146
+ - options:
147
+ - `--version`
148
+ : show program's version number and exit
149
+ - `-h, --help`
150
+ : show this help message and exit
151
+ - `--markdown`
152
+ : show `--help` formatted in Markdown
153
+ - `--numbers`
154
+ : emit number prefixes even with a single input `PATH`
155
+ - `--literal`
156
+ : emit paths without escaping them even when they contain special symbols
157
+ - `--modes`
158
+ : emit file modes
159
+ - `--mtimes`
160
+ : emit file mtimes
161
+ - `--no-sizes`
162
+ : do not emit file sizes
163
+ - `--full`
164
+ : an alias for `--mtimes --modes`
165
+ - `--relative, --relative-hardlinks`
166
+ : emit relative paths when emitting `ref`s
167
+ - `-L, --dereference, --follow-symlinks`
168
+ : follow all symbolic links; replaces all `sym` elements of the output with description of symlink targets
169
+ - `--time-precision INT`
170
+ : time precision (as a negative power of 10); default: `0`, which means seconds, set to `9` for nanosecond precision
171
+ - `--hash-length INT`
172
+ : cut hashes by taking their prefixes of this many characters; default: print them whole
173
+
174
+ # Development: `./test-example.sh [--help] [--wine]`
175
+
176
+ Check that all `kisstdlib/example/*.py` are fixed-output.
@@ -0,0 +1,125 @@
1
+ # Copyright (c) 2018-2025 Jan Malakhovski <oxij@oxij.org>
2
+ #
3
+ # This file is a part of `kisstdlib` project.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ """Re-exports allowing to simply use `from kisstdlib import *` in most simple programs."""
24
+
25
+ import os.path as _op
26
+ import sys as _sys
27
+ import typing as _t
28
+
29
+ from gettext import gettext, ngettext
30
+
31
+ from .base import *
32
+ from .failure import *
33
+ from .string_ext import *
34
+ from .itertools_ext import *
35
+ from .io.stdio import *
36
+ from .logging_ext import *
37
+ from .signal_ext import *
38
+
39
+
40
+ def setup_kisstdlib(
41
+ prog: str | None = None,
42
+ do_setup_stdio: bool = True,
43
+ signals: list[str] | None = None,
44
+ **kwargs: _t.Any,
45
+ ) -> tuple[LogCounter, ANSILogHandler]:
46
+ """Run all `setup_*` functions of all other `kisstdlib.*` modules and setup
47
+ everything they configure to play well with each other.
48
+ """
49
+ if prog is None:
50
+ prog = _op.basename(_sys.argv[0])
51
+ if do_setup_stdio:
52
+ setup_stdio()
53
+ res = setup_logging(prog, **kwargs)
54
+ setup_delay_signal_messages(prog + ": ")
55
+ setup_delay_signals(signals)
56
+ return res
57
+
58
+
59
+ def run_kisstdlib_main(
60
+ setup_logging_result: tuple[LogCounter, ANSILogHandler],
61
+ func: _t.Callable[AParamSpec, None],
62
+ /,
63
+ *args: AParamSpec.args,
64
+ **kwargs: AParamSpec.kwargs,
65
+ ) -> None:
66
+ """Run given function with given arguments under `with no_signals()` block while
67
+ properly handling all exceptions in `ExceptionGroup`s,
68
+ `CatastrophicFailure`s, `Exception`s, etc it raises by turning them into
69
+ `logging.error`s. Then, flush `stdio`, report warning and error counts, and
70
+ call `sys.exit` with a non-zero exit code if the function itself
71
+ `sys.exit`ed with a non-zero code, or if it `raise`d any exceptions, or
72
+ logged any errors via `logging`.
73
+
74
+ I.e., this functions abstracts away all the boilerplate code you need to
75
+ write to build a proper UNIXy CLI program.
76
+
77
+ `setup_logging_result` argument should be supplied from the result of
78
+ `setup_logging` or `setup_kisstdlib`.
79
+ """
80
+ counter, logger = setup_logging_result
81
+ exit_code: int = 0
82
+
83
+ try:
84
+ try:
85
+ with no_signals():
86
+ func(*args, **kwargs)
87
+ except SystemExit as exc:
88
+ code = exc.code
89
+ if code is not None:
90
+ if isinstance(code, str):
91
+ critical("%s", code)
92
+ else:
93
+ exit_code = code
94
+ except* (SignalInterrupt, KeyboardInterrupt):
95
+ critical(gettext("Interrupted!"))
96
+ except* CatastrophicFailure as excs:
97
+ ce: CatastrophicFailure
98
+ for ce in flat_exceptions(excs):
99
+ critical("%s", ce.get_message(gettext))
100
+ except* BaseException as excs:
101
+ be: BaseException
102
+ for be in flat_exceptions(excs):
103
+ critical(gettext("Uncaught exception: %s"), str(be), exc_info=be)
104
+
105
+ logger.flush()
106
+ stdout.flush()
107
+ stderr.flush()
108
+
109
+ num_warnings = counter.warnings
110
+ num_errors = counter.errors
111
+
112
+ if num_warnings > 0:
113
+ warning(
114
+ ngettext("There was %d warning!", "There were %d warnings!", num_warnings),
115
+ num_warnings,
116
+ )
117
+ if num_errors > 0:
118
+ error(
119
+ ngettext("There was %d error!", "There were %d errors!", num_errors),
120
+ num_errors,
121
+ )
122
+ if exit_code == 0:
123
+ exit_code = 1
124
+
125
+ _sys.exit(exit_code)