easyvista-python-client 0.4.0__tar.gz → 0.4.2__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 (77) hide show
  1. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/CHANGELOG.md +72 -1
  2. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/PKG-INFO +1 -1
  3. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/docs/content.rst +48 -21
  4. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/_version.py +1 -1
  5. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/content/conversion.py +63 -7
  6. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/pyproject.toml +1 -1
  7. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/skills/easyvista-asset-workflow/SKILL.md +1 -1
  8. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/skills/easyvista-client-setup/SKILL.md +1 -1
  9. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/skills/easyvista-directory/SKILL.md +1 -1
  10. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/skills/easyvista-document-workflow/SKILL.md +1 -1
  11. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/skills/easyvista-instance-discovery/SKILL.md +1 -1
  12. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/skills/easyvista-reporting-and-context/SKILL.md +1 -1
  13. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/skills/easyvista-search-syntax/SKILL.md +1 -1
  14. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/skills/easyvista-ticket-actions/SKILL.md +3 -2
  15. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/skills/easyvista-ticket-workflow/SKILL.md +1 -1
  16. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/.gitignore +0 -0
  17. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/.pre-commit-config.yaml +0 -0
  18. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/.readthedocs.yaml +0 -0
  19. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/CONTRIBUTING.md +0 -0
  20. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/LICENSE +0 -0
  21. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/README.md +0 -0
  22. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/docs/_static/.gitkeep +0 -0
  23. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/docs/_templates/.gitkeep +0 -0
  24. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/docs/api_reference.rst +0 -0
  25. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/docs/conf.py +0 -0
  26. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/docs/development.rst +0 -0
  27. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/docs/index.rst +0 -0
  28. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/docs/installation.rst +0 -0
  29. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/docs/publishing.rst +0 -0
  30. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/docs/sponsoring.rst +0 -0
  31. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/docs/user_guide.rst +0 -0
  32. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/docs/vendor-api-reference.md +0 -0
  33. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/__init__.py +0 -0
  34. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/_async/__init__.py +0 -0
  35. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/_async/_concurrency.py +0 -0
  36. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/_async/_transport.py +0 -0
  37. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/_async/client.py +0 -0
  38. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/_fields.py +0 -0
  39. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/_html.py +0 -0
  40. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/_sync/__init__.py +0 -0
  41. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/_sync/_concurrency.py +0 -0
  42. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/_sync/_transport.py +0 -0
  43. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/_sync/client.py +0 -0
  44. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/_transport.py +0 -0
  45. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/config.py +0 -0
  46. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/content/__init__.py +0 -0
  47. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/context.py +0 -0
  48. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/directory.py +0 -0
  49. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/discovery.py +0 -0
  50. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/exceptions.py +0 -0
  51. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/field_model.py +0 -0
  52. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/filters.py +0 -0
  53. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/models/__init__.py +0 -0
  54. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/models/action.py +0 -0
  55. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/models/asset.py +0 -0
  56. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/models/common.py +0 -0
  57. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/models/department.py +0 -0
  58. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/models/document.py +0 -0
  59. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/models/employee.py +0 -0
  60. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/models/generic.py +0 -0
  61. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/models/request.py +0 -0
  62. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/pagination.py +0 -0
  63. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/py.typed +0 -0
  64. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/references.py +0 -0
  65. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/reporting.py +0 -0
  66. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/resources/__init__.py +0 -0
  67. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/resources/actions.py +0 -0
  68. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/resources/assets.py +0 -0
  69. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/resources/departments.py +0 -0
  70. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/resources/descriptor.py +0 -0
  71. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/resources/discovery.py +0 -0
  72. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/resources/documents.py +0 -0
  73. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/resources/employees.py +0 -0
  74. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/resources/requests.py +0 -0
  75. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/timestamps.py +0 -0
  76. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/easyvista_python_client/workflow.py +0 -0
  77. {easyvista_python_client-0.4.0 → easyvista_python_client-0.4.2}/skills/README.md +0 -0
@@ -15,6 +15,75 @@ is the error. Tags carry no `v` prefix.
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [0.4.2] - 2026-10-07
19
+
20
+ A patch release of the content writer: a link written into a memo opens in a
21
+ new window. Reading is unchanged, and so is everything outside
22
+ `easyvista_python_client.content`.
23
+
24
+ ### Changed
25
+
26
+ - **Every link `to_transport` writes carries `target="_blank"
27
+ rel="noopener noreferrer"`**, as a link written in EasyVista's own editor
28
+ does. Without it, EasyVista's memo view opens the link in place, inside its
29
+ own window, so a link to another application seemed to do nothing (measured
30
+ on a live memo, 2026-10-07: links with and without the attributes, side by
31
+ side). Reading ignores both attributes, so `from_transport(to_transport(m))`
32
+ is unchanged for every link.
33
+
34
+ ## [0.4.1] - 2026-10-02
35
+
36
+ A patch release of the content reader. A `<u>`, `<mark>` or `<ins>` round a
37
+ block no longer reads as broken Markdown, four guards of 0.4.0's fixes gain
38
+ the tests they lacked, and `docs/content.rst` corrects what it said about
39
+ `start` values, `ValueError`, the CVE-2025-6069 tail and a second round
40
+ trip. Writing is unchanged, and so is everything outside
41
+ `easyvista_python_client.content`. `glpi_python_client`'s port of this
42
+ converter makes the same correction.
43
+
44
+ ### Fixed
45
+
46
+ - **A block inside `<u>`, `<mark>` or `<ins>` read as broken Markdown**, since
47
+ 0.4.0 kept these tags raw (fix 11). Markdown has no inline tag round blocks:
48
+ a table inside one read as pipe text, a list, a heading, a quote or a rule
49
+ as its Markdown source, paragraphs were not a fixed point, and
50
+ `<u><pre>code</pre></u>` left a fence open that showed the rest of the memo
51
+ as code. Round a block the tag is now dropped and the blocks kept, as
52
+ `glpi_python_client`'s reader at `917f030` read them. Inside a table cell
53
+ or a heading, whose blocks are one line, the tag stays, and around inline
54
+ content nothing changes. The Markdown read from such a memo changes once.
55
+
56
+ ### Documentation
57
+
58
+ - `docs/content.rst`, corrected after a review of the converter:
59
+ - a `start` that is not a decimal number counts from 1 as a browser
60
+ counts one holding no digit; a browser reads `" 3"`, `"+3"` or
61
+ `"3abc"` as 3, which the converter counts from 1;
62
+ - any `ValueError` raised while converting degrades the memo to text, not
63
+ only an unreadable `colspan` or `start` (the module and
64
+ `from_transport` docstrings say so too);
65
+ - the CVE-2025-6069 note says that everything after a memo's last `>`
66
+ reads as text, an unterminated comment and a closing tag cut short
67
+ inside a link included, where a patched CPython drops some of it;
68
+ - a second write-and-read cycle changes nothing more except for two
69
+ shapes, adjacent lists with different bullets and a fence whose info
70
+ string holds a character reference, where 0.4.0's page and its
71
+ changelog said it changes nothing;
72
+ - `<u>`, `<mark>` and `<ins>` round a block, and a known hole for
73
+ `<b>`, `<em>` and `<s>` round blocks.
74
+ - The `easyvista-ticket-actions` skill says a `<u>` round a block is
75
+ dropped.
76
+
77
+ ### Notes
78
+
79
+ - Four guards of the 0.4.0 fixes that no test caught are pinned, each by a
80
+ test that fails without it: a second `<` before a space keeps one escape
81
+ (fix 5), a header cell escapes a `|` in `<kbd>` or `<samp>` and splits a
82
+ line break in inline code (fix 6), a table inside an `<a>` without `href`
83
+ stays a table (fix 7), and an ordered item numbered 10 or more indents its
84
+ content by its bullet's width (fix 12). A count pins that the walk finding
85
+ blocks inside `<u>`, `<mark>` and `<ins>` checks each tag about once.
86
+
18
87
  ## [0.4.0] - 2026-10-02
19
88
 
20
89
  Adds Markdown <-> memo HTML conversion as an optional extra, drops Python
@@ -1592,7 +1661,9 @@ Initial public release.
1592
1661
  status/error code, with non-retryable validation errors (HTTP 590, code 2013).
1593
1662
  - `py.typed` marker — the package ships inline type information.
1594
1663
 
1595
- [Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.4.0...HEAD
1664
+ [Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.4.2...HEAD
1665
+ [0.4.2]: https://github.com/baraline/easyvista_python_client/compare/0.4.1...0.4.2
1666
+ [0.4.1]: https://github.com/baraline/easyvista_python_client/compare/0.4.0...0.4.1
1596
1667
  [0.4.0]: https://github.com/baraline/easyvista_python_client/compare/0.3.0...0.4.0
1597
1668
  [0.3.0]: https://github.com/baraline/easyvista_python_client/compare/0.2.0...0.3.0
1598
1669
  [0.2.0]: https://github.com/baraline/easyvista_python_client/compare/0.1.0...0.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: easyvista-python-client
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Typed Python client for the EasyVista Service Manager REST API
5
5
  Project-URL: Homepage, https://github.com/baraline/easyvista_python_client
6
6
  Project-URL: Documentation, https://easyvista-python-client.readthedocs.io/en/latest/
@@ -141,15 +141,21 @@ What each kind of element becomes:
141
141
  stay as those raw tags, and **struck text** (``<s>``, ``<del>``,
142
142
  ``<strike>``) as a raw ``<s>``. CommonMark has no spelling for any of them,
143
143
  and ``to_transport`` passes the tags through, so the formatting survives.
144
- The cost is raw HTML in the Markdown.
144
+ The cost is raw HTML in the Markdown. Markdown has no inline tag round
145
+ blocks, so a ``<u>``, ``<mark>`` or ``<ins>`` holding a table, a list, a
146
+ heading, a quote, a ``<pre>``, a rule or paragraphs is dropped and its
147
+ blocks kept; inside a table cell or a heading, which hold one line, it
148
+ stays.
145
149
  * **Links** become ``[text](https://... "title")``, and a link whose text is
146
150
  its own URL, a pasted link, becomes the autolink ``<https://...>``. No link
147
151
  target is filtered, ``javascript:`` included (see `It is not a sanitiser`_).
148
- * **Lists** nest and keep their numbers, including an ``<ol start>``.
149
- **Tables** become GFM tables, and a ``<br>`` inside a cell stays a raw
150
- ``<br>``, since a GFM cell is one line. **Preformatted blocks** become fences
151
- that keep the ``language-`` class ``cmark-gfm`` writes, with a fence longer
152
- than any run of backticks in the code.
152
+ * **Lists** nest and keep their numbers, including an ``<ol start>``. A
153
+ ``start`` that is not a decimal number counts from 1, as a browser counts
154
+ one holding no digit, such as ``²``; a browser reads ``" 3"``, ``"+3"`` or
155
+ ``"3abc"`` as 3. **Tables** become GFM tables, and a ``<br>`` inside a cell
156
+ stays a raw ``<br>``, since a GFM cell is one line. **Preformatted blocks**
157
+ become fences that keep the ``language-`` class ``cmark-gfm`` writes, with a
158
+ fence longer than any run of backticks in the code.
153
159
  * ``<head>``, ``<script>``, ``<style>``, ``<template>`` and ``<title>`` are
154
160
  dropped, as a browser does not display them. Styling such as ``<font>``
155
161
  colours or ``<span style>`` keeps its text and loses the style.
@@ -180,10 +186,12 @@ and 3.14.6 (measured 2026-10-02, default recursion limit). The converter does
180
186
  not predict that. It attempts the conversion and, if the walk does not fit,
181
187
  reads the memo as its text instead, a line per block. Every word the
182
188
  conversion would have produced is still there, in order. What is lost is
183
- structure: link targets, image alt text, emphasis and code fencing. A
184
- ``colspan`` or ``start`` attribute ``markdownify`` cannot read as a number,
185
- such as ``"²"``, takes the same path, and so does a document ``html.parser``
186
- refuses outright, such as one carrying an unknown ``<![FOO[`` marked section.
189
+ structure: link targets, image alt text, emphasis and code fencing. Any
190
+ ``ValueError`` from the conversion takes the same path -- ``markdownify``
191
+ raises one for a ``colspan`` or ``start`` attribute it cannot read as a
192
+ number, such as a ``colspan`` of ``"²"`` -- and so does a document
193
+ ``html.parser`` refuses outright, such as one carrying an unknown
194
+ ``<![FOO[`` marked section.
187
195
 
188
196
  Because the budget is whatever stack is left when the call starts, the same
189
197
  memo can convert from one call site and degrade from a deeper one. A caller
@@ -206,6 +214,11 @@ options, ``HARDBREAKS`` and ``UNSAFE``:
206
214
  * a fence becomes ``<pre><code>``, with the info string as a
207
215
  ``class="language-..."``;
208
216
  * a table becomes a ``<table>`` with a ``<thead>``;
217
+ * a link opens in a new window: each ``<a href>`` the renderer writes carries
218
+ ``target="_blank" rel="noopener noreferrer"``, as a link written in
219
+ EasyVista's editor does. Without it the memo view opens the link in place,
220
+ inside EasyVista's window (measured 2026-10-07). Reading ignores both
221
+ attributes, so a round trip is unchanged;
209
222
  * raw HTML, inline or as a block, is passed through as written;
210
223
  * ``""``, or only whitespace, stays ``""`` rather than becoming ``<p></p>``.
211
224
 
@@ -275,9 +288,13 @@ A few things do not come back:
275
288
  * a line holding only ``*`` is an empty list item in CommonMark, and reads
276
289
  back as nothing.
277
290
 
278
- A second cycle changes nothing more. That is the property a two-way sync
279
- relies on: once a text has made one trip, writing what was read back and
280
- reading it again gives exactly the same Markdown.
291
+ After that first cycle, a further one changes nothing more, with two
292
+ exceptions (reproduced 2026-10-02): two adjacent lists with different
293
+ bullets read as one loose list, then as one tight list; and a fence whose
294
+ info string holds a character reference, such as ``&amp;amp;``, loses one
295
+ level of it on each cycle. A two-way sync relies on the rest: once a text
296
+ has made one trip, writing what was read back and reading it again gives
297
+ the same Markdown.
281
298
 
282
299
  **A memo read, written back and read again.** This direction is held to more.
283
300
  The aim is that what the Markdown displays is what the memo displayed, and
@@ -332,7 +349,12 @@ in the preproduction sample:
332
349
  * a table whose ``<td>`` and ``<tr>`` are never closed folds into one cell,
333
350
  keeping its words;
334
351
  * a definition list (``<dl>``) reads as a ``term`` line and a
335
- ``: definition`` line, so its display gains the colon.
352
+ ``: definition`` line, so its display gains the colon;
353
+ * bold, italic or struck text (``<b>``, ``<em>``, ``<s>`` and their
354
+ synonyms) wrapped round blocks, other than a single paragraph, shows its
355
+ markers or its tag as text, and a list, a table or a heading inside it as
356
+ Markdown source; round a ``<pre>`` it leaves a fence open, so the rest of
357
+ the memo shows as code.
336
358
 
337
359
  Some shapes are not fixed points at the first read, and settle after one more
338
360
  cycle. None broke a fixed point in the preproduction sample. Adjacent lists
@@ -390,9 +412,12 @@ fixed in those releases according to their changelogs). Many unfinished tags
390
412
  after a memo's last ``>`` is the shape that reaches the converter, and a memo
391
413
  is outside data. So ``from_transport`` spells every ``<`` after the last ``>``
392
414
  as ``&lt;`` before parsing, since no ``<`` there can finish a tag. As a side
393
- effect, an unfinished tag at the very end, ``x <a``, reads as the text
394
- ``x \<a`` on every interpreter, whereas a patched CPython would drop it. Prefer
395
- a patched interpreter anyway: the guard covers the converter's input, and the
415
+ effect, whatever follows the memo's last ``>`` reads as text on every
416
+ interpreter, where a patched CPython drops some of it: an unfinished tag at
417
+ the very end, ``x <a``, reads as ``x \<a``; an unterminated comment,
418
+ ``<!-- note``, as ``\<!-- note``; and a closing tag cut short inside a link,
419
+ ``<a href="...">lien</a``, leaves ``\</a`` in the link's text. Prefer a
420
+ patched interpreter anyway: the guard covers the converter's input, and the
396
421
  CPython fix covers the parser itself.
397
422
 
398
423
  Where it comes from
@@ -414,12 +439,14 @@ proposed to ``glpi_python_client``. The fixes cover:
414
439
  * ``<br>`` inside inline code;
415
440
  * tables inside headings and links, and the spacing of flattened cells;
416
441
  * ``<center>``;
417
- * underline and highlight, as raw tags;
442
+ * underline and highlight, as raw tags, except round a block (corrected in
443
+ 0.4.1);
418
444
  * numbering a long ordered list in linear time;
419
445
  * a quadratic pattern on runs of spaces;
420
446
  * unreadable ``colspan`` and ``start`` values;
421
447
  * the CVE-2025-6069 tail.
422
448
 
423
- ``CHANGELOG.md`` lists them under 0.4.0. Apart from those fixes, the names and
424
- the error messages, the only difference from ``glpi_python_client`` is that the
425
- libraries are an optional extra here, not dependencies.
449
+ ``CHANGELOG.md`` lists them under 0.4.0, and the correction under 0.4.1.
450
+ Apart from those fixes, the names and the error messages, the only
451
+ difference from ``glpi_python_client`` is that the libraries are an optional
452
+ extra here, not dependencies.
@@ -5,4 +5,4 @@
5
5
  live only in ``__init__``.
6
6
  """
7
7
 
8
- __version__ = "0.4.0"
8
+ __version__ = "0.4.2"
@@ -26,9 +26,10 @@ page lists them too. The glue below covers what the three libraries leave
26
26
  out: plain text, line breaks a browser does not show, bold and italic
27
27
  CommonMark would not close, link targets, and an mdformat set up without
28
28
  its nesting cap and with its quadratic lookups made linear. A body nested
29
- too deeply for the stack is read as its text (:func:`_text_of`); anything
30
- else that fails raises
31
- :class:`~easyvista_python_client.EasyvistaContentError`.
29
+ too deeply for the stack, or on which the conversion raises ``ValueError``
30
+ -- markdownify does for a ``colspan`` or ``start`` it cannot read as a
31
+ number -- is read as its text (:func:`_text_of`); anything else that fails
32
+ raises :class:`~easyvista_python_client.EasyvistaContentError`.
32
33
 
33
34
  A memo with no HTML element in it is read as literal lines, a line break
34
35
  per line. How EasyVista's web UI displays such a memo is unverified: the
@@ -138,6 +139,11 @@ _EMPHASIS = frozenset({"b", "strong", "em", "i"})
138
139
  _BEFORE, _AFTER = "data-ev-before", "data-ev-after"
139
140
  _NUMBER = "data-ev-number" # an ordered item's number (_Converter.convert_li)
140
141
 
142
+ #: The elements kept as raw tags (:meth:`_Converter.convert_u`), and the
143
+ #: attribute marking one that holds a block (:func:`_note_blocks`).
144
+ _RAW_INLINE = frozenset({"u", "mark", "ins"})
145
+ _HOLDS_BLOCK = "data-ev-block"
146
+
141
147
  #: Text split into leading line breaks and spaces, content, trailing ones.
142
148
  #: The content ends on its last character that is neither, found greedily:
143
149
  #: a lazy ``.*?`` rescanned the run after it at every step, quadratic.
@@ -330,6 +336,33 @@ def _note_sides(root: Tag) -> None:
330
336
  element[_AFTER] = " "
331
337
 
332
338
 
339
+ def _note_blocks(root: Tag) -> None:
340
+ """Mark each ``<u>``, ``<mark>`` or ``<ins>`` that holds a block.
341
+
342
+ Markdown has no inline tag around blocks: such a tag wrapped round them
343
+ showed a table, a list or a heading as its Markdown source, and round a
344
+ ``<pre>`` left a fence open to the end of the body. One walk: a block
345
+ marks the open ones from the innermost out and stops at one already
346
+ marked, whose holders were marked with it, so each is marked once
347
+ however deeply they nest.
348
+ """
349
+
350
+ holders: list[Tag] = []
351
+ for node, entering in _walk(root):
352
+ if not isinstance(node, Tag):
353
+ continue
354
+ if node.name in _RAW_INLINE:
355
+ if entering:
356
+ holders.append(node)
357
+ else:
358
+ holders.pop()
359
+ elif entering and node.name in _BLOCKS:
360
+ for holder in reversed(holders):
361
+ if holder.has_attr(_HOLDS_BLOCK):
362
+ break
363
+ holder[_HOLDS_BLOCK] = ""
364
+
365
+
333
366
  def _punctuation(char: str) -> bool:
334
367
  """Return whether CommonMark counts ``char`` as punctuation."""
335
368
 
@@ -426,8 +459,14 @@ class _Converter(MarkdownConverter):
426
459
  convert_strike = convert_s
427
460
 
428
461
  def convert_u(self, el: Tag, text: str, parent_tags: set[str]) -> str:
429
- """Underlined or highlighted text: CommonMark has neither, so raw HTML."""
462
+ """Underlined or highlighted text: CommonMark has neither, so raw HTML.
430
463
 
464
+ Round a block the tag is dropped and the blocks kept, except on the
465
+ one line of a cell or a heading, where the blocks are inline text.
466
+ """
467
+
468
+ if el.has_attr(_HOLDS_BLOCK) and "_inline" not in parent_tags:
469
+ return text
431
470
  return self._markup(el, text, parent_tags, "", el.name)
432
471
 
433
472
  convert_mark = convert_ins = convert_u
@@ -705,15 +744,30 @@ def html_to_markdown(html: str) -> str:
705
744
  _flatten_nested_tables(soup)
706
745
  _drop_trailing_breaks(soup)
707
746
  _note_sides(soup)
747
+ _note_blocks(soup)
708
748
  return str(_FORMATTER.render(_CONVERTER.convert_soup(soup))).strip()
709
749
 
710
750
 
751
+ #: A link as cmark-gfm writes one: an ``href``, an optional ``title``, nothing else.
752
+ _RENDERED_LINK = re.compile(r'<a href="[^"]*"(?: title="[^"]*")?>')
753
+
754
+ #: What EasyVista's own editor writes on a link: without it the memo view opens the
755
+ #: link in place, inside EasyVista's window, rather than in a new one.
756
+ _NEW_WINDOW = ' target="_blank" rel="noopener noreferrer"'
757
+
758
+
711
759
  def markdown_to_html(markdown: str) -> str:
712
- """Render Markdown as HTML: CommonMark with GFM tables, through cmark-gfm."""
760
+ """Render Markdown as HTML: CommonMark with GFM tables, through cmark-gfm.
761
+
762
+ Every link opens in a new window, the way a link written in EasyVista's
763
+ editor does: ``target="_blank" rel="noopener noreferrer"`` is added to each
764
+ link cmark-gfm writes. Reading ignores both attributes.
765
+ """
713
766
 
714
767
  html: str = cmarkgfm.markdown_to_html_with_extensions(
715
768
  markdown, options=_RENDER_OPTIONS, extensions=["table"]
716
769
  )
770
+ html = _RENDERED_LINK.sub(lambda link: link.group(0)[:-1] + _NEW_WINDOW + ">", html)
717
771
  return html.strip()
718
772
 
719
773
 
@@ -762,8 +816,10 @@ class EasyvistaContentConverter:
762
816
  ------
763
817
  EasyvistaContentError
764
818
  The value could not be converted. A body nested too deeply for
765
- the stack left is read as its text instead, so this is a
766
- backstop.
819
+ the stack left, or on which the conversion raises
820
+ ``ValueError`` (a ``colspan`` or ``start`` markdownify cannot
821
+ read as a number, among others), is read as its text instead,
822
+ so this is a backstop.
767
823
  RecursionError
768
824
  Only when called from within a few frames of the recursion
769
825
  limit, where no stack is left even to report the failure as
@@ -44,7 +44,7 @@ exclude = [
44
44
 
45
45
  [project]
46
46
  name = "easyvista-python-client"
47
- version = "0.4.0"
47
+ version = "0.4.2"
48
48
  description = "Typed Python client for the EasyVista Service Manager REST API"
49
49
  readme = "README.md"
50
50
  requires-python = ">=3.11"
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the assets resource."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.0"
8
+ version: "0.4.2"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and valid EasyVista credentials."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.0"
8
+ version: "0.4.2"
9
9
  ---
10
10
 
11
11
  `easyvista_python_client` ships both clients over one surface:
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the departments and employees resources (writes are additionally profile-gated)."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.0"
8
+ version: "0.4.2"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the documents sub-resource."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.0"
8
+ version: "0.4.2"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, and network access to an EasyVista Service Manager REST API. Every call here is a GET; nothing is created, updated or deleted."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.0"
8
+ version: "0.4.2"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, and network access to an EasyVista Service Manager REST API."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.0"
8
+ version: "0.4.2"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, and network access to an EasyVista Service Manager REST API."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.0"
8
+ version: "0.4.2"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the actions sub-resource."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.0"
8
+ version: "0.4.2"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,
@@ -618,7 +618,8 @@ with EasyvistaClient.from_env() as client:
618
618
  `init`. Reading spells a note's text as literal text (`__init__` comes back
619
619
  as `\_\_init\_\_`, a displayed `<b>` as `\<b>`), so render the Markdown
620
620
  rather than stripping its backslashes. Underline and strike come back as raw
621
- `<u>` and `<s>` tags, and a note with no HTML in it reads as literal lines.
621
+ `<u>` and `<s>` tags (a `<u>` round a block is dropped and the block kept),
622
+ and a note with no HTML in it reads as literal lines.
622
623
  It sanitises nothing: raw HTML, `javascript:` link targets and
623
624
  `<javascript:...>` autolinks go out live, and reading keeps a memo's
624
625
  `javascript:` links, so neutralise both in Markdown you did not write — a
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires Python 3.11+, easyvista-python-client, network access to an EasyVista Service Manager REST API, and a profile authorized for the requests resource."
6
6
  metadata:
7
7
  package: easyvista-python-client
8
- version: "0.4.0"
8
+ version: "0.4.2"
9
9
  ---
10
10
 
11
11
  > **Sync and async.** Examples use `EasyvistaClient`. For `AsyncEasyvistaClient`,