From ea774369b637136740abf0f05b177b7b29f3e000 Mon Sep 17 00:00:00 2001 From: LiangliangSui Date: Fri, 24 May 2024 21:52:52 +0800 Subject: [PATCH 1/5] feat: add a script to automatically generate doc Signed-off-by: LiangliangSui --- docs/guide/DEVELOPMENT.md | 36 +++ docs/guide/java_serialization_guide.md | 3 +- .../org/apache/fury/config/FuryBuilder.java | 3 + tools/gen_fury_builder_doc.py | 215 ++++++++++++++++++ 4 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 tools/gen_fury_builder_doc.py diff --git a/docs/guide/DEVELOPMENT.md b/docs/guide/DEVELOPMENT.md index bfab541791..45922c561a 100644 --- a/docs/guide/DEVELOPMENT.md +++ b/docs/guide/DEVELOPMENT.md @@ -100,3 +100,39 @@ npm run test - node 14+ - npm 8+ +# Update doc + +If [FuryBuilder](https://github.com/apache/incubator-fury/blob/main/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java) is modified (including modifying configuration fields or adding new configuration fields), we need to update the table in [java_serialization_guide.md#furybuilder--options](https://github.com/apache/incubator-fury/blob/main/docs/guide/java_serialization_guide.md#furybuilder--options). + +We provide a script to automatically update this table. After modifying [FuryBuilder](https://github.com/apache/incubator-fury/blob/main/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java), we can perform the update operation through the following command. + +```bash +python3 tools/gen_fury_builder_doc.py +``` + +To use this script, we need to add comments on the [FuryBuilder](https://github.com/apache/incubator-fury/blob/main/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java) configuration fields in the following format. + +``` +/** + * Comment body + * + * defaultValue: xxx + */ +``` +or +``` +/** Comment body defaultValue: xxx */ +``` + +The `Comment body` corresponds to the `Description` in the table, and the `defaultValue(optional)` corresponds to the `Default Value` in the table. If no `defaultValue` is provided in the comment, the value specified after the field will be used as the `defaultValue` (e.g. `boolean enable = true`, `true` will be used as `defaultValue`). + +All configuration fields should be within the following two tags + +``` +// org/apache/fury/config/FuryBuilder.java + +// ======== Config Area Begin ======== +xxxxxx +xxxxxx +// ======== Config Area End ========= +``` diff --git a/docs/guide/java_serialization_guide.md b/docs/guide/java_serialization_guide.md index a86c39e408..afb3ab5124 100644 --- a/docs/guide/java_serialization_guide.md +++ b/docs/guide/java_serialization_guide.md @@ -95,7 +95,7 @@ public class Example { ``` ## FuryBuilder options - + | Option Name | Description | Default Value | |-------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------| | `timeRefIgnored` | Whether to ignore reference tracking of all time types registered in `TimeSerializers` and subclasses of those types when ref tracking is enabled. If ignored, ref tracking of every time type can be enabled by invoking `Fury#registerSerializer(Class, Serializer)`. For example, `fury.registerSerializer(Date.class, new DateSerializer(fury, true))`. Note that enabling ref tracking should happen before serializer codegen of any types which contain time fields. Otherwise, those fields will still skip ref tracking. | `true` | @@ -114,6 +114,7 @@ public class Example { | `codeGenEnabled` | Disabling may result in faster initial serialization but slower subsequent serializations. | `true` | | `asyncCompilationEnabled` | If enabled, serialization uses interpreter mode first and switches to JIT serialization after async serializer JIT for a class is finished. | `false` | | `scalaOptimizationEnabled` | Enables or disables Scala-specific serialization optimization. | `false` | + ## Advanced Usage diff --git a/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java b/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java index 9df183a960..96cf5dae66 100644 --- a/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java +++ b/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java @@ -54,6 +54,7 @@ public final class FuryBuilder { ENABLE_CLASS_REGISTRATION_FORCIBLY = "true".equals(flagValue) || "1".equals(flagValue); } + // ======== Config Area Begin ======== boolean checkClassVersion = false; Language language = Language.JAVA; boolean trackingRef = false; @@ -77,6 +78,8 @@ public final class FuryBuilder { boolean suppressClassRegistrationWarnings = true; boolean deserializeNonexistentEnumValueAsNull = false; + // ======== Config Area End ========= + public FuryBuilder() {} /** diff --git a/tools/gen_fury_builder_doc.py b/tools/gen_fury_builder_doc.py new file mode 100644 index 0000000000..5725286a0b --- /dev/null +++ b/tools/gen_fury_builder_doc.py @@ -0,0 +1,215 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import os +import re +import sys +import dataclasses + +PROJECT_ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../") + +FURY_BUILDER_PATH = os.path.join( + PROJECT_ROOT_DIR, + "java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java", +) +JAVA_DOC_PATH = os.path.join(PROJECT_ROOT_DIR, "docs/guide/java_serialization_guide.md") + +JAVA_CONFIG_BEGIN = "// ======== Config Area Begin ========" +JAVA_CONFIG_END = "// ======== Config Area End =========" +DOC_GEN_BEGIN = "" +DOC_GEN_END = "" + +SPLIT_FIELD_PATTERN = re.compile(r"(?![*]+);\s*$", flags=re.MULTILINE) +COMMENT_TMPL_PATTERN = re.compile(r"^\s*/[*]+\s*.*\s*[*]+/$", re.S | re.MULTILINE) +SINGLE_LINE_COMMENT_PATTERN = re.compile(r"^\s*/[*]+(.+)[*]+/$", re.MULTILINE) +MULTI_LINE_COMMENT_PATTERN = re.compile( + r"^(?!^\s*[*]\s?$)(\s*[*]+[\x20]*)[\x20]?(.*)[\x20]?(?)\s+(\w+)\s*=?\s*(.*)$", + re.MULTILINE, +) +FILE_REPLACE_PATTERN = re.compile( + rf"^{DOC_GEN_BEGIN}.*{DOC_GEN_END}$", flags=re.MULTILINE | re.S +) + + +@dataclasses.dataclass +class FieldInfo: + field_scope: str + field_name: str + field_type: str + field_default_val: str + field_comment: str + + +def _parse_fields(content): + fields = SPLIT_FIELD_PATTERN.split(content) + result = [] + for field in fields: + field_info = _parse_field(field) + if field_info is not None: + result.append(field_info) + + return result + + +def _parse_field(field): + # 1. Format comment section + comment_overview_match = COMMENT_TMPL_PATTERN.search(field) + if comment_overview_match is None: + comment = None + default_val = None + else: + single_line_match = SINGLE_LINE_COMMENT_PATTERN.search(field) + if single_line_match is not None: + fat_comment = single_line_match.group(1) + else: + multi_line_matches = MULTI_LINE_COMMENT_PATTERN.finditer(field) + fat_comment = "" + for match in multi_line_matches: + data = match.group(2) + fat_comment += data + " " + + cutting_with_tag = "

defaultValue:" + cutting = "defaultValue:" + if field.find(cutting_with_tag) != -1: + split_data = fat_comment.split(cutting_with_tag) + else: + split_data = fat_comment.split(cutting) + assert ( + len(split_data) <= 2 + ), "Only one defaultValue can be specified in the comment." + + default_val = split_data[1].strip() if len(split_data) == 2 else None + comment = split_data[0].strip() + + # 2. Format field section + field_match = FIELD_PATTERN.search(field) + if field_match is None: + return None + + scope = field_match.group(1).strip() + type = field_match.group(2) + name = field_match.group(3) + if ( + default_val is None + and field_match.group(4) is not None + and len(field_match.group(4)) > 0 + ): + default_val = field_match.group(4) + field_info = FieldInfo(scope, name, type, default_val, comment) + return field_info + + +def _write_content(fields): + if len(fields) == 0: + return + + with open(JAVA_DOC_PATH) as f: + content = f.read() + if content is None: + return + + """ + Table format: + | Option Name | Description | Default Value | <------ Table header + |---------------|--------------------|-------------------| <------ Table delimiter + | `xxxxxx` | xxxxxxxx | xxxxx | <------ Table body + """ + hdr1 = " Option Name" + hdr2 = " Description" + hdr3 = " Default Value" + margin_right = 10 + hdr1_width = len(hdr1) + margin_right + hdr2_width = len(hdr2) + margin_right + hdr3_width = len(hdr3) + margin_right + for field in fields: + fname = field.field_name + fdesc = ( + field.field_comment + if field.field_comment is not None and len(field.field_comment) > 0 + else "None" + ) + fval = ( + field.field_default_val + if field.field_default_val is not None and len(field.field_default_val) > 0 + else "None" + ) + + if len(fname) > hdr1_width: + hdr1_width = len(fname) + margin_right + + if len(fdesc) > hdr2_width: + hdr2_width = len(fdesc) + margin_right + + if len(fval) > hdr3_width: + hdr3_width = len(fval) + margin_right + + table_header = ( + f"|{hdr1}{(hdr1_width - len(hdr1)) * ' '}" + f"|{hdr2}{(hdr2_width - len(hdr2)) * ' '}" + f"|{hdr3}{(hdr3_width - len(hdr3)) * ' '}|" + f"\n" + ) + table_delimiter = f"|{hdr1_width * '-'}|{hdr2_width * '-'}|{hdr3_width * '-'}|\n" + table_body = "" + for field in fields: + fname = field.field_name + fdesc = ( + field.field_comment + if field.field_comment is not None and len(field.field_comment) > 0 + else "None" + ) + fval = ( + field.field_default_val + if field.field_default_val is not None and len(field.field_default_val) > 0 + else "None" + ) + + row = ( + f"|`{fname}`{(hdr1_width - len(fname) - 2) * ' '}" + f"| {fdesc}{(hdr2_width - len(fdesc) - 1) * ' '}" + f"| {fval}{(hdr3_width - len(fval) - 1) * ' '}|" + f"\n" + ) + table_body += row + + table = table_header + table_delimiter + table_body + repl = f"{DOC_GEN_BEGIN}\n{table}\n{DOC_GEN_END}" + to_write = FILE_REPLACE_PATTERN.sub(repl, content) + + with open(JAVA_DOC_PATH, "w") as f: + f.write(to_write) + + +def main(): + with open(FURY_BUILDER_PATH) as f: + content = f.read() + if content is None: + return 1 + start_idx = content.find(JAVA_CONFIG_BEGIN) + end_idx = content.find(JAVA_CONFIG_END) + if start_idx == -1 or end_idx == -1: + return 0 + + fields = _parse_fields(content[start_idx + len(JAVA_CONFIG_BEGIN) : end_idx]) + _write_content(fields) + + +if __name__ == "__main__": + sys.exit(main()) From c6951525a67df6167ed5481a665693e86eb54d39 Mon Sep 17 00:00:00 2001 From: LiangliangSui Date: Mon, 27 May 2024 16:43:14 +0800 Subject: [PATCH 2/5] javadoc parse Signed-off-by: LiangliangSui --- docs/guide/DEVELOPMENT.md | 18 +--- java/fury-core/pom.xml | 15 +++ tools/gen_fury_builder_doc.py | 173 ++++++++++++++++++---------------- 3 files changed, 108 insertions(+), 98 deletions(-) diff --git a/docs/guide/DEVELOPMENT.md b/docs/guide/DEVELOPMENT.md index 45922c561a..9fe49e5ab8 100644 --- a/docs/guide/DEVELOPMENT.md +++ b/docs/guide/DEVELOPMENT.md @@ -116,23 +116,9 @@ To use this script, we need to add comments on the [FuryBuilder](https://github. /** * Comment body * - * defaultValue: xxx + * @defaultValue xxx */ ``` -or -``` -/** Comment body defaultValue: xxx */ -``` - -The `Comment body` corresponds to the `Description` in the table, and the `defaultValue(optional)` corresponds to the `Default Value` in the table. If no `defaultValue` is provided in the comment, the value specified after the field will be used as the `defaultValue` (e.g. `boolean enable = true`, `true` will be used as `defaultValue`). -All configuration fields should be within the following two tags +The `Comment body` corresponds to the `Description` in the table, and the `@defaultValue` corresponds to the `Default Value` in the table. -``` -// org/apache/fury/config/FuryBuilder.java - -// ======== Config Area Begin ======== -xxxxxx -xxxxxx -// ======== Config Area End ========= -``` diff --git a/java/fury-core/pom.xml b/java/fury-core/pom.xml index dcf872042c..8d1bcae0a6 100644 --- a/java/fury-core/pom.xml +++ b/java/fury-core/pom.xml @@ -128,6 +128,21 @@ org.apache.maven.plugins maven-compiler-plugin + + org.apache.maven.plugins + maven-javadoc-plugin + + UTF-8 + UTF-8 + + + defaultValue + f + defaultValue + + + + diff --git a/tools/gen_fury_builder_doc.py b/tools/gen_fury_builder_doc.py index 5725286a0b..9e34be3ad7 100644 --- a/tools/gen_fury_builder_doc.py +++ b/tools/gen_fury_builder_doc.py @@ -19,6 +19,9 @@ import re import sys import dataclasses +import subprocess +import tempfile +import shutil PROJECT_ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../") @@ -28,20 +31,11 @@ ) JAVA_DOC_PATH = os.path.join(PROJECT_ROOT_DIR, "docs/guide/java_serialization_guide.md") -JAVA_CONFIG_BEGIN = "// ======== Config Area Begin ========" -JAVA_CONFIG_END = "// ======== Config Area End =========" DOC_GEN_BEGIN = "" DOC_GEN_END = "" -SPLIT_FIELD_PATTERN = re.compile(r"(?![*]+);\s*$", flags=re.MULTILINE) -COMMENT_TMPL_PATTERN = re.compile(r"^\s*/[*]+\s*.*\s*[*]+/$", re.S | re.MULTILINE) -SINGLE_LINE_COMMENT_PATTERN = re.compile(r"^\s*/[*]+(.+)[*]+/$", re.MULTILINE) -MULTI_LINE_COMMENT_PATTERN = re.compile( - r"^(?!^\s*[*]\s?$)(\s*[*]+[\x20]*)[\x20]?(.*)[\x20]?(?)\s+(\w+)\s*=?\s*(.*)$", - re.MULTILINE, +FIELD_LINE_PATTERN = re.compile( + r"^(public\s+|protected\s+|private\s+|)(\w+|\w+<.+>)\s+\w+$" ) FILE_REPLACE_PATTERN = re.compile( rf"^{DOC_GEN_BEGIN}.*{DOC_GEN_END}$", flags=re.MULTILINE | re.S @@ -57,63 +51,46 @@ class FieldInfo: field_comment: str -def _parse_fields(content): - fields = SPLIT_FIELD_PATTERN.split(content) - result = [] - for field in fields: - field_info = _parse_field(field) - if field_info is not None: - result.append(field_info) - - return result - - -def _parse_field(field): - # 1. Format comment section - comment_overview_match = COMMENT_TMPL_PATTERN.search(field) - if comment_overview_match is None: - comment = None - default_val = None - else: - single_line_match = SINGLE_LINE_COMMENT_PATTERN.search(field) - if single_line_match is not None: - fat_comment = single_line_match.group(1) - else: - multi_line_matches = MULTI_LINE_COMMENT_PATTERN.finditer(field) - fat_comment = "" - for match in multi_line_matches: - data = match.group(2) - fat_comment += data + " " - - cutting_with_tag = "

defaultValue:" - cutting = "defaultValue:" - if field.find(cutting_with_tag) != -1: - split_data = fat_comment.split(cutting_with_tag) - else: - split_data = fat_comment.split(cutting) - assert ( - len(split_data) <= 2 - ), "Only one defaultValue can be specified in the comment." - - default_val = split_data[1].strip() if len(split_data) == 2 else None - comment = split_data[0].strip() - - # 2. Format field section - field_match = FIELD_PATTERN.search(field) - if field_match is None: - return None - - scope = field_match.group(1).strip() - type = field_match.group(2) - name = field_match.group(3) - if ( - default_val is None - and field_match.group(4) is not None - and len(field_match.group(4)) > 0 - ): - default_val = field_match.group(4) - field_info = FieldInfo(scope, name, type, default_val, comment) - return field_info +def _parse_fields(fields_content): + fields_info = [] + for field in fields_content: + """ + Field format: +

+ """ + tag_labels = field.xpath('li/dl/dt/span[@class="simpleTagLabel"]/text()') + is_config_field = "defaultValue" in tag_labels + if not is_config_field: + continue + + field_default_val = "".join(field.xpath("li/dl/dd//text()")) + field_name = field.xpath("li/h4/text()")[0] + field_comment = "".join(field.xpath("li/div//text()")).replace("\n", "") + + field_line = "".join(field.xpath("li/pre//text()")) + match = FIELD_LINE_PATTERN.search(field_line) + scope_group = match.group(1).strip() + field_scope = None if len(scope_group) == 0 else scope_group + field_type = match.group(2) + fields_info.append( + FieldInfo( + field_scope, field_name, field_type, field_default_val, field_comment + ) + ) + + return fields_info def _write_content(fields): @@ -126,10 +103,10 @@ def _write_content(fields): return """ - Table format: - | Option Name | Description | Default Value | <------ Table header - |---------------|--------------------|-------------------| <------ Table delimiter - | `xxxxxx` | xxxxxxxx | xxxxx | <------ Table body + Table format: + | Option Name | Description | Default Value | <------ Table header + |---------------|--------------------|-------------------| <------ Table delimiter + | `xxxxxx` | xxxxxxxx | xxxxx | <------ Table body """ hdr1 = " Option Name" hdr2 = " Description" @@ -198,17 +175,49 @@ def _write_content(fields): def main(): - with open(FURY_BUILDER_PATH) as f: + # 1. Try installing lxml + try_count = 3 + while try_count >= 0: + try: + from lxml import etree + except Exception: + if try_count == 0: + return 1 + print("Try installing lxml.") + subprocess.check_call("pip3 install lxml", shell=True) + finally: + try_count -= 1 + + # 2. Generating javadoc + print("Generating javadoc...") + tmp_dir = tempfile.gettempdir() + output_dir = "output" + subprocess.call( + f"cd {PROJECT_ROOT_DIR}java/fury-core;" + f"mvn javadoc:javadoc -DreportOutputDirectory={tmp_dir} -DdestDir={output_dir} -Dshow=private", + shell=True, + stdout=subprocess.DEVNULL, + ) + print("javadoc generated successfully.") + + # 3. Parsing javadoc + javadoc_dir = os.path.join(tmp_dir, output_dir) + fury_build_src = os.path.join( + javadoc_dir, "org/apache/fury/config/FuryBuilder.html" + ) + with open(fury_build_src) as f: content = f.read() - if content is None: - return 1 - start_idx = content.find(JAVA_CONFIG_BEGIN) - end_idx = content.find(JAVA_CONFIG_END) - if start_idx == -1 or end_idx == -1: - return 0 - - fields = _parse_fields(content[start_idx + len(JAVA_CONFIG_BEGIN) : end_idx]) - _write_content(fields) + shutil.rmtree(javadoc_dir) + + html = etree.HTML(content) + # There is only one `Field Detail` + field_detail = html.xpath('//div[@class="details"]//section[1]')[0] + if field_detail is None: + return 1 + fields_content = field_detail.xpath("ul/li/ul") + fields_info = _parse_fields(fields_content) + _write_content(fields_info) + print("Doc update completed.") if __name__ == "__main__": From 621826b5e57a0cb5a346acf9cfc3a174592e02c7 Mon Sep 17 00:00:00 2001 From: LiangliangSui Date: Mon, 27 May 2024 16:45:18 +0800 Subject: [PATCH 3/5] update Signed-off-by: LiangliangSui --- .../src/main/java/org/apache/fury/config/FuryBuilder.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java b/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java index 96cf5dae66..9df183a960 100644 --- a/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java +++ b/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java @@ -54,7 +54,6 @@ public final class FuryBuilder { ENABLE_CLASS_REGISTRATION_FORCIBLY = "true".equals(flagValue) || "1".equals(flagValue); } - // ======== Config Area Begin ======== boolean checkClassVersion = false; Language language = Language.JAVA; boolean trackingRef = false; @@ -78,8 +77,6 @@ public final class FuryBuilder { boolean suppressClassRegistrationWarnings = true; boolean deserializeNonexistentEnumValueAsNull = false; - // ======== Config Area End ========= - public FuryBuilder() {} /** From 11f251c127d4a0eedcd490207fd80deca5cac1be Mon Sep 17 00:00:00 2001 From: LiangliangSui Date: Mon, 27 May 2024 16:47:23 +0800 Subject: [PATCH 4/5] update doc Signed-off-by: LiangliangSui --- docs/guide/DEVELOPMENT.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/DEVELOPMENT.md b/docs/guide/DEVELOPMENT.md index 9fe49e5ab8..1f3e83c436 100644 --- a/docs/guide/DEVELOPMENT.md +++ b/docs/guide/DEVELOPMENT.md @@ -104,13 +104,13 @@ npm run test If [FuryBuilder](https://github.com/apache/incubator-fury/blob/main/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java) is modified (including modifying configuration fields or adding new configuration fields), we need to update the table in [java_serialization_guide.md#furybuilder--options](https://github.com/apache/incubator-fury/blob/main/docs/guide/java_serialization_guide.md#furybuilder--options). -We provide a script to automatically update this table. After modifying [FuryBuilder](https://github.com/apache/incubator-fury/blob/main/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java), we can perform the update operation through the following command. +We provide a script to automatically update the table. After modifying [FuryBuilder](https://github.com/apache/incubator-fury/blob/main/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java), we can perform the update operation through the following command. ```bash python3 tools/gen_fury_builder_doc.py ``` -To use this script, we need to add comments on the [FuryBuilder](https://github.com/apache/incubator-fury/blob/main/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java) configuration fields in the following format. +To use this script, we need to add comment on the [FuryBuilder](https://github.com/apache/incubator-fury/blob/main/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java) configuration fields in the following format. ``` /** From 6bd7574588151e4b4291b234c4c3f7f7468427bc Mon Sep 17 00:00:00 2001 From: LiangliangSui Date: Mon, 27 May 2024 22:07:39 +0800 Subject: [PATCH 5/5] Use thrown exception instead of error code Signed-off-by: LiangliangSui --- tools/gen_fury_builder_doc.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/gen_fury_builder_doc.py b/tools/gen_fury_builder_doc.py index 9e34be3ad7..69cf7139c8 100644 --- a/tools/gen_fury_builder_doc.py +++ b/tools/gen_fury_builder_doc.py @@ -17,7 +17,6 @@ import os import re -import sys import dataclasses import subprocess import tempfile @@ -182,7 +181,7 @@ def main(): from lxml import etree except Exception: if try_count == 0: - return 1 + raise Exception(f"Retrying {try_count} times to install lxml failed.") print("Try installing lxml.") subprocess.check_call("pip3 install lxml", shell=True) finally: @@ -213,7 +212,9 @@ def main(): # There is only one `Field Detail` field_detail = html.xpath('//div[@class="details"]//section[1]')[0] if field_detail is None: - return 1 + raise Exception( + "There is no `Field Detail` related content in the current Javadoc." + ) fields_content = field_detail.xpath("ul/li/ul") fields_info = _parse_fields(fields_content) _write_content(fields_info) @@ -221,4 +222,4 @@ def main(): if __name__ == "__main__": - sys.exit(main()) + main()