robotframework-libtoc 1.4.3__tar.gz → 1.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotframework-libtoc
3
- Version: 1.4.3
3
+ Version: 1.6.0
4
4
  Summary: Docs and TOC generator for Robot Framework resources and libs
5
5
  Home-page: https://github.com/amochin/robotframework-libtoc
6
6
  License: Apache-2.0
@@ -21,7 +21,7 @@ Description-Content-Type: text/markdown
21
21
  ## Robot Framework LibTOC
22
22
 
23
23
  ## What it does
24
- This tool generates docs using Robot Framework [Libdoc](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#libdoc) for an entire folder (or multiple folders) with Robot Framework resources/libs and creates a TOC (table of contents) file for them
24
+ This tool generates docs using Robot Framework [Libdoc](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#libdoc) for an entire folder (or multiple folders) with Robot Framework resources/libs and creates a TOC (table of contents) file for them.
25
25
 
26
26
  ## Why use it
27
27
  The Robot Framework Libdoc tool normally generates a HTML file for a single keyword library or a resource file.
@@ -84,11 +84,12 @@ pip install robotframework-libtoc
84
84
  - Create the `.libtoc` config files in the *root of the resources folder* and/or in *direct subfolders* where you need docs to be created.
85
85
  - Run `libtoc`. The last `resources_dirs` parameter is mandatory, it takes any number of paths. Other params are optional:
86
86
  - `-d, --output_dir`
87
+ - `-P, --pythonpath`
87
88
  - `--config_file`
88
89
  - `--toc_file`
89
90
  - `--toc_template`
90
91
  - `--homepage_template`
91
- - `-P, --pythonpath`
92
+ - `--no_timestamp`
92
93
 
93
94
  Examples:
94
95
  ```shell
@@ -111,4 +112,3 @@ There are two ways to extend the list of paths where the libraries are searched
111
112
  2. Set the **PYTHONPATH** environment variable
112
113
 
113
114
  See more in [Robot Framework User Guide](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#pythonpath).
114
-
@@ -1,7 +1,7 @@
1
1
  ## Robot Framework LibTOC
2
2
 
3
3
  ## What it does
4
- This tool generates docs using Robot Framework [Libdoc](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#libdoc) for an entire folder (or multiple folders) with Robot Framework resources/libs and creates a TOC (table of contents) file for them
4
+ This tool generates docs using Robot Framework [Libdoc](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#libdoc) for an entire folder (or multiple folders) with Robot Framework resources/libs and creates a TOC (table of contents) file for them.
5
5
 
6
6
  ## Why use it
7
7
  The Robot Framework Libdoc tool normally generates a HTML file for a single keyword library or a resource file.
@@ -64,11 +64,12 @@ pip install robotframework-libtoc
64
64
  - Create the `.libtoc` config files in the *root of the resources folder* and/or in *direct subfolders* where you need docs to be created.
65
65
  - Run `libtoc`. The last `resources_dirs` parameter is mandatory, it takes any number of paths. Other params are optional:
66
66
  - `-d, --output_dir`
67
+ - `-P, --pythonpath`
67
68
  - `--config_file`
68
69
  - `--toc_file`
69
70
  - `--toc_template`
70
71
  - `--homepage_template`
71
- - `-P, --pythonpath`
72
+ - `--no_timestamp`
72
73
 
73
74
  Examples:
74
75
  ```shell
@@ -90,4 +91,4 @@ There are two ways to extend the list of paths where the libraries are searched
90
91
  1. Using the `--pythonpath` option
91
92
  2. Set the **PYTHONPATH** environment variable
92
93
 
93
- See more in [Robot Framework User Guide](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#pythonpath).
94
+ See more in [Robot Framework User Guide](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#pythonpath).
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "robotframework-libtoc"
3
- version = "1.4.3"
3
+ version = "1.6.0"
4
4
  description = "Docs and TOC generator for Robot Framework resources and libs"
5
5
  authors = ["Andre Mochinin"]
6
6
  license = "Apache-2.0"
@@ -0,0 +1,31 @@
1
+ <html>
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <style>
5
+ :root { --bg: white; --text: #333; --text-muted: #666; --kbd-bg: #f3f3f3; --kbd-border: #e0e0e2; }
6
+ [data-theme=dark] { --bg: #1c2227; --text: #e2e1d7; --text-muted: #a8b2b8; --kbd-bg: #002b36; --kbd-border: #4e4e4e; color-scheme: dark; }
7
+ body { font-family: system-ui, -apple-system, sans-serif; color: var(--text); background: var(--bg); max-width: 640px; margin: 60px auto; padding: 0 24px; text-align: center; }
8
+ h1 { font-weight: 600; font-size: 24px; }
9
+ p { color: var(--text-muted); font-size: 15px; line-height: 1.6; }
10
+ kbd { background: var(--kbd-bg); border: 1px solid var(--kbd-border); border-radius: 4px; padding: 2px 7px; font-family: monospace; font-size: 12px; }
11
+ </style>
12
+ <script>!function(){var t=localStorage.getItem('libtoc-theme')||(window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light');document.documentElement.setAttribute('data-theme',t);document.addEventListener('keydown',function(e){if((e.ctrlKey||e.metaKey)&&e.key==='k'){e.preventDefault();window.parent.postMessage('libtoc-open-search','*')}})}()</script>
13
+ </head>
14
+ <body>
15
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
16
+ viewBox="0 0 202.4325 202.34125" height="120" width="120">
17
+ <g transform="matrix(1.25,0,0,-1.25,0,202.34125)">
18
+ <g>
19
+ <g clip-path="none">
20
+ <g transform="translate(52.4477,88.1268)">
21
+ <path fill="#00c0b5"
22
+ d="m 0,0 c 0,7.6 6.179,13.779 13.77,13.779 7.6,0 13.779,-6.179 13.779,-13.779 0,-2.769 -2.238,-5.007 -4.998,-5.007 -2.761,0 -4.999,2.238 -4.999,5.007 0,2.078 -1.695,3.765 -3.782,3.765 C 11.693,3.765 9.997,2.078 9.997,0 9.997,-2.769 7.76,-5.007 4.999,-5.007 2.238,-5.007 0,-2.769 0,0 m 57.05,-23.153 c 0,-2.771 -2.237,-5.007 -4.998,-5.007 l -46.378,0 c -2.761,0 -4.999,2.236 -4.999,5.007 0,2.769 2.238,5.007 4.999,5.007 l 46.378,0 c 2.761,0 4.998,-2.238 4.998,-5.007 M 35.379,-2.805 c -1.545,2.291 -0.941,5.398 1.35,6.943 l 11.594,7.83 c 2.273,1.58 5.398,0.941 6.943,-1.332 1.545,-2.29 0.941,-5.398 -1.35,-6.943 l -11.594,-7.83 c -0.852,-0.586 -1.829,-0.87 -2.788,-0.87 -1.607,0 -3.187,0.781 -4.155,2.202 m 31.748,-30.786 c 0,-0.945 -0.376,-1.852 -1.045,-2.522 l -8.617,-8.617 c -0.669,-0.668 -1.576,-1.045 -2.523,-1.045 l -52.833,0 c -0.947,0 -1.854,0.377 -2.523,1.045 l -8.617,8.617 c -0.669,0.67 -1.045,1.577 -1.045,2.522 l 0,52.799 c 0,0.947 0.376,1.854 1.045,2.522 l 8.617,8.619 c 0.669,0.668 1.576,1.044 2.523,1.044 l 52.833,0 c 0.947,0 1.854,-0.376 2.523,-1.044 l 8.617,-8.619 c 0.669,-0.668 1.045,-1.575 1.045,-2.522 l 0,-52.799 z m 7.334,61.086 -11.25,11.25 c -1.705,1.705 -4.018,2.663 -6.428,2.663 l -56.523,0 c -2.412,0 -4.725,-0.959 -6.43,-2.665 L -17.412,27.494 c -1.704,-1.705 -2.661,-4.016 -2.661,-6.427 l 0,-56.515 c 0,-2.411 0.958,-4.725 2.663,-6.428 l 11.25,-11.25 c 1.705,-1.705 4.017,-2.662 6.428,-2.662 l 56.515,0 c 2.41,0 4.723,0.957 6.428,2.662 l 11.25,11.25 c 1.705,1.703 2.663,4.017 2.663,6.428 l 0,56.514 c 0,2.412 -0.958,4.724 -2.663,6.429" />
23
+ </g>
24
+ </g>
25
+ </g>
26
+ </g>
27
+ </svg>
28
+ <h1>Keyword Documentation</h1>
29
+ <p>Select a library or resource from the sidebar, or press <kbd>Ctrl+K</kbd> to search.</p>
30
+ </body>
31
+ </html>
@@ -1,6 +1,8 @@
1
1
  import argparse
2
2
  import glob
3
+ import json
3
4
  import os
5
+ import re
4
6
  import shutil
5
7
  import sys
6
8
  from datetime import datetime
@@ -15,7 +17,7 @@ class LibdocException(Exception):
15
17
  self.broken_file = broken_file
16
18
 
17
19
 
18
- def toc(links, timestamp, home_page_path, template_file=""):
20
+ def toc(links, timestamp, home_page_path, template_file="", search_index=None):
19
21
  """
20
22
  Returns a HTML source code for TOC (table of contents) page, based on the template and including
21
23
  the provided `links`, generation `timestamp` and the `home_page_path` HTML file as a landing page.
@@ -25,27 +27,34 @@ def toc(links, timestamp, home_page_path, template_file=""):
25
27
  with open(template_file, encoding="utf8") as f:
26
28
  html_template = f.read()
27
29
 
28
- # double all brackets to make the further formatting work
29
- html_with_escaped_braces = html_template.replace("{", "{{")
30
- html_with_escaped_braces = html_with_escaped_braces.replace("}", "}}")
30
+ result = html_template.replace("{}", home_page_path, 1)
31
+ result = result.replace("{}", links, 1)
32
+ if timestamp:
33
+ result = result.replace("{}", timestamp, 1)
34
+ else:
35
+ result = result.replace("Created: {}", "", 1)
31
36
 
32
- # and convert the formatting brackets back
33
- html_with_escaped_braces = html_with_escaped_braces.replace("{{}}", "{}")
37
+ # inject search index data (done after format() to avoid brace escaping issues)
38
+ if search_index is not None:
39
+ search_json = json.dumps(search_index, ensure_ascii=False)
40
+ search_json = search_json.replace("</script>", r"<\/script>")
41
+ result = result.replace("SEARCH_INDEX_DATA", search_json)
42
+ else:
43
+ result = result.replace("SEARCH_INDEX_DATA", "[]")
34
44
 
35
- return html_with_escaped_braces.format(home_page_path, links, timestamp)
45
+ return result
36
46
 
37
47
 
38
- def homepage(timestamp, template_file=""):
48
+ def homepage(template_file=""):
39
49
  """
40
- Returns a HTML source code for a landing page, based on the template and includig the provided `timestamp`.
50
+ Returns a HTML source code for a landing page, based on the template.
41
51
  """
42
52
  if template_file == "":
43
53
  template_file = os.path.join(
44
54
  os.path.dirname(__file__), "homepage_template.html"
45
55
  )
46
56
  with open(template_file, encoding="utf_8") as f:
47
- html_template = f.read()
48
- return html_template.format(timestamp)
57
+ return f.read()
49
58
 
50
59
 
51
60
  def read_config(config_file):
@@ -96,6 +105,139 @@ def read_config(config_file):
96
105
  }
97
106
 
98
107
 
108
+ def extract_libdoc_data(html_file_path):
109
+ """
110
+ Extracts the libdoc JSON data from a generated libdoc HTML file.
111
+ The libdoc variable is embedded as a JSON object in a script tag.
112
+ """
113
+ with open(html_file_path, encoding="utf-8") as f:
114
+ content = f.read()
115
+ for line in content.split("\n"):
116
+ stripped = line.strip()
117
+ if stripped.startswith("libdoc") and '"specversion"' in stripped:
118
+ json_start = stripped.index("{")
119
+ json_str = stripped[json_start:]
120
+ try:
121
+ return json.loads(json_str)
122
+ except json.JSONDecodeError:
123
+ print(f"Warning: Failed to parse libdoc JSON (for global searches) in file {html_file_path}")
124
+ return None
125
+
126
+
127
+ def build_search_index(src_dir, base_dir, homepage_file):
128
+ """
129
+ Builds a search index from all libdoc HTML files in src_dir (except ``homepage_file``).
130
+ Returns a list of library/resource entries with their keywords.
131
+ """
132
+ index = []
133
+ for dirpath, dirnames, filenames in os.walk(src_dir):
134
+ dirnames.sort()
135
+ for file_name in sorted(filenames):
136
+ if file_name.endswith(".html") and file_name != homepage_file:
137
+ file_path = os.path.join(dirpath, file_name)
138
+ data = extract_libdoc_data(file_path)
139
+ if data:
140
+ rel_path = os.path.relpath(file_path, base_dir)
141
+ rel_path = rel_path.replace("\\", "/")
142
+ keywords = []
143
+ for kw in data.get("keywords", []):
144
+ args_list = []
145
+ for a in kw.get("args", []):
146
+ if isinstance(a, str):
147
+ args_list.append(a)
148
+ elif isinstance(a, dict):
149
+ args_list.append(
150
+ a.get("repr", a.get("name", ""))
151
+ )
152
+ keywords.append(
153
+ {
154
+ "name": kw.get("name", ""),
155
+ "args": ", ".join(args_list),
156
+ "shortdoc": kw.get("shortdoc", ""),
157
+ "tags": kw.get("tags", []),
158
+ }
159
+ )
160
+ doc_text = re.sub(r"<[^>]+>", "", data.get("doc", ""))
161
+ # file_name without .html extension for file searching
162
+ name_without_ext = os.path.splitext(file_name)[0]
163
+ index.append(
164
+ {
165
+ "name": data.get("name", ""),
166
+ "fileName": name_without_ext,
167
+ "path": rel_path,
168
+ "doc": doc_text,
169
+ "type": data.get("type", ""),
170
+ "keywords": keywords,
171
+ }
172
+ )
173
+ return index
174
+
175
+
176
+ def strip_libdoc_timestamps(src_dir, homepage_file):
177
+ """
178
+ Removes the 'generated' timestamp from the libdoc JSON data embedded in each
179
+ libdoc HTML file in src_dir (except ``homepage_file``), replacing it with an empty string.
180
+ """
181
+ for dirpath, dirnames, filenames in os.walk(src_dir):
182
+ dirnames.sort()
183
+ for file_name in sorted(filenames):
184
+ if file_name.endswith(".html") and file_name != homepage_file:
185
+ file_path = os.path.join(dirpath, file_name)
186
+ with open(file_path, "r", encoding="utf-8") as f:
187
+ content = f.read()
188
+ new_content = re.sub(
189
+ r'"generated":\s*"[^"]*"',
190
+ '"generated": ""',
191
+ content,
192
+ )
193
+ if new_content != content:
194
+ with open(file_path, "w", encoding="utf-8") as f:
195
+ f.write(new_content)
196
+
197
+
198
+ def inject_libtoc_script(src_dir, homepage_file):
199
+ """
200
+ Injects a small <script> into each libdoc HTML file in src_dir (except ``homepage_file``) that:
201
+ - Reads the theme from localStorage and applies data-theme on <html> so the
202
+ page respects the libtoc theme choice even when file:// cross-origin prevents
203
+ parent frame DOM access.
204
+ - Opens all external links (http/https/ftp/protocol-relative) in a new tab via
205
+ a click event listener, so dynamically-rendered links are handled correctly.
206
+ - Forwards Ctrl+K / Cmd+K keypresses to the parent frame to open the libtoc search.
207
+ """
208
+ libtoc_script = (
209
+ '\n<script>!function(){var t=localStorage.getItem("libtoc-theme")'
210
+ '||(window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light");'
211
+ 'document.documentElement.setAttribute("data-theme",t);'
212
+ 'document.addEventListener("DOMContentLoaded",function(){'
213
+ 'document.documentElement.setAttribute("data-theme",t)});'
214
+ 'document.addEventListener("click",function(e){'
215
+ 'var el=e.target.closest("a");'
216
+ 'if(el){var h=el.getAttribute("href");'
217
+ 'if(h&&/^(https?:|ftp:\\/\\/|\\/\\/)/i.test(h)){'
218
+ 'e.preventDefault();'
219
+ 'window.open(h,"_blank","noopener,noreferrer")}}});'
220
+ 'document.addEventListener("keydown",function(e){'
221
+ 'if((e.ctrlKey||e.metaKey)&&e.key==="k"){'
222
+ 'e.preventDefault();'
223
+ 'window.parent.postMessage("libtoc-open-search","*")}})'
224
+ '}()</script>\n'
225
+ )
226
+ for dirpath, dirnames, filenames in os.walk(src_dir):
227
+ dirnames.sort()
228
+ for file_name in sorted(filenames):
229
+ if file_name.endswith(".html") and file_name != homepage_file:
230
+ file_path = os.path.join(dirpath, file_name)
231
+ with open(file_path, "r", encoding="utf-8") as f:
232
+ content = f.read()
233
+ # Insert before </head> - uses DOMContentLoaded + setTimeout
234
+ # to override libdoc's own theme initialization
235
+ if "libtoc-theme" not in content:
236
+ content = content.replace("</head>", libtoc_script + "</head>", 1)
237
+ with open(file_path, "w", encoding="utf-8") as f:
238
+ f.write(content)
239
+
240
+
99
241
  def add_files_from_folder(folder, base_dir_path, root=True):
100
242
  """
101
243
  Creates a HTML source code with links to all HTML files in the `folder` and all it's subfolders.
@@ -236,6 +378,7 @@ def create_toc(
236
378
  homepage_file="homepage.html",
237
379
  toc_template="",
238
380
  homepage_template="",
381
+ no_timestamp=False,
239
382
  ):
240
383
  """
241
384
  Generates a `toc_file` (Table of Contents) HTML page with links to all HTML files inside the `html_docs_dir` and all it's subfolders.
@@ -259,10 +402,20 @@ def create_toc(
259
402
 
260
403
  # create homepage in "src"
261
404
  homepage_path = os.path.join(src_subdir, homepage_file)
262
- current_date_time = datetime.now().strftime("%d.%m.%Y %H:%M:%S")
405
+ current_date_time = "" if no_timestamp else datetime.now().strftime("%d.%m.%Y %H:%M:%S")
263
406
  doc_files_links = add_files_from_folder(src_subdir, os.path.abspath(html_docs_dir))
264
407
  with open(homepage_path, "w", encoding="utf8") as f:
265
- f.write(homepage(current_date_time, homepage_template))
408
+ f.write(homepage(homepage_template))
409
+
410
+ # build search index from generated docs
411
+ search_index = build_search_index(src_subdir, os.path.abspath(html_docs_dir), homepage_file)
412
+
413
+ # strip timestamps from libdoc HTML files if requested
414
+ if no_timestamp:
415
+ strip_libdoc_timestamps(src_subdir, homepage_file)
416
+
417
+ # inject libtoc script into all libdoc HTML files
418
+ inject_libtoc_script(src_subdir, homepage_file)
266
419
 
267
420
  # create TOC
268
421
  toc_file_path = os.path.join(html_docs_dir, toc_file)
@@ -273,6 +426,7 @@ def create_toc(
273
426
  current_date_time,
274
427
  os.path.relpath(homepage_path, os.path.abspath(html_docs_dir)),
275
428
  toc_template,
429
+ search_index,
276
430
  )
277
431
  )
278
432
 
@@ -306,6 +460,12 @@ def main():
306
460
  default="",
307
461
  help="Custom HTML template for the homepage file",
308
462
  )
463
+ parser.add_argument(
464
+ "--no_timestamp",
465
+ action="store_true",
466
+ default=False,
467
+ help="Do not include timestamps in the generated TOC and libdoc HTML files",
468
+ )
309
469
  parser.add_argument(
310
470
  "-P",
311
471
  "--pythonpath",
@@ -389,6 +549,7 @@ def main():
389
549
  args.toc_file,
390
550
  toc_template=args.toc_template,
391
551
  homepage_template=args.homepage_template,
552
+ no_timestamp=args.no_timestamp,
392
553
  )
393
554
  else:
394
555
  print("No docs were created!")