bluer-objects 6.104.1__py3-none-any.whl → 6.377.1__py3-none-any.whl

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 (146) hide show
  1. bluer_objects/.abcli/abcli.sh +6 -0
  2. bluer_objects/.abcli/alias.sh +11 -0
  3. bluer_objects/.abcli/assets/cd.sh +20 -0
  4. bluer_objects/.abcli/assets/mv.sh +34 -0
  5. bluer_objects/.abcli/assets/publish.sh +37 -0
  6. bluer_objects/.abcli/assets.sh +15 -0
  7. bluer_objects/.abcli/create_test_asset.sh +10 -0
  8. bluer_objects/.abcli/download.sh +3 -1
  9. bluer_objects/.abcli/file.sh +15 -4
  10. bluer_objects/.abcli/gif.sh +18 -0
  11. bluer_objects/.abcli/host.sh +23 -7
  12. bluer_objects/.abcli/ls.sh +19 -8
  13. bluer_objects/.abcli/metadata/download.sh +9 -0
  14. bluer_objects/.abcli/metadata/edit.sh +15 -0
  15. bluer_objects/.abcli/metadata/upload.sh +9 -0
  16. bluer_objects/.abcli/mlflow/browse.sh +2 -0
  17. bluer_objects/.abcli/mlflow/deploy.sh +21 -5
  18. bluer_objects/.abcli/mlflow/lock/lock.sh +11 -0
  19. bluer_objects/.abcli/mlflow/lock/unlock.sh +12 -0
  20. bluer_objects/.abcli/mlflow/lock.sh +15 -0
  21. bluer_objects/.abcli/mlflow.sh +0 -2
  22. bluer_objects/.abcli/pdf/convert.sh +92 -0
  23. bluer_objects/.abcli/pdf.sh +15 -0
  24. bluer_objects/.abcli/storage/clear.sh +2 -0
  25. bluer_objects/.abcli/tests/clone.sh +2 -3
  26. bluer_objects/.abcli/tests/create_test_asset.sh +16 -0
  27. bluer_objects/.abcli/tests/file.sh +64 -0
  28. bluer_objects/.abcli/tests/gif.sh +3 -3
  29. bluer_objects/.abcli/tests/help.sh +27 -4
  30. bluer_objects/.abcli/tests/ls.sh +11 -4
  31. bluer_objects/.abcli/tests/metadata.sh +35 -0
  32. bluer_objects/.abcli/tests/mlflow_lock.sh +30 -0
  33. bluer_objects/.abcli/tests/open.sh +11 -0
  34. bluer_objects/.abcli/tests/open_gif_open.sh +14 -0
  35. bluer_objects/.abcli/tests/pdf.sh +31 -0
  36. bluer_objects/.abcli/tests/storage_clear.sh +11 -0
  37. bluer_objects/.abcli/tests/storage_public_upload.sh +25 -0
  38. bluer_objects/.abcli/tests/storage_status.sh +12 -0
  39. bluer_objects/.abcli/tests/{storage.sh → storage_upload_download.sh} +26 -8
  40. bluer_objects/.abcli/tests/web_is_accessible.sh +17 -0
  41. bluer_objects/.abcli/tests/web_where_am_ai.sh +5 -0
  42. bluer_objects/.abcli/upload.sh +26 -2
  43. bluer_objects/.abcli/url.sh +15 -0
  44. bluer_objects/.abcli/web/is_accessible.sh +13 -0
  45. bluer_objects/.abcli/web/where_am_i.sh +5 -0
  46. bluer_objects/README/__init__.py +24 -9
  47. bluer_objects/README/alias.py +56 -0
  48. bluer_objects/README/consts.py +39 -0
  49. bluer_objects/README/functions.py +127 -205
  50. bluer_objects/README/items.py +78 -6
  51. bluer_objects/README/utils.py +275 -0
  52. bluer_objects/__init__.py +1 -1
  53. bluer_objects/assets/__init__.py +0 -0
  54. bluer_objects/assets/__main__.py +57 -0
  55. bluer_objects/assets/functions.py +62 -0
  56. bluer_objects/config.env +9 -1
  57. bluer_objects/env.py +23 -0
  58. bluer_objects/file/__main__.py +52 -7
  59. bluer_objects/file/functions.py +13 -3
  60. bluer_objects/file/load.py +2 -9
  61. bluer_objects/file/save.py +17 -24
  62. bluer_objects/graphics/__main__.py +7 -0
  63. bluer_objects/graphics/gif.py +11 -7
  64. bluer_objects/graphics/screen.py +9 -8
  65. bluer_objects/help/assets.py +96 -0
  66. bluer_objects/help/create_test_asset.py +22 -0
  67. bluer_objects/help/download.py +17 -3
  68. bluer_objects/help/file.py +59 -0
  69. bluer_objects/help/functions.py +11 -1
  70. bluer_objects/help/gif.py +25 -0
  71. bluer_objects/help/host.py +6 -4
  72. bluer_objects/help/ls.py +26 -3
  73. bluer_objects/help/metadata.py +51 -0
  74. bluer_objects/help/mlflow/__init__.py +23 -2
  75. bluer_objects/help/mlflow/lock.py +52 -0
  76. bluer_objects/help/pdf.py +67 -0
  77. bluer_objects/help/upload.py +10 -3
  78. bluer_objects/help/web.py +38 -0
  79. bluer_objects/host/functions.py +4 -1
  80. bluer_objects/logger/confusion_matrix.py +76 -0
  81. bluer_objects/logger/image.py +110 -0
  82. bluer_objects/logger/stitch.py +107 -0
  83. bluer_objects/markdown.py +8 -6
  84. bluer_objects/metadata/__init__.py +1 -0
  85. bluer_objects/metadata/flatten.py +27 -0
  86. bluer_objects/mlflow/lock/__init__.py +1 -0
  87. bluer_objects/mlflow/lock/__main__.py +58 -0
  88. bluer_objects/mlflow/lock/functions.py +121 -0
  89. bluer_objects/mlflow/logging.py +47 -41
  90. bluer_objects/pdf/__init__.py +1 -0
  91. bluer_objects/pdf/__main__.py +78 -0
  92. bluer_objects/pdf/convert/__init__.py +0 -0
  93. bluer_objects/pdf/convert/batch.py +54 -0
  94. bluer_objects/pdf/convert/combination.py +32 -0
  95. bluer_objects/pdf/convert/convert.py +111 -0
  96. bluer_objects/pdf/convert/image.py +53 -0
  97. bluer_objects/pdf/convert/md.py +97 -0
  98. bluer_objects/pdf/convert/missing.py +96 -0
  99. bluer_objects/pdf/convert/pdf.py +37 -0
  100. bluer_objects/sample.env +6 -0
  101. bluer_objects/storage/WebDAV.py +11 -7
  102. bluer_objects/storage/WebDAVrequest.py +360 -0
  103. bluer_objects/storage/WebDAVzip.py +26 -29
  104. bluer_objects/storage/__init__.py +28 -1
  105. bluer_objects/storage/__main__.py +40 -6
  106. bluer_objects/storage/base.py +84 -5
  107. bluer_objects/storage/policies.py +7 -0
  108. bluer_objects/storage/s3.py +367 -0
  109. bluer_objects/testing/__main__.py +6 -0
  110. bluer_objects/tests/test_README_consts.py +71 -0
  111. bluer_objects/tests/test_README_items.py +128 -0
  112. bluer_objects/tests/test_alias.py +33 -0
  113. bluer_objects/tests/test_env.py +25 -2
  114. bluer_objects/tests/test_file_download.py +25 -0
  115. bluer_objects/tests/test_file_load_save.py +1 -2
  116. bluer_objects/tests/test_file_load_save_text.py +46 -0
  117. bluer_objects/tests/test_graphics_gif.py +2 -0
  118. bluer_objects/tests/test_log_image_grid.py +29 -0
  119. bluer_objects/tests/test_logger_confusion_matrix.py +18 -0
  120. bluer_objects/tests/test_logger_matrix.py +2 -2
  121. bluer_objects/tests/test_logger_stitch_images.py +47 -0
  122. bluer_objects/tests/test_metadata.py +12 -6
  123. bluer_objects/tests/test_metadata_flatten.py +109 -0
  124. bluer_objects/tests/test_mlflow.py +2 -2
  125. bluer_objects/tests/test_mlflow_lock.py +26 -0
  126. bluer_objects/tests/test_objects.py +2 -0
  127. bluer_objects/tests/test_shell.py +34 -0
  128. bluer_objects/tests/test_storage.py +8 -21
  129. bluer_objects/tests/test_storage_base.py +39 -0
  130. bluer_objects/tests/test_storage_s3.py +67 -0
  131. bluer_objects/tests/test_storage_webdav_request.py +75 -0
  132. bluer_objects/tests/test_storage_webdav_zip.py +42 -0
  133. bluer_objects/tests/test_web_is_accessible.py +11 -0
  134. bluer_objects/web/__init__.py +1 -0
  135. bluer_objects/web/__main__.py +31 -0
  136. bluer_objects/web/functions.py +9 -0
  137. {bluer_objects-6.104.1.dist-info → bluer_objects-6.377.1.dist-info}/METADATA +6 -3
  138. bluer_objects-6.377.1.dist-info/RECORD +217 -0
  139. {bluer_objects-6.104.1.dist-info → bluer_objects-6.377.1.dist-info}/WHEEL +1 -1
  140. bluer_objects/.abcli/storage/download_file.sh +0 -9
  141. bluer_objects/.abcli/storage/exists.sh +0 -8
  142. bluer_objects/.abcli/storage/list.sh +0 -8
  143. bluer_objects/.abcli/storage/rm.sh +0 -11
  144. bluer_objects-6.104.1.dist-info/RECORD +0 -143
  145. {bluer_objects-6.104.1.dist-info → bluer_objects-6.377.1.dist-info}/licenses/LICENSE +0 -0
  146. {bluer_objects-6.104.1.dist-info → bluer_objects-6.377.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,121 @@
1
+ import time
2
+
3
+ from blueness import module
4
+ from bluer_options import string
5
+
6
+ from bluer_objects import NAME, env
7
+ from bluer_objects.mlflow.tags import get_tags, set_tags
8
+ from bluer_objects.logger import logger
9
+
10
+
11
+ NAME = module.name(__file__, NAME)
12
+
13
+
14
+ def lock(
15
+ object_name: str,
16
+ lock_name: str = "lock",
17
+ timeout: int = -1,
18
+ verbose: bool = True,
19
+ ) -> bool:
20
+ logger.info(
21
+ "{}.lock: {}.{}{}".format(
22
+ NAME,
23
+ object_name,
24
+ lock_name,
25
+ "" if timeout == -1 else " @ {}".format(string.pretty_duration(timeout)),
26
+ )
27
+ )
28
+
29
+ start_time = time.time()
30
+ while True:
31
+ if timeout > 0 and time.time() - start_time > timeout:
32
+ if verbose:
33
+ logger.warning(
34
+ "{}.lock: {}.{} timeout.".format(
35
+ NAME,
36
+ object_name,
37
+ lock_name,
38
+ )
39
+ )
40
+ return False
41
+
42
+ success, list_of_tags = get_tags(object_name=object_name)
43
+ if not success:
44
+ return False
45
+
46
+ lock_value = list_of_tags.get(lock_name, "")
47
+ if lock_value:
48
+ if verbose:
49
+ logger.warning(
50
+ "{}.lock: {}.{} is locked by {}.".format(
51
+ NAME,
52
+ object_name,
53
+ lock_name,
54
+ lock_value,
55
+ )
56
+ )
57
+ time.sleep(env.MLFLOW_LOCK_WAIT_FOR_CLEARANCE)
58
+ continue
59
+
60
+ lock_value = string.random()
61
+ if not set_tags(
62
+ object_name=object_name,
63
+ tags={lock_name: lock_value},
64
+ log=verbose,
65
+ icon="🔒",
66
+ ):
67
+ return False
68
+
69
+ time.sleep(env.MLFLOW_LOCK_WAIT_FOR_EXCLUSIVITY)
70
+
71
+ success, list_of_tags = get_tags(object_name=object_name)
72
+ if not success:
73
+ return False
74
+
75
+ lock_value_read = list_of_tags.get(lock_name, "")
76
+ if lock_value_read != lock_value:
77
+ if verbose:
78
+ logger.warning(
79
+ "{}.lock: {}.{} is relocked by {} != {}.".format(
80
+ NAME,
81
+ object_name,
82
+ lock_name,
83
+ lock_value_read,
84
+ lock_value,
85
+ )
86
+ )
87
+ time.sleep(env.MLFLOW_LOCK_WAIT_FOR_CLEARANCE)
88
+ continue
89
+
90
+ break
91
+
92
+ logger.info(
93
+ "{}.lock: {}.{} is locked by {}.".format(
94
+ NAME,
95
+ object_name,
96
+ lock_name,
97
+ lock_value,
98
+ )
99
+ )
100
+ return True
101
+
102
+
103
+ def unlock(
104
+ object_name: str,
105
+ lock_name: str = "lock",
106
+ verbose: bool = True,
107
+ ) -> bool:
108
+ logger.info(
109
+ "{}.unlock: {}.{}".format(
110
+ NAME,
111
+ object_name,
112
+ lock_name,
113
+ )
114
+ )
115
+
116
+ return set_tags(
117
+ object_name=object_name,
118
+ tags={lock_name: ""},
119
+ log=verbose,
120
+ icon="🔒",
121
+ )
@@ -6,7 +6,7 @@ import mlflow
6
6
  from blueness import module
7
7
  from bluer_options.logger import crash_report
8
8
 
9
- from bluer_objects import file, objects, NAME
9
+ from bluer_objects import file, objects, NAME, env
10
10
  from bluer_objects.mlflow.runs import start_run, end_run
11
11
  from bluer_objects.logger import logger
12
12
 
@@ -22,25 +22,28 @@ def log_artifacts(
22
22
 
23
23
  object_path = objects.object_path(object_name, create=True)
24
24
 
25
- try:
26
- mlflow.log_artifacts(object_path)
25
+ if env.MLFLOW_LOG_ARTIFACTS:
26
+ try:
27
+ mlflow.log_artifacts(object_path)
27
28
 
28
- logger.info("⬆️ {}".format(object_name))
29
+ logger.info("⬆️ {}".format(object_name))
29
30
 
30
- # https://mlflow.org/docs/latest/python_api/mlflow.html#mlflow.register_model
31
- # https://stackoverflow.com/a/71447758/17619982
32
- if model_name:
33
- mv = mlflow.register_model(
34
- "runs:/{}".format(mlflow.active_run().info.run_id),
35
- model_name,
36
- await_registration_for=0,
37
- )
31
+ # https://mlflow.org/docs/latest/python_api/mlflow.html#mlflow.register_model
32
+ # https://stackoverflow.com/a/71447758/17619982
33
+ if model_name:
34
+ mv = mlflow.register_model(
35
+ "runs:/{}".format(mlflow.active_run().info.run_id),
36
+ model_name,
37
+ await_registration_for=0,
38
+ )
38
39
 
39
- logger.info("*️⃣ {} -> {}.{}".format(object_name, mv.name, mv.version))
40
+ logger.info("*️⃣ {} -> {}.{}".format(object_name, mv.name, mv.version))
40
41
 
41
- except:
42
- crash_report(f"{NAME}.log_artifacts({object_name})")
43
- return False
42
+ except:
43
+ crash_report(f"{NAME}.log_artifacts({object_name})")
44
+ return False
45
+ else:
46
+ logger.info("skipped log artifacts.")
44
47
 
45
48
  return end_run(object_name)
46
49
 
@@ -51,31 +54,34 @@ def log_run(object_name: str) -> bool:
51
54
 
52
55
  object_path = objects.object_path(object_name, create=True)
53
56
 
54
- counts: Dict[str, int] = {}
55
- skipped_count = 0
56
- for extension in "dot,gif,jpeg,jpg,json,png,sh,xml,yaml".split(","):
57
- for filename in glob.glob(
58
- os.path.join(object_path, f"*.{extension}"),
59
- ):
60
- filename_name = file.name(filename)
61
-
62
- counts[len(filename_name)] = counts.get(len(filename_name), 0) + 1
63
-
64
- if any(
65
- [
66
- file.size(filename) > 10 * 1024 * 1024,
67
- filename_name.startswith("thumbnail"),
68
- counts[len(filename_name)] > 20,
69
- ]
57
+ if env.MLFLOW_LOG_ARTIFACTS:
58
+ counts: Dict[str, int] = {}
59
+ skipped_count = 0
60
+ for extension in "dot,gif,jpeg,jpg,json,png,sh,xml,yaml".split(","):
61
+ for filename in glob.glob(
62
+ os.path.join(object_path, f"*.{extension}"),
70
63
  ):
71
- logger.info(f"skipping {filename}")
72
- skipped_count += 1
73
- continue
74
-
75
- mlflow.log_artifact(filename)
76
- logger.info(f"⬆️ {filename}")
77
-
78
- if skipped_count:
79
- logger.info(f"skipped {skipped_count:,} file(s).")
64
+ filename_name = file.name(filename)
65
+
66
+ counts[len(filename_name)] = counts.get(len(filename_name), 0) + 1
67
+
68
+ if any(
69
+ [
70
+ file.size(filename) > 10 * 1024 * 1024,
71
+ filename_name.startswith("thumbnail"),
72
+ counts[len(filename_name)] > 20,
73
+ ]
74
+ ):
75
+ logger.info(f"skipping {filename}")
76
+ skipped_count += 1
77
+ continue
78
+
79
+ mlflow.log_artifact(filename)
80
+ logger.info(f"⬆️ {filename}")
81
+
82
+ if skipped_count:
83
+ logger.info(f"skipped {skipped_count:,} file(s).")
84
+ else:
85
+ logger.info("skipped log artifacts.")
80
86
 
81
87
  return end_run(object_name)
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,78 @@
1
+ import argparse
2
+
3
+ from blueness import module
4
+ from blueness.argparse.generic import sys_exit
5
+
6
+ from bluer_objects import NAME
7
+ from bluer_objects.pdf.convert.batch import batch as batch_convert
8
+ from bluer_objects.pdf.convert.convert import convert
9
+ from bluer_objects.logger import logger
10
+
11
+ NAME = module.name(__file__, NAME)
12
+
13
+ parser = argparse.ArgumentParser(NAME)
14
+ parser.add_argument(
15
+ "task",
16
+ type=str,
17
+ help="convert",
18
+ )
19
+ parser.add_argument(
20
+ "--path_prefix",
21
+ type=str,
22
+ )
23
+ parser.add_argument(
24
+ "--object_name",
25
+ type=str,
26
+ )
27
+ parser.add_argument(
28
+ "--suffixes",
29
+ type=str,
30
+ )
31
+ parser.add_argument(
32
+ "--combine",
33
+ type=int,
34
+ default=0,
35
+ help="0 | 1",
36
+ )
37
+
38
+ parser.add_argument(
39
+ "--count",
40
+ type=int,
41
+ default=-1,
42
+ help="-1: all",
43
+ )
44
+ parser.add_argument(
45
+ "--list_missing",
46
+ type=int,
47
+ default=1,
48
+ help="0 | 1",
49
+ )
50
+ parser.add_argument(
51
+ "--use_metadata",
52
+ type=int,
53
+ default=0,
54
+ help="0 | 1",
55
+ )
56
+ args = parser.parse_args()
57
+
58
+ success = False
59
+ if args.task == "convert":
60
+ if args.use_metadata == 1:
61
+ success = batch_convert(
62
+ object_name=args.object_name,
63
+ combine=args.combine == 1,
64
+ count=args.count,
65
+ list_missing=args.list_missing == 1,
66
+ )
67
+ else:
68
+ success = convert(
69
+ path_prefix=args.path_prefix,
70
+ list_of_suffixes=args.suffixes.split(","),
71
+ object_name=args.object_name,
72
+ combine=args.combine == 1,
73
+ count=args.count,
74
+ )
75
+ else:
76
+ success = None
77
+
78
+ sys_exit(logger, NAME, args.task, success)
File without changes
@@ -0,0 +1,54 @@
1
+ from typing import List
2
+
3
+ from blueness import module
4
+
5
+ from bluer_objects import NAME
6
+ from bluer_objects.metadata import get_from_object
7
+ from bluer_objects.pdf.convert.convert import convert
8
+ from bluer_objects.pdf.convert.missing import list_missing_docs
9
+ from bluer_objects.env import abcli_path_git
10
+ from bluer_objects.logger import logger
11
+
12
+
13
+ NAME = module.name(__file__, NAME)
14
+
15
+
16
+ def batch(
17
+ object_name: str,
18
+ combine: bool,
19
+ count: int = -1,
20
+ list_missing: bool = True,
21
+ ) -> bool:
22
+ logger.info(
23
+ "{}.batch: {}{}{}{}".format(
24
+ NAME,
25
+ object_name,
26
+ " + combine" if combine else "",
27
+ "" if count == -1 else f" {count} files",
28
+ " + list missing" if list_missing else "",
29
+ )
30
+ )
31
+
32
+ list_of_suffixes: List[str] = get_from_object(
33
+ object_name,
34
+ "pdf",
35
+ [],
36
+ )
37
+
38
+ if not convert(
39
+ path_prefix=abcli_path_git,
40
+ list_of_suffixes=list_of_suffixes,
41
+ object_name=object_name,
42
+ combine=combine,
43
+ count=count,
44
+ incremental=False,
45
+ ):
46
+ return False
47
+
48
+ if list_missing:
49
+ list_missing_docs(
50
+ object_name=object_name,
51
+ list_of_suffixes=list_of_suffixes,
52
+ )
53
+
54
+ return True
@@ -0,0 +1,32 @@
1
+ from typing import List
2
+ from PyPDF2 import PdfMerger
3
+
4
+ from bluer_options.logger import crash_report
5
+
6
+ from bluer_objects import objects
7
+ from bluer_objects.logger import logger
8
+
9
+
10
+ def combine_pdfs(
11
+ list_of_pdfs: List[str],
12
+ object_name: str,
13
+ ) -> bool:
14
+ logger.info(f"combining {len(list_of_pdfs)} pdf(s)...")
15
+ combined_filename = objects.path_of(
16
+ filename="release.pdf",
17
+ object_name=object_name,
18
+ )
19
+
20
+ try:
21
+ merger = PdfMerger()
22
+ for filename in list_of_pdfs:
23
+ merger.append(filename)
24
+
25
+ merger.write(combined_filename)
26
+ merger.close()
27
+ except Exception as e:
28
+ crash_report(e)
29
+ return False
30
+
31
+ logger.info(f"-> {combined_filename}")
32
+ return True
@@ -0,0 +1,111 @@
1
+ from tqdm import tqdm
2
+ from typing import List
3
+ import os
4
+ from PIL import Image
5
+
6
+ from blueness import module
7
+
8
+ from bluer_objects import NAME
9
+ from bluer_objects import file, path
10
+ from bluer_objects.metadata import get_from_object, post_to_object
11
+ from bluer_objects.pdf.convert.combination import combine_pdfs
12
+ from bluer_objects.pdf.convert.image import convert_image
13
+ from bluer_objects.pdf.convert.md import convert_md
14
+ from bluer_objects.pdf.convert.pdf import convert_pdf
15
+ from bluer_objects.logger import logger
16
+
17
+
18
+ NAME = module.name(__file__, NAME)
19
+
20
+
21
+ def convert(
22
+ path_prefix: str,
23
+ list_of_suffixes: List[str],
24
+ object_name: str,
25
+ combine: bool,
26
+ count: int = -1,
27
+ incremental: bool = True,
28
+ ) -> bool:
29
+ logger.info(f"path_prefix: {path_prefix}")
30
+
31
+ list_of_pdfs: List[str] = (
32
+ get_from_object(
33
+ object_name,
34
+ "list_of_pdfs",
35
+ [],
36
+ )
37
+ if incremental
38
+ else []
39
+ )
40
+ if incremental:
41
+ logger.info(f"found {len(list_of_pdfs)} pdf(s)...")
42
+
43
+ list_of_pdfs_len_target = -1 if count == -1 else len(list_of_pdfs) + count
44
+ for suffix in tqdm(list_of_suffixes):
45
+ if (
46
+ list_of_pdfs_len_target != -1
47
+ and len(list_of_pdfs) >= list_of_pdfs_len_target
48
+ ):
49
+ logger.info(f"max count {count}, stopping.")
50
+ break
51
+
52
+ logger.info(
53
+ "{}.convert {} -> {}".format(
54
+ NAME,
55
+ suffix,
56
+ object_name,
57
+ )
58
+ )
59
+
60
+ source_filename = os.path.join(path_prefix, suffix)
61
+ if path.exists(source_filename):
62
+ source_filename = os.path.join(source_filename, "README.md")
63
+ suffix = os.path.join(suffix, "README.md")
64
+
65
+ if source_filename.endswith(".md"):
66
+ if not convert_md(
67
+ source_filename,
68
+ suffix,
69
+ object_name,
70
+ list_of_pdfs,
71
+ ):
72
+ return False
73
+ elif file.extension(source_filename) == "pdf":
74
+ if not convert_pdf(
75
+ source_filename,
76
+ suffix,
77
+ object_name,
78
+ list_of_pdfs,
79
+ ):
80
+ return False
81
+ elif file.extension(source_filename) in [
82
+ extension.split(".", 1)[1]
83
+ for extension in Image.registered_extensions().keys()
84
+ ]:
85
+ if not convert_image(
86
+ source_filename,
87
+ suffix,
88
+ object_name,
89
+ list_of_pdfs,
90
+ ):
91
+ return False
92
+ else:
93
+ logger.error(f"{source_filename}: cannot convert to pdf.")
94
+ return False
95
+
96
+ if incremental:
97
+ logger.info(f"{len(list_of_pdfs)} pdf(s) so far ...")
98
+ if not post_to_object(
99
+ object_name,
100
+ "list_of_pdfs",
101
+ list_of_pdfs,
102
+ ):
103
+ return False
104
+
105
+ if combine:
106
+ return combine_pdfs(
107
+ list_of_pdfs,
108
+ object_name,
109
+ )
110
+
111
+ return True
@@ -0,0 +1,53 @@
1
+ from PIL import Image
2
+
3
+ from bluer_options.logger import crash_report
4
+
5
+ from bluer_objects import file, objects, path
6
+ from bluer_objects.env import abcli_path_git
7
+ from bluer_objects.logger import logger
8
+
9
+
10
+ def convert_image(
11
+ source_filename: str,
12
+ suffix: str,
13
+ object_name: str,
14
+ list_of_pdfs: list[str],
15
+ ) -> bool:
16
+ logger.info("🌠 image found!")
17
+ filename_pdf = file.add_extension(
18
+ objects.path_of(
19
+ filename="docs/{}".format(
20
+ (
21
+ suffix.split(abcli_path_git, 1)[1]
22
+ if abcli_path_git in suffix
23
+ else suffix
24
+ ),
25
+ ),
26
+ object_name=object_name,
27
+ ),
28
+ "pdf",
29
+ )
30
+
31
+ if filename_pdf not in list_of_pdfs:
32
+ list_of_pdfs.append(filename_pdf)
33
+
34
+ if file.exists(filename_pdf):
35
+ logger.info(f"✅ {filename_pdf}")
36
+ return True
37
+
38
+ if not path.create(
39
+ file.path(filename_pdf),
40
+ log=True,
41
+ ):
42
+ return False
43
+
44
+ try:
45
+ image = Image.open(source_filename)
46
+ image = image.convert("RGB")
47
+ image.save(filename_pdf)
48
+ except Exception as e:
49
+ crash_report(e)
50
+ return False
51
+
52
+ logger.info(f"-> {filename_pdf}")
53
+ return True
@@ -0,0 +1,97 @@
1
+ import pypandoc
2
+ import subprocess
3
+ import os
4
+
5
+ from bluer_options.logger import crash_report
6
+
7
+ from bluer_objects import file, objects, path
8
+ from bluer_objects.env import abcli_path_git
9
+ from bluer_objects.logger import logger
10
+
11
+ css = """
12
+ <style>
13
+ body { font-family: sans-serif; margin: 2cm; }
14
+ img { max-width: 100%; height: auto; }
15
+ table { width: 100%; border-collapse: collapse; word-break: break-word; }
16
+ th, td { border: 1px solid #ccc; padding: 4px; vertical-align: top; }
17
+ code, pre { white-space: pre-wrap; }
18
+ </style>
19
+ """
20
+
21
+
22
+ def convert_md(
23
+ source_filename: str,
24
+ suffix: str,
25
+ object_name: str,
26
+ list_of_pdfs: list[str],
27
+ ) -> bool:
28
+ filename_html = file.add_extension(
29
+ objects.path_of(
30
+ filename="docs/{}".format(
31
+ (
32
+ suffix.split(abcli_path_git, 1)[1]
33
+ if abcli_path_git in suffix
34
+ else suffix
35
+ ),
36
+ ),
37
+ object_name=object_name,
38
+ ),
39
+ "html",
40
+ )
41
+ filename_pdf = file.add_extension(
42
+ filename_html,
43
+ "pdf",
44
+ )
45
+
46
+ if filename_pdf not in list_of_pdfs:
47
+ list_of_pdfs.append(filename_pdf)
48
+
49
+ if file.exists(filename_pdf):
50
+ logger.info(f"✅ {filename_pdf}")
51
+ return True
52
+
53
+ logger.info(f"{source_filename} -> {filename_pdf}")
54
+
55
+ try:
56
+ with open(source_filename, "r", encoding="utf-8") as f:
57
+ markdown_text = f.read()
58
+
59
+ html_text = pypandoc.convert_text(
60
+ markdown_text,
61
+ "html",
62
+ format="md",
63
+ )
64
+
65
+ html_text = (
66
+ f"<!DOCTYPE html><html><head>{css}</head><body>{html_text}</body></html>"
67
+ )
68
+
69
+ if not path.create(
70
+ file.path(filename_html),
71
+ log=True,
72
+ ):
73
+ return (False,)
74
+
75
+ with open(
76
+ filename_html,
77
+ "w",
78
+ encoding="utf-8",
79
+ ) as f:
80
+ f.write(html_text)
81
+
82
+ subprocess.run(
83
+ [
84
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
85
+ "--headless",
86
+ "--disable-gpu",
87
+ "--no-margins",
88
+ f"--print-to-pdf={filename_pdf}",
89
+ os.path.abspath(filename_html),
90
+ ],
91
+ check=True,
92
+ )
93
+ except Exception as e:
94
+ crash_report(e)
95
+ return False
96
+
97
+ return True