mirror of
https://github.com/ONLYOFFICE/build_tools.git
synced 2026-04-07 14:06:31 +08:00
Compare commits
204 Commits
fix/docs-g
...
v9.0.0.8
| Author | SHA1 | Date | |
|---|---|---|---|
| 8dcf0277ac | |||
| 6664051127 | |||
| 019f10ee86 | |||
| a7ee5d5679 | |||
| 7c31890fc0 | |||
| ad9258710b | |||
| 626dd37312 | |||
| b4d95ccbb9 | |||
| 0d0ae2b5e6 | |||
| d7b3b7f120 | |||
| 7a864171b3 | |||
| 8cfb8f3d84 | |||
| 2f39454d31 | |||
| d3d1080c89 | |||
| 539597f07c | |||
| 40b11e192d | |||
| 72cc19f346 | |||
| efb22f741f | |||
| 2458673d3c | |||
| 3881a6659e | |||
| a567cc2222 | |||
| 1fc9382ce9 | |||
| ea43e67fe8 | |||
| dd28a41e17 | |||
| b11a273d65 | |||
| d4ee25b004 | |||
| a2b7719100 | |||
| 1e6cde4d98 | |||
| 34f627d146 | |||
| 54accd4394 | |||
| 63557fba56 | |||
| 7a4be158c2 | |||
| 810e12bd22 | |||
| 066f7ad8c1 | |||
| e52a654731 | |||
| 370879f636 | |||
| 170a511654 | |||
| 679afe1bc4 | |||
| 8b5cfff24a | |||
| 27de97031e | |||
| 8ee874da14 | |||
| 11c783f088 | |||
| a3cb31291f | |||
| 6a43b86912 | |||
| 21bb535ee0 | |||
| 9ea948b825 | |||
| fe2fad9378 | |||
| d566ffd9fa | |||
| 370b23f38f | |||
| 253ee696be | |||
| e08c6f79bc | |||
| 4240319fef | |||
| e1aaa2415b | |||
| e71eb56630 | |||
| 38496f2971 | |||
| d1c7d8d9f6 | |||
| 36fdfd672f | |||
| 55c0f61189 | |||
| 053e317850 | |||
| 38296bf292 | |||
| f0ba4564cc | |||
| 21ec70214d | |||
| 6d1a8376ba | |||
| 0ca83fe152 | |||
| 2301c407a2 | |||
| d6096431bd | |||
| d7532d5b83 | |||
| c7d805f8df | |||
| d78ab30cdf | |||
| c123f77195 | |||
| a60bc78e23 | |||
| 78ee107e85 | |||
| 12c3310451 | |||
| d525d8f603 | |||
| 337d1095dc | |||
| fab40cb6b3 | |||
| f4cdc1aecd | |||
| f702e3245a | |||
| d890ba4f43 | |||
| d929ed411f | |||
| 55daa28d74 | |||
| 2bab12aad1 | |||
| 80fb376132 | |||
| 1d557f1065 | |||
| 30df3df8cf | |||
| 02b4655a16 | |||
| debf0158d4 | |||
| 0f730c1948 | |||
| fa7e324fe0 | |||
| e2313e6a3d | |||
| 2ce8c42323 | |||
| 684e65adaa | |||
| a8fc3fb2f1 | |||
| 68bcdb2f88 | |||
| af3627bccb | |||
| 4cbe032363 | |||
| 5e4b3cf0d2 | |||
| 593af1048b | |||
| ae00ecb773 | |||
| da83e42172 | |||
| 2895d53f8e | |||
| 10d1f22ec3 | |||
| 4ed1e64a61 | |||
| 6402936285 | |||
| e01e5c145a | |||
| 56f6d82c8f | |||
| 3e79cf0c12 | |||
| efc09657a8 | |||
| 64390c3e01 | |||
| 513edb802d | |||
| 52c35b8e3c | |||
| cf1c25031c | |||
| 7b9f18867a | |||
| 0985b4dbe8 | |||
| 772fb721ae | |||
| 1ef1c795c1 | |||
| 6d956566c5 | |||
| edec5bb25f | |||
| 3534f65f0e | |||
| 6fbea9c8a4 | |||
| 18bba5da3d | |||
| 952270e1ba | |||
| 0c180e6ee5 | |||
| fdd9c329b1 | |||
| 5b80459b37 | |||
| 1b646a6e00 | |||
| cf970efbec | |||
| 4020cdac69 | |||
| 2415c2ffe8 | |||
| d41502ea19 | |||
| f5d0ef4005 | |||
| c4a89ecf61 | |||
| 71eb25e561 | |||
| 486a6683fd | |||
| 2175d8d87c | |||
| f463bff49e | |||
| a817e2b046 | |||
| 3539e36bde | |||
| 6930a9ffe1 | |||
| e0a44502b1 | |||
| 19e1bd5586 | |||
| ea65ba02f1 | |||
| 8406e48009 | |||
| a8f1d11cbc | |||
| f245a4a9c6 | |||
| 597529a16d | |||
| 9b9dba05c2 | |||
| 2d0bbc824f | |||
| fa523c673f | |||
| da1a4ba393 | |||
| e9c9712e52 | |||
| 78561ca659 | |||
| 1ad87383e3 | |||
| c29ac1549f | |||
| f09eeb19e5 | |||
| 4b7b2c78a2 | |||
| 414af6bdb0 | |||
| df7288b275 | |||
| ce80953086 | |||
| d1344dab71 | |||
| 4f2ba4ae76 | |||
| 6bd525c3b4 | |||
| 341671a612 | |||
| 9161aa1556 | |||
| 70e9fbabce | |||
| a2c00deba2 | |||
| 9b4ef9d1d7 | |||
| 3baee0c14e | |||
| 0508bf43d1 | |||
| bd279d1ad7 | |||
| 4d55a66307 | |||
| 9481e01581 | |||
| fe91bf9620 | |||
| d812ba379b | |||
| e1cc7f3c83 | |||
| f50d5d2cd1 | |||
| b3987b0ad5 | |||
| 243946a189 | |||
| 63fbbc5603 | |||
| fcb857df69 | |||
| dabbc31c09 | |||
| 997bfa3dd5 | |||
| 50eca8aab5 | |||
| 6e4a2e4d5e | |||
| 40e9938885 | |||
| 5bc8ca2266 | |||
| 4cdbfbfb86 | |||
| 01575d1f2e | |||
| 8f75c75b80 | |||
| ebc084f9ea | |||
| 626efaf5cf | |||
| 096ce99588 | |||
| 9ce103b31b | |||
| 13cbd84b58 | |||
| a8912dff41 | |||
| 8b773614ba | |||
| d04f04f382 | |||
| 9a44dae4f9 | |||
| 07665dd93e | |||
| eeca17e78b | |||
| f91264bc94 | |||
| 10b7f63f9f | |||
| f2dff2d173 | |||
| aa49605ac4 |
@ -11,5 +11,4 @@ RUN rm /usr/bin/python && ln -s /usr/bin/python2 /usr/bin/python
|
||||
ADD . /build_tools
|
||||
WORKDIR /build_tools
|
||||
|
||||
CMD cd tools/linux && \
|
||||
python3 ./automate.py
|
||||
CMD ["sh", "-c", "cd tools/linux && python3 ./automate.py"]
|
||||
|
||||
@ -27,7 +27,7 @@ parser.add_option("--db-pass", action="store", type="string", dest="db-pass", de
|
||||
parser.add_option("--compiler", action="store", type="string", dest="compiler", default="", help="defines compiler name. It is not recommended to use it as it's defined automatically (msvc2015, msvc2015_64, gcc, gcc_64, clang, clang_64, etc)")
|
||||
parser.add_option("--no-apps", action="store", type="string", dest="no-apps", default="0", help="disables building desktop apps that use qt")
|
||||
parser.add_option("--themesparams", action="store", type="string", dest="themesparams", default="", help="provides settings for generating presentation themes thumbnails")
|
||||
parser.add_option("--git-protocol", action="store", type="string", dest="git-protocol", default="https", help="can be used only if update is set to true - 'https', 'ssh'")
|
||||
parser.add_option("--git-protocol", action="store", type="string", dest="git-protocol", default="auto", help="can be used only if update is set to true - 'https', 'ssh'")
|
||||
parser.add_option("--branding", action="store", type="string", dest="branding", default="", help="provides branding path")
|
||||
parser.add_option("--branding-name", action="store", type="string", dest="branding-name", default="", help="provides branding name")
|
||||
parser.add_option("--branding-url", action="store", type="string", dest="branding-url", default="", help="provides branding url")
|
||||
|
||||
2
make.py
2
make.py
@ -92,6 +92,8 @@ if config.check_option("module", "desktop"):
|
||||
config.extend_option("config", "updmodule")
|
||||
base.set_env("DESKTOP_URL_UPDATES_MAIN_CHANNEL", "https://download.onlyoffice.com/install/desktop/editors/windows/onlyoffice/appcast.json")
|
||||
base.set_env("DESKTOP_URL_UPDATES_DEV_CHANNEL", "https://download.onlyoffice.com/install/desktop/editors/windows/onlyoffice/appcastdev.json")
|
||||
base.set_env("DESKTOP_URL_INSTALL_CHANNEL", "https://download.onlyoffice.com/install/desktop/editors/windows/distrib/onlyoffice/<file>")
|
||||
base.set_env("DESKTOP_URL_INSTALL_DEV_CHANNEL", "https://download.onlyoffice.com/install/desktop/editors/windows/onlyoffice/onlineinstallerdev/<file>")
|
||||
|
||||
# build
|
||||
build_sln.make()
|
||||
|
||||
@ -10,15 +10,17 @@ import package_utils as utils
|
||||
# parse
|
||||
parser = argparse.ArgumentParser(description="Build packages.")
|
||||
parser.add_argument("-P", "--platform", dest="platform", type=str,
|
||||
action="store", help="Defines platform", required=True)
|
||||
parser.add_argument("-T", "--targets", dest="targets", type=str, nargs="+",
|
||||
action="store", help="Defines targets", required=True)
|
||||
parser.add_argument("-R", "--branding", dest="branding", type=str,
|
||||
action="store", help="Provides branding path")
|
||||
action="store", help="Defines platform", required=True)
|
||||
parser.add_argument("-T", "--targets", dest="targets", type=str, nargs="+",
|
||||
action="store", help="Defines targets", required=True)
|
||||
parser.add_argument("-V", "--version", dest="version", type=str,
|
||||
action="store", help="Defines version")
|
||||
action="store", help="Defines version")
|
||||
parser.add_argument("-B", "--build", dest="build", type=str,
|
||||
action="store", help="Defines build")
|
||||
action="store", help="Defines build")
|
||||
parser.add_argument("-H", "--branch", dest="branch", type=str,
|
||||
action="store", help="Defines branch")
|
||||
parser.add_argument("-R", "--branding", dest="branding", type=str,
|
||||
action="store", help="Provides branding path")
|
||||
args = parser.parse_args()
|
||||
|
||||
# vars
|
||||
@ -29,13 +31,20 @@ common.targets = args.targets
|
||||
common.clean = "clean" in args.targets
|
||||
common.sign = "sign" in args.targets
|
||||
common.deploy = "deploy" in args.targets
|
||||
common.version = args.version if args.version else utils.get_env("BUILD_VERSION", "0.0.0")
|
||||
common.build = args.build if args.build else utils.get_env("BUILD_NUMBER", "0")
|
||||
if args.version: common.version = args.version
|
||||
else: common.version = utils.get_env("PRODUCT_VERSION", "0.0.0")
|
||||
utils.set_env("PRODUCT_VERSION", common.version)
|
||||
utils.set_env("BUILD_VERSION", common.version)
|
||||
if args.build: common.build = args.build
|
||||
else: common.build = utils.get_env("BUILD_NUMBER", "0")
|
||||
utils.set_env("BUILD_NUMBER", common.build)
|
||||
if args.branch: common.branch = args.branch
|
||||
else: common.branch = utils.get_env("BRANCH_NAME", "null")
|
||||
utils.set_env("BRANCH_NAME", common.branch)
|
||||
common.branding = args.branding
|
||||
common.timestamp = utils.get_timestamp()
|
||||
common.workspace_dir = utils.get_abspath(utils.get_script_dir(__file__) + "/..")
|
||||
common.branding_dir = utils.get_abspath(common.workspace_dir + "/" + args.branding) if args.branding else common.workspace_dir
|
||||
common.deploy_data = utils.get_path(common.workspace_dir + "/deploy.txt")
|
||||
common.summary = []
|
||||
utils.log("os_family: " + common.os_family)
|
||||
utils.log("platform: " + str(common.platform))
|
||||
@ -64,15 +73,14 @@ import package_mobile
|
||||
|
||||
# build
|
||||
utils.set_cwd(common.workspace_dir, verbose=True)
|
||||
utils.delete_file(common.deploy_data)
|
||||
if "core" in common.targets:
|
||||
package_core.make()
|
||||
if "closuremaps_opensource" in common.targets:
|
||||
if "closuremaps_sdkjs_opensource" in common.targets:
|
||||
package_core.deploy_closuremaps_sdkjs("opensource")
|
||||
package_core.deploy_closuremaps_webapps("opensource")
|
||||
if "closuremaps_commercial" in common.targets:
|
||||
if "closuremaps_sdkjs_commercial" in common.targets:
|
||||
package_core.deploy_closuremaps_sdkjs("commercial")
|
||||
package_core.deploy_closuremaps_webapps("commercial")
|
||||
if "closuremaps_webapps" in common.targets:
|
||||
package_core.deploy_closuremaps_webapps("opensource")
|
||||
if "desktop" in common.targets:
|
||||
package_desktop.make()
|
||||
if "builder" in common.targets:
|
||||
@ -83,6 +91,8 @@ if "server_enterprise" in common.targets:
|
||||
package_server.make("enterprise")
|
||||
if "server_developer" in common.targets:
|
||||
package_server.make("developer")
|
||||
if "server_prerequisites" in common.targets:
|
||||
package_server.make("prerequisites")
|
||||
if "mobile" in common.targets:
|
||||
package_mobile.make()
|
||||
|
||||
|
||||
131
scripts/base.py
131
scripts/base.py
@ -39,6 +39,9 @@ def is_os_arm():
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_platform():
|
||||
return platform.machine().lower()
|
||||
|
||||
def is_python_64bit():
|
||||
return (struct.calcsize("P") == 8)
|
||||
|
||||
@ -172,6 +175,13 @@ def find_file(path, pattern):
|
||||
for filename in fnmatch.filter(filenames, pattern):
|
||||
return os.path.join(root, filename)
|
||||
|
||||
def find_files(path, pattern):
|
||||
result = []
|
||||
for root, dirnames, filenames in os.walk(path):
|
||||
for filename in fnmatch.filter(filenames, pattern):
|
||||
result.append(os.path.join(root, filename))
|
||||
return result
|
||||
|
||||
def create_dir(path):
|
||||
path2 = get_path(path)
|
||||
if not os.path.exists(path2):
|
||||
@ -367,7 +377,7 @@ def cmd(prog, args=[], is_no_errors=False):
|
||||
else:
|
||||
command = prog
|
||||
for arg in args:
|
||||
command += (" \"" + arg + "\"")
|
||||
command += (" \"" + arg.replace('\"', '\\\"') + "\"")
|
||||
ret = subprocess.call(command, stderr=subprocess.STDOUT, shell=True)
|
||||
if ret != 0 and True != is_no_errors:
|
||||
sys.exit("Error (" + prog + "): " + str(ret))
|
||||
@ -403,7 +413,7 @@ def cmd_exe(prog, args, is_no_errors=False):
|
||||
else:
|
||||
command = prog
|
||||
for arg in args:
|
||||
command += (" \"" + arg + "\"")
|
||||
command += (" \"" + arg.replace('\"', '\\\"') + "\"")
|
||||
process = subprocess.Popen(command, stderr=subprocess.STDOUT, shell=True, env=env_dir)
|
||||
ret = process.wait()
|
||||
if ret != 0 and True != is_no_errors:
|
||||
@ -426,12 +436,13 @@ def cmd_and_return_cwd(prog, args=[], is_no_errors=False):
|
||||
|
||||
def run_command(sCommand):
|
||||
popen = subprocess.Popen(sCommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
result = {'stdout' : '', 'stderr' : ''}
|
||||
result = {'stdout' : '', 'stderr' : '', 'returncode' : 0}
|
||||
try:
|
||||
stdout, stderr = popen.communicate()
|
||||
popen.wait()
|
||||
result['stdout'] = stdout.strip().decode('utf-8', errors='ignore')
|
||||
result['stderr'] = stderr.strip().decode('utf-8', errors='ignore')
|
||||
result['returncode'] = popen.returncode
|
||||
finally:
|
||||
popen.stdout.close()
|
||||
popen.stderr.close()
|
||||
@ -496,12 +507,37 @@ def set_cwd(dir):
|
||||
return
|
||||
|
||||
# git ---------------------------------------------------
|
||||
def git_get_origin():
|
||||
cur_dir = os.getcwd()
|
||||
os.chdir(get_script_dir() + "/../")
|
||||
ret = run_command("git config --get remote.origin.url")["stdout"]
|
||||
os.chdir(cur_dir)
|
||||
return ret
|
||||
|
||||
def git_is_ssh():
|
||||
git_protocol = config.option("git-protocol")
|
||||
if (git_protocol == "https"):
|
||||
return False
|
||||
if (git_protocol == "ssh"):
|
||||
return True
|
||||
origin = git_get_origin()
|
||||
if (git_protocol == "auto") and (origin.find(":ONLYOFFICE/") != -1):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_ssh_base_url():
|
||||
cur_origin = git_get_origin()
|
||||
ind = cur_origin.find(":ONLYOFFICE/")
|
||||
if (ind == -1):
|
||||
return "git@github.com:ONLYOFFICE/"
|
||||
return cur_origin[:ind+12]
|
||||
|
||||
def git_update(repo, is_no_errors=False, is_current_dir=False, git_owner=""):
|
||||
print("[git] update: " + repo)
|
||||
owner = git_owner if git_owner else "ONLYOFFICE"
|
||||
url = "https://github.com/" + owner + "/" + repo + ".git"
|
||||
if config.option("git-protocol") == "ssh":
|
||||
url = "git@github.com:ONLYOFFICE/" + repo + ".git"
|
||||
if git_is_ssh():
|
||||
url = get_ssh_base_url() + repo + ".git"
|
||||
folder = get_script_dir() + "/../../" + repo
|
||||
if is_current_dir:
|
||||
folder = repo
|
||||
@ -534,10 +570,12 @@ def get_repositories():
|
||||
result.update(get_sdkjs_addons())
|
||||
result["onlyoffice.github.io"] = [False, False]
|
||||
result["web-apps"] = [False, False]
|
||||
result.update(get_web_apps_addons())
|
||||
result["dictionaries"] = [False, False]
|
||||
result["core-fonts"] = [False, False]
|
||||
|
||||
if config.check_option("module", "server"):
|
||||
result.update(get_web_apps_addons())
|
||||
|
||||
if config.check_option("module", "builder"):
|
||||
result["document-templates"] = [False, False]
|
||||
|
||||
@ -570,8 +608,8 @@ def get_branding_repositories(checker):
|
||||
def create_pull_request(branches_to, repo, is_no_errors=False, is_current_dir=False):
|
||||
print("[git] create pull request: " + repo)
|
||||
url = "https://github.com/ONLYOFFICE/" + repo + ".git"
|
||||
if config.option("git-protocol") == "ssh":
|
||||
url = "git@github.com:ONLYOFFICE/" + repo + ".git"
|
||||
if git_is_ssh():
|
||||
url = get_ssh_base_url() + repo + ".git"
|
||||
folder = get_script_dir() + "/../../" + repo
|
||||
if is_current_dir:
|
||||
folder = repo
|
||||
@ -738,8 +776,9 @@ def qt_config(platform):
|
||||
if (-1 != platform.find("xp")):
|
||||
config_param += " build_xp"
|
||||
if ("ios" == platform):
|
||||
set_env("BITCODE_GENERATION_MODE", "bitcode")
|
||||
set_env("ENABLE_BITCODE", "YES")
|
||||
if (config.check_option("bitcode", "yes")):
|
||||
set_env("BITCODE_GENERATION_MODE", "bitcode")
|
||||
set_env("ENABLE_BITCODE", "YES")
|
||||
config_param = config_param.replace("desktop", "")
|
||||
config_param += " iphoneos device"
|
||||
if (-1 == config_param_lower.find("debug")):
|
||||
@ -1187,18 +1226,19 @@ def mac_correct_rpath_x2t(dir):
|
||||
mac_correct_rpath_library("kernel", ["UnicodeConverter"])
|
||||
mac_correct_rpath_library("kernel_network", ["UnicodeConverter", "kernel"])
|
||||
mac_correct_rpath_library("graphics", ["UnicodeConverter", "kernel"])
|
||||
mac_correct_rpath_library("doctrenderer", ["UnicodeConverter", "kernel", "kernel_network", "graphics"])
|
||||
mac_correct_rpath_library("doctrenderer", ["UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "XpsFile", "DjVuFile", "DocxRenderer"])
|
||||
mac_correct_rpath_library("HtmlFile2", ["UnicodeConverter", "kernel", "kernel_network", "graphics"])
|
||||
mac_correct_rpath_library("EpubFile", ["UnicodeConverter", "kernel", "HtmlFile2", "graphics"])
|
||||
mac_correct_rpath_library("Fb2File", ["UnicodeConverter", "kernel", "graphics"])
|
||||
mac_correct_rpath_library("HtmlRenderer", ["UnicodeConverter", "kernel", "graphics"])
|
||||
mac_correct_rpath_library("PdfFile", ["UnicodeConverter", "kernel", "graphics", "kernel_network"])
|
||||
mac_correct_rpath_library("DjVuFile", ["UnicodeConverter", "kernel", "graphics", "PdfFile"])
|
||||
mac_correct_rpath_library("XpsFile", ["UnicodeConverter", "kernel", "graphics", "PdfFile"])
|
||||
mac_correct_rpath_library("DocxRenderer", ["UnicodeConverter", "kernel", "graphics"])
|
||||
mac_correct_rpath_library("IWorkFile", ["UnicodeConverter", "kernel"])
|
||||
mac_correct_rpath_library("HWPFile", ["UnicodeConverter", "kernel", "graphics"])
|
||||
cmd("chmod", ["-v", "+x", "./x2t"])
|
||||
cmd("install_name_tool", ["-add_rpath", "@executable_path", "./x2t"], True)
|
||||
mac_correct_rpath_binary("./x2t", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "HtmlRenderer", "XpsFile", "DjVuFile", "HtmlFile2", "Fb2File", "EpubFile", "doctrenderer", "DocxRenderer"])
|
||||
mac_correct_rpath_binary("./x2t", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "XpsFile", "DjVuFile", "HtmlFile2", "Fb2File", "EpubFile", "doctrenderer", "DocxRenderer", "IWorkFile", "HWPFile"])
|
||||
if is_file("./allfontsgen"):
|
||||
cmd("chmod", ["-v", "+x", "./allfontsgen"])
|
||||
cmd("install_name_tool", ["-add_rpath", "@executable_path", "./allfontsgen"], True)
|
||||
@ -1206,7 +1246,7 @@ def mac_correct_rpath_x2t(dir):
|
||||
if is_file("./allthemesgen"):
|
||||
cmd("chmod", ["-v", "+x", "./allthemesgen"])
|
||||
cmd("install_name_tool", ["-add_rpath", "@executable_path", "./allthemesgen"], True)
|
||||
mac_correct_rpath_binary("./allthemesgen", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "graphics", "kernel_network", "doctrenderer"])
|
||||
mac_correct_rpath_binary("./allthemesgen", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "graphics", "kernel_network", "doctrenderer", "PdfFile", "XpsFile", "DjVuFile", "DocxRenderer"])
|
||||
if is_file("./pluginsmanager"):
|
||||
cmd("chmod", ["-v", "+x", "./pluginsmanager"])
|
||||
cmd("install_name_tool", ["-add_rpath", "@executable_path", "./pluginsmanager"], True)
|
||||
@ -1223,9 +1263,14 @@ def mac_correct_rpath_docbuilder(dir):
|
||||
os.chdir(dir)
|
||||
cmd("chmod", ["-v", "+x", "./docbuilder"])
|
||||
cmd("install_name_tool", ["-add_rpath", "@executable_path", "./docbuilder"], True)
|
||||
mac_correct_rpath_binary("./docbuilder", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "HtmlRenderer", "XpsFile", "DjVuFile", "HtmlFile2", "Fb2File", "EpubFile", "doctrenderer", "DocxRenderer"])
|
||||
mac_correct_rpath_library("docbuilder.c", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "doctrenderer"])
|
||||
cmd("install_name_tool", ["-add_rpath", "@loader_path", "libdocbuilder.c.dylib"], True)
|
||||
mac_correct_rpath_binary("./docbuilder", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "XpsFile", "DjVuFile", "HtmlFile2", "Fb2File", "EpubFile", "IWorkFile", "HWPFile", "doctrenderer", "DocxRenderer"])
|
||||
mac_correct_rpath_library("docbuilder.c", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "doctrenderer", "PdfFile", "XpsFile", "DjVuFile", "DocxRenderer"])
|
||||
|
||||
def add_loader_path_to_rpath(libs):
|
||||
for lib in libs:
|
||||
cmd("install_name_tool", ["-add_rpath", "@loader_path", "lib" + lib + ".dylib"], True)
|
||||
|
||||
add_loader_path_to_rpath(["icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "doctrenderer", "PdfFile", "XpsFile", "DjVuFile", "DocxRenderer", "docbuilder.c"])
|
||||
os.chdir(cur_dir)
|
||||
return
|
||||
|
||||
@ -1235,9 +1280,9 @@ def mac_correct_rpath_desktop(dir):
|
||||
os.chdir(dir)
|
||||
mac_correct_rpath_library("hunspell", [])
|
||||
mac_correct_rpath_library("ooxmlsignature", ["kernel"])
|
||||
mac_correct_rpath_library("ascdocumentscore", ["UnicodeConverter", "kernel", "graphics", "kernel_network", "PdfFile", "HtmlRenderer", "XpsFile", "DjVuFile", "hunspell", "ooxmlsignature"])
|
||||
mac_correct_rpath_library("ascdocumentscore", ["UnicodeConverter", "kernel", "graphics", "kernel_network", "PdfFile", "XpsFile", "DjVuFile", "hunspell", "ooxmlsignature"])
|
||||
cmd("install_name_tool", ["-change", "@executable_path/../Frameworks/Chromium Embedded Framework.framework/Chromium Embedded Framework", "@rpath/Chromium Embedded Framework.framework/Chromium Embedded Framework", "libascdocumentscore.dylib"])
|
||||
mac_correct_rpath_binary("./editors_helper.app/Contents/MacOS/editors_helper", ["ascdocumentscore", "UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "HtmlRenderer", "XpsFile", "DjVuFile", "hunspell", "ooxmlsignature"])
|
||||
mac_correct_rpath_binary("./editors_helper.app/Contents/MacOS/editors_helper", ["ascdocumentscore", "UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "XpsFile", "DjVuFile", "hunspell", "ooxmlsignature"])
|
||||
cmd("install_name_tool", ["-add_rpath", "@executable_path/../../../../Frameworks", "./editors_helper.app/Contents/MacOS/editors_helper"], True)
|
||||
cmd("install_name_tool", ["-add_rpath", "@executable_path/../../../../Resources/converter", "./editors_helper.app/Contents/MacOS/editors_helper"], True)
|
||||
cmd("chmod", ["-v", "+x", "./editors_helper.app/Contents/MacOS/editors_helper"])
|
||||
@ -1271,7 +1316,7 @@ def linux_set_origin_rpath_libraries(dir, libs):
|
||||
return
|
||||
|
||||
def linux_correct_rpath_docbuilder(dir):
|
||||
linux_set_origin_rpath_libraries(dir, ["docbuilder.c.so", "icuuc.so.58", "doctrenderer.so", "graphics.so", "kernel.so", "kernel_network.so", "UnicodeConverter.so"])
|
||||
linux_set_origin_rpath_libraries(dir, ["docbuilder.jni.so", "docbuilder.c.so", "icuuc.so.58", "doctrenderer.so", "graphics.so", "kernel.so", "kernel_network.so", "UnicodeConverter.so", "PdfFile.so", "XpsFile.so", "DjVuFile.so", "DocxRenderer.so"])
|
||||
return
|
||||
|
||||
def common_check_version(name, good_version, clean_func):
|
||||
@ -1327,7 +1372,7 @@ def copy_marketplace_plugin(dst_dir, is_name_as_guid=False, is_desktop_local=Fal
|
||||
git_dir = __file__script__path__ + "/../.."
|
||||
if False:
|
||||
# old version
|
||||
base.copy_sdkjs_plugin(git_dir + "/desktop-sdk/ChromiumBasedEditors/plugins", dst_dir, "manager", is_name_as_guid, is_desktop_local)
|
||||
copy_sdkjs_plugin(git_dir + "/desktop-sdk/ChromiumBasedEditors/plugins", dst_dir, "manager", is_name_as_guid, is_desktop_local)
|
||||
return
|
||||
src_dir_path = git_dir + "/onlyoffice.github.io/store/plugin"
|
||||
name = "marketplace"
|
||||
@ -1393,6 +1438,7 @@ def support_old_versions_plugins(out_dir):
|
||||
def generate_sdkjs_plugin_list(dst):
|
||||
plugins_list = config.option("sdkjs-plugin").rsplit(", ") \
|
||||
+ config.option("sdkjs-plugin-server").rsplit(", ")
|
||||
plugins_list = list(filter(None, plugins_list))
|
||||
with open(get_path(dst), 'w') as file:
|
||||
dump = json.dumps(sorted(plugins_list), indent=4)
|
||||
file.write(re.sub(r"^(\s{4})", '\t', dump, 0, re.MULTILINE))
|
||||
@ -1754,3 +1800,46 @@ def apply_patch(file, patch):
|
||||
#file_content_new = "\n#if 0" + file_content_old + "#else" + file_content_new + "#endif\n"
|
||||
replaceInFile(file, file_content_old, file_content_new)
|
||||
return
|
||||
|
||||
def get_autobuild_version(product, platform="", branch="", build=""):
|
||||
download_platform = platform
|
||||
if ("" == download_platform):
|
||||
osType = get_platform()
|
||||
isArm = True if (-1 != osType.find("arm")) else False
|
||||
is64 = True if (osType.endswith("64")) else False
|
||||
|
||||
if ("windows" == host_platform()):
|
||||
download_platform = "win-"
|
||||
elif ("linux" == host_platform()):
|
||||
download_platform = "linux-"
|
||||
else:
|
||||
download_platform = "mac-"
|
||||
|
||||
download_platform += ("arm" if isArm else "")
|
||||
download_platform += ("64" if is64 else "32")
|
||||
else:
|
||||
download_platform = download_platform.replace("_", "-")
|
||||
|
||||
download_build = build
|
||||
if ("" == download_build):
|
||||
download_build = "latest"
|
||||
|
||||
download_branch = branch
|
||||
if ("" == download_branch):
|
||||
download_branch = "develop"
|
||||
|
||||
download_addon = download_branch + "/" + download_build + "/" + product + "-" + download_platform + ".7z"
|
||||
return "http://repo-doc-onlyoffice-com.s3.amazonaws.com/archive/" + download_addon
|
||||
|
||||
def create_x2t_js_cache(dir, product):
|
||||
if is_file(dir + "/libdoctrenderer.dylib") and (os.path.getsize(dir + "/libdoctrenderer.dylib") < 5*1024*1024):
|
||||
return
|
||||
|
||||
cmd_in_dir(dir, "./x2t", ["-create-js-snapshots"], True)
|
||||
return
|
||||
|
||||
def setup_local_qmake(dir_qmake):
|
||||
dir_base = os.path.dirname(dir_qmake)
|
||||
writeFile(dir_base + "/onlyoffice_qt.conf", "Prefix = " + dir_base)
|
||||
return
|
||||
|
||||
@ -41,6 +41,7 @@ def make():
|
||||
base.create_dir(out_dir)
|
||||
|
||||
# builder
|
||||
base.cmd_in_dir(base_dir + "/../web-apps/translation", "python", ["merge_and_check.py"])
|
||||
build_interface(base_dir + "/../web-apps/build")
|
||||
build_sdk_builder(base_dir + "/../sdkjs/build")
|
||||
base.create_dir(out_dir + "/builder")
|
||||
@ -55,12 +56,14 @@ def make():
|
||||
base.copy_dir(base_dir + "/../sdkjs/deploy/sdkjs", out_dir + "/desktop/sdkjs")
|
||||
correct_sdkjs_licence(out_dir + "/desktop/sdkjs")
|
||||
base.copy_dir(base_dir + "/../web-apps/deploy/web-apps", out_dir + "/desktop/web-apps")
|
||||
base.delete_dir(out_dir + "/desktop/web-apps/apps/documenteditor/embed")
|
||||
base.delete_dir(out_dir + "/desktop/web-apps/apps/documenteditor/mobile")
|
||||
base.delete_dir(out_dir + "/desktop/web-apps/apps/presentationeditor/embed")
|
||||
base.delete_dir(out_dir + "/desktop/web-apps/apps/presentationeditor/mobile")
|
||||
base.delete_dir(out_dir + "/desktop/web-apps/apps/spreadsheeteditor/embed")
|
||||
base.delete_dir(out_dir + "/desktop/web-apps/apps/spreadsheeteditor/mobile")
|
||||
|
||||
deldirs = ['ie', 'mobile', 'embed']
|
||||
[base.delete_dir(root + "/" + d) for root, dirs, f in os.walk(out_dir + "/desktop/web-apps/apps") for d in dirs if d in deldirs]
|
||||
|
||||
# for bug 62528. remove empty folders
|
||||
walklist = list(os.walk(out_dir + "/desktop/sdkjs"))
|
||||
[os.remove(p) for p, _, _ in walklist[::-1] if len(os.listdir(p)) == 0]
|
||||
|
||||
base.copy_file(base_dir + "/../web-apps/apps/api/documents/index.html.desktop", out_dir + "/desktop/web-apps/apps/api/documents/index.html")
|
||||
|
||||
build_interface(base_dir + "/../desktop-apps/common/loginpage/build")
|
||||
@ -168,6 +171,7 @@ def build_js_develop(root_dir):
|
||||
_run_npm(root_dir + external_folder + "/web-apps/build")
|
||||
_run_npm_ci(root_dir + external_folder + "/web-apps/build/sprites")
|
||||
_run_grunt(root_dir + external_folder + "/web-apps/build/sprites", [])
|
||||
base.cmd_in_dir(root_dir + external_folder + "/web-apps/translation", "python", ["merge_and_check.py"])
|
||||
|
||||
old_cur = os.getcwd()
|
||||
old_product_version = base.get_env("PRODUCT_VERSION")
|
||||
|
||||
@ -36,14 +36,28 @@ def make(solution=""):
|
||||
|
||||
if config.check_option("module", "builder") and base.is_windows() and "onlyoffice" == config.branding():
|
||||
# check replace
|
||||
new_replace_path = base.correctPathForBuilder(os.getcwd() + "/../core/DesktopEditor/doctrenderer/docbuilder.com/src/docbuilder.h")
|
||||
if ("2019" == config.option("vs-version")):
|
||||
base.make_sln_project("../core/DesktopEditor/doctrenderer/docbuilder.com/src", "docbuilder.com_2019.sln")
|
||||
if (True):
|
||||
new_path_net = base.correctPathForBuilder(os.getcwd() + "/../core/DesktopEditor/doctrenderer/docbuilder.net/src/docbuilder.net.cpp")
|
||||
base.make_sln_project("../core/DesktopEditor/doctrenderer/docbuilder.net/src", "docbuilder.net.sln")
|
||||
base.restorePathForBuilder(new_path_net)
|
||||
else:
|
||||
base.make_sln_project("../core/DesktopEditor/doctrenderer/docbuilder.com/src", "docbuilder.com.sln")
|
||||
base.restorePathForBuilder(new_replace_path)
|
||||
directory_builder_branding = os.getcwd() + "/../core/DesktopEditor/doctrenderer"
|
||||
if base.is_dir(directory_builder_branding):
|
||||
new_replace_path = base.correctPathForBuilder(directory_builder_branding + "/docbuilder.com/src/docbuilder.h")
|
||||
if ("2019" == config.option("vs-version")):
|
||||
base.make_sln_project("../core/DesktopEditor/doctrenderer/docbuilder.com/src", "docbuilder.com_2019.sln")
|
||||
if (True):
|
||||
new_path_net = base.correctPathForBuilder(directory_builder_branding + "/docbuilder.net/src/docbuilder.net.cpp")
|
||||
base.make_sln_project("../core/DesktopEditor/doctrenderer/docbuilder.net/src", "docbuilder.net.sln")
|
||||
base.restorePathForBuilder(new_path_net)
|
||||
else:
|
||||
base.make_sln_project("../core/DesktopEditor/doctrenderer/docbuilder.com/src", "docbuilder.com.sln")
|
||||
base.restorePathForBuilder(new_replace_path)
|
||||
|
||||
# build Java docbuilder wrapper
|
||||
if config.check_option("module", "builder") and "onlyoffice" == config.branding():
|
||||
for platform in platforms:
|
||||
if not platform in config.platforms:
|
||||
continue
|
||||
|
||||
# build JNI library
|
||||
qmake.make(platform, base.get_script_dir() + "/../../core/DesktopEditor/doctrenderer/docbuilder.java/src/jni/docbuilder_jni.pro", "", True)
|
||||
# build Java code to JAR
|
||||
base.cmd_in_dir(base.get_script_dir() + "/../../core/DesktopEditor/doctrenderer/docbuilder.java", "python", ["make.py"])
|
||||
|
||||
return
|
||||
|
||||
@ -16,6 +16,7 @@ import curl
|
||||
import websocket_all
|
||||
import v8
|
||||
import html2
|
||||
import iwork
|
||||
import hunspell
|
||||
import glew
|
||||
import harfbuzz
|
||||
@ -42,6 +43,7 @@ def make():
|
||||
openssl.make()
|
||||
v8.make()
|
||||
html2.make()
|
||||
iwork.make(False)
|
||||
hunspell.make(False)
|
||||
harfbuzz.make()
|
||||
glew.make()
|
||||
|
||||
@ -3,6 +3,11 @@ sys.path.append('../../../scripts')
|
||||
import base
|
||||
import os
|
||||
|
||||
def clean():
|
||||
if base.is_dir("hunspell"):
|
||||
base.delete_dir_with_access_error("hunspell")
|
||||
return
|
||||
|
||||
def make(build_js = True):
|
||||
|
||||
old_cur_dir = os.getcwd()
|
||||
@ -11,6 +16,8 @@ def make(build_js = True):
|
||||
core_common_dir = base.get_script_dir() + "/../../core/Common"
|
||||
|
||||
os.chdir(core_common_dir + "/3dParty/hunspell")
|
||||
|
||||
base.common_check_version("hunspell", "1", clean)
|
||||
base.cmd("python", ["./before.py"])
|
||||
|
||||
if (build_js):
|
||||
|
||||
38
scripts/core_common/modules/iwork.py
Normal file
38
scripts/core_common/modules/iwork.py
Normal file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
sys.path.append('../..')
|
||||
import config
|
||||
import base
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
def clear_module():
|
||||
directories = ["glm", "libetonyek", "libodfgen", "librevenge", "mdds"]
|
||||
|
||||
for dir in directories:
|
||||
if base.is_dir(dir):
|
||||
base.delete_dir_with_access_error(dir)
|
||||
|
||||
def make(use_gperf = True):
|
||||
old_cur_dir = os.getcwd()
|
||||
|
||||
print("[fetch & build]: iwork")
|
||||
|
||||
base_dir = base.get_script_dir() + "/../../core/Common/3dParty/apple"
|
||||
|
||||
os.chdir(base_dir)
|
||||
base.check_module_version("3", clear_module)
|
||||
os.chdir(old_cur_dir)
|
||||
|
||||
cmd_args = ["fetch.py"]
|
||||
|
||||
if use_gperf:
|
||||
cmd_args.append("--gperf")
|
||||
|
||||
base.cmd_in_dir(base_dir, "python", cmd_args)
|
||||
return
|
||||
|
||||
if __name__ == '__main__':
|
||||
# manual compile
|
||||
make(False)
|
||||
@ -119,7 +119,7 @@ def make():
|
||||
# windows hack (delete later) ----------------------
|
||||
if ("windows" == base.host_platform()):
|
||||
base.delete_dir_with_access_error("v8/buildtools/win")
|
||||
base.cmd("git", ["config", "--system", "core.longpaths", "true"])
|
||||
base.cmd("git", ["config", "--system", "core.longpaths", "true"], True)
|
||||
base.cmd("gclient", ["sync", "--force"], True)
|
||||
else:
|
||||
base.cmd("gclient", ["sync"], True)
|
||||
@ -246,7 +246,7 @@ def make_xp():
|
||||
base.cmd("./depot_tools/fetch", ["v8"], True)
|
||||
base.cmd("./depot_tools/gclient", ["sync", "-r", "4.10.253"], True)
|
||||
base.delete_dir_with_access_error("v8/buildtools/win")
|
||||
base.cmd("git", ["config", "--system", "core.longpaths", "true"])
|
||||
base.cmd("git", ["config", "--system", "core.longpaths", "true"], True)
|
||||
base.cmd("gclient", ["sync", "--force"], True)
|
||||
|
||||
# save common py script
|
||||
|
||||
@ -21,6 +21,12 @@ def change_bootstrap():
|
||||
content += "@Subdir git\n"
|
||||
content += "infra/3pp/tools/git/${platform} version:2@2.41.0.chromium.11\n"
|
||||
|
||||
base.replaceInFile("./depot_tools/bootstrap/bootstrap.py",
|
||||
"raise subprocess.CalledProcessError(proc.returncode, argv, None)", "return")
|
||||
|
||||
base.replaceInFile("./depot_tools/bootstrap/bootstrap.py",
|
||||
" _win_git_bootstrap_config()", " #_win_git_bootstrap_config()")
|
||||
|
||||
base.writeFile("./depot_tools/bootstrap/manifest.txt", content)
|
||||
return
|
||||
|
||||
@ -105,6 +111,9 @@ def make():
|
||||
if not base.is_dir(base_dir):
|
||||
base.create_dir(base_dir)
|
||||
|
||||
if ("mac" == base.host_platform()):
|
||||
base.cmd("git", ["config", "--global", "http.postBuffer", "157286400"], True)
|
||||
|
||||
os.chdir(base_dir)
|
||||
if not base.is_dir("depot_tools"):
|
||||
base.cmd("git", ["clone", "https://chromium.googlesource.com/chromium/tools/depot_tools.git"])
|
||||
@ -121,7 +130,7 @@ def make():
|
||||
base.copy_dir("./v8/third_party", "./v8/third_party_new")
|
||||
if ("windows" == base.host_platform()):
|
||||
os.chdir("v8")
|
||||
base.cmd("git", ["config", "--system", "core.longpaths", "true"])
|
||||
base.cmd("git", ["config", "--system", "core.longpaths", "true"], True)
|
||||
os.chdir("../")
|
||||
v8_branch_version = "remotes/branch-heads/8.9"
|
||||
if ("mac" == base.host_platform()):
|
||||
@ -137,6 +146,11 @@ def make():
|
||||
else:
|
||||
base.replaceInFile("depot_tools/gclient_paths.py", "@functools.lru_cache", "")
|
||||
|
||||
if ("mac" == base.host_platform()):
|
||||
if not base.is_file("v8/build/config/compiler/BUILD.gn.bak"):
|
||||
base.copy_file("v8/build/config/compiler/BUILD.gn", "v8/build/config/compiler/BUILD.gn.bak")
|
||||
base.replaceInFile("v8/build/config/compiler/BUILD.gn", "\"-Wloop-analysis\",", "\"-Wloop-analysis\", \"-D_Float16=short\",")
|
||||
|
||||
if not base.is_file("v8/third_party/jinja2/tests.py.bak"):
|
||||
base.copy_file("v8/third_party/jinja2/tests.py", "v8/third_party/jinja2/tests.py.bak")
|
||||
base.replaceInFile("v8/third_party/jinja2/tests.py", "from collections import Mapping", "try:\n from collections.abc import Mapping\nexcept ImportError:\n from collections import Mapping")
|
||||
|
||||
@ -40,9 +40,10 @@ def make():
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "DjVuFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "XpsFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "HtmlFile2")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "HtmlRenderer")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "Fb2File")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "EpubFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "IWorkFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "HWPFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "DocxRenderer")
|
||||
base.copy_file(git_dir + "/sdkjs/pdf/src/engine/cmap.bin", root_dir + "/cmap.bin")
|
||||
|
||||
@ -76,9 +77,12 @@ def make():
|
||||
if (0 == platform.find("win")):
|
||||
base.copy_file(core_build_dir + "/lib/" + platform_postfix + "/doctrenderer.lib", root_dir + "/doctrenderer.lib")
|
||||
base.copy_v8_files(core_dir, root_dir, platform, isWindowsXP)
|
||||
# python wrapper
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "docbuilder.c")
|
||||
|
||||
base.copy_file(core_dir + "/DesktopEditor/doctrenderer/docbuilder.python/src/docbuilder.py", root_dir + "/docbuilder.py")
|
||||
# java wrapper
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "docbuilder.jni")
|
||||
base.copy_file(core_dir + "/DesktopEditor/doctrenderer/docbuilder.java/build/libs/docbuilder.jar", root_dir + "/docbuilder.jar")
|
||||
|
||||
# app
|
||||
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, root_dir, "docbuilder")
|
||||
@ -120,6 +124,20 @@ def make():
|
||||
if (0 == platform.find("mac")):
|
||||
base.mac_correct_rpath_x2t(root_dir)
|
||||
base.mac_correct_rpath_docbuilder(root_dir)
|
||||
|
||||
base.create_x2t_js_cache(root_dir, "builder")
|
||||
|
||||
# delete unnecessary builder files
|
||||
def delete_files(files):
|
||||
for file in files:
|
||||
base.delete_file(file)
|
||||
|
||||
delete_files(base.find_files(root_dir, "*.wasm"))
|
||||
delete_files(base.find_files(root_dir, "*_ie.js"))
|
||||
base.delete_file(root_dir + "/sdkjs/pdf/src/engine/cmap.bin")
|
||||
if 0 != platform.find("mac"):
|
||||
delete_files(base.find_files(root_dir, "sdk-all.js"))
|
||||
delete_files(base.find_files(root_dir, "sdk-all-min.js"))
|
||||
|
||||
return
|
||||
|
||||
|
||||
@ -30,7 +30,6 @@ def make():
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "kernel_network")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "graphics")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "doctrenderer")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "HtmlRenderer")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "DjVuFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "XpsFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "PdfFile")
|
||||
@ -38,7 +37,10 @@ def make():
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "UnicodeConverter")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "Fb2File")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "EpubFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "IWorkFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "HWPFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "DocxRenderer")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "hunspell")
|
||||
base.copy_file(git_dir + "/sdkjs/pdf/src/engine/cmap.bin", archive_dir + "/cmap.bin")
|
||||
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, archive_dir, "x2t")
|
||||
|
||||
@ -61,13 +63,14 @@ def make():
|
||||
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, archive_dir, "ooxml_crypt")
|
||||
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, archive_dir, "vboxtester")
|
||||
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, archive_dir, "metafiletester")
|
||||
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, archive_dir, "dictionariestester")
|
||||
|
||||
# js cache
|
||||
base.generate_doctrenderer_config(archive_dir + "/DoctRenderer.config", "./", "builder", "", "./dictionaries")
|
||||
base.create_x2t_js_cache(archive_dir, "core")
|
||||
base.delete_file(archive_dir + "/DoctRenderer.config")
|
||||
|
||||
# dictionaries
|
||||
base.copy_dictionaries(git_dir + "/dictionaries", archive_dir + "/dictionaries", True, False)
|
||||
|
||||
if base.is_file(archive_dir + ".7z"):
|
||||
base.delete_file(archive_dir + ".7z")
|
||||
base.archive_folder(archive_dir + "/*", archive_dir + ".7z")
|
||||
|
||||
return
|
||||
|
||||
|
||||
@ -66,9 +66,10 @@ def make():
|
||||
base.copy_lib(build_libraries_path, root_dir + "/converter", "DjVuFile")
|
||||
base.copy_lib(build_libraries_path, root_dir + "/converter", "XpsFile")
|
||||
base.copy_lib(build_libraries_path, root_dir + "/converter", "HtmlFile2")
|
||||
base.copy_lib(build_libraries_path, root_dir + "/converter", "HtmlRenderer")
|
||||
base.copy_lib(build_libraries_path, root_dir + "/converter", "Fb2File")
|
||||
base.copy_lib(build_libraries_path, root_dir + "/converter", "EpubFile")
|
||||
base.copy_lib(build_libraries_path, root_dir + "/converter", "IWorkFile")
|
||||
base.copy_lib(build_libraries_path, root_dir + "/converter", "HWPFile")
|
||||
base.copy_lib(build_libraries_path, root_dir + "/converter", "DocxRenderer")
|
||||
|
||||
if ("ios" == platform):
|
||||
@ -102,6 +103,7 @@ def make():
|
||||
|
||||
base.generate_doctrenderer_config(root_dir + "/converter/DoctRenderer.config", "../editors/", "desktop", "", "../dictionaries")
|
||||
base.copy_dir(git_dir + "/document-templates/new", root_dir + "/converter/empty")
|
||||
base.copy_dir(git_dir + "/desktop-apps/common/templates", root_dir + "/converter/templates")
|
||||
|
||||
# dictionaries
|
||||
base.copy_dictionaries(git_dir + "/dictionaries", root_dir + "/dictionaries")
|
||||
@ -182,6 +184,8 @@ def make():
|
||||
base.copy_file(git_dir + "/desktop-apps/win-linux/extras/projicons/" + apps_postfix + "/projicons.exe", root_dir + "/DesktopEditors.exe")
|
||||
if not isWindowsXP:
|
||||
base.copy_file(git_dir + "/desktop-apps/win-linux/extras/update-daemon/" + apps_postfix + "/updatesvc.exe", root_dir + "/updatesvc.exe")
|
||||
else:
|
||||
base.copy_file(git_dir + "/desktop-apps/win-linux/extras/online-installer/" + apps_postfix + "/online-installer.exe", root_dir + "/online-installer.exe")
|
||||
base.copy_file(git_dir + "/desktop-apps/win-linux/" + apps_postfix + "/DesktopEditors.exe", root_dir + "/editors.exe")
|
||||
base.copy_file(git_dir + "/desktop-apps/win-linux/res/icons/desktopeditors.ico", root_dir + "/app.ico")
|
||||
elif (0 == platform.find("linux")):
|
||||
@ -213,8 +217,6 @@ def make():
|
||||
|
||||
base.create_dir(root_dir + "/editors")
|
||||
base.copy_dir(base_dir + "/js/" + branding + "/desktop/sdkjs", root_dir + "/editors/sdkjs")
|
||||
if len(os.listdir(root_dir + "/editors/sdkjs")) == 0:
|
||||
base.delete_dir(root_dir + "/editors/sdkjs") # delete empty folder. for bug 62528
|
||||
base.copy_dir(base_dir + "/js/" + branding + "/desktop/web-apps", root_dir + "/editors/web-apps")
|
||||
for file in glob.glob(root_dir + "/editors/web-apps/apps/*/*/*.js.map"):
|
||||
base.delete_file(file)
|
||||
@ -260,6 +262,8 @@ def make():
|
||||
if isUseJSC:
|
||||
base.delete_file(root_dir + "/converter/icudtl.dat")
|
||||
|
||||
base.create_x2t_js_cache(root_dir + "/converter", "desktop")
|
||||
|
||||
if (0 == platform.find("win")):
|
||||
base.delete_file(root_dir + "/cef_sandbox.lib")
|
||||
base.delete_file(root_dir + "/libcef.lib")
|
||||
|
||||
@ -58,10 +58,11 @@ def make():
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "DjVuFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "XpsFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "HtmlFile2")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "HtmlRenderer")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "doctrenderer")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "Fb2File")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "EpubFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "IWorkFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "HWPFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "DocxRenderer")
|
||||
base.copy_file(git_dir + "/sdkjs/pdf/src/engine/cmap.bin", root_dir + "/cmap.bin")
|
||||
|
||||
|
||||
@ -78,10 +78,11 @@ def make():
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "DjVuFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "XpsFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "HtmlFile2")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "HtmlRenderer")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "doctrenderer")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "Fb2File")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "EpubFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "IWorkFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "HWPFile")
|
||||
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "DocxRenderer")
|
||||
base.copy_file(git_dir + "/sdkjs/pdf/src/engine/cmap.bin", converter_dir + "/cmap.bin")
|
||||
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, converter_dir, "x2t")
|
||||
@ -118,6 +119,8 @@ def make():
|
||||
+ glob.glob(js_dir + "/web-apps/apps/*/mobile/dist/js/*.js.map"):
|
||||
base.delete_file(file)
|
||||
|
||||
base.create_x2t_js_cache(converter_dir, "server")
|
||||
|
||||
# add embed worker code
|
||||
base.cmd_in_dir(git_dir + "/sdkjs/common/embed", "python", ["make.py", js_dir + "/web-apps/apps/api/documents/api.js"])
|
||||
|
||||
|
||||
@ -5,9 +5,6 @@ import base
|
||||
import os
|
||||
import json
|
||||
|
||||
def get_core_url(arch, branch):
|
||||
return "http://repo-doc-onlyoffice-com.s3.amazonaws.com/" + base.host_platform() + "/core/" + branch + "/latest/" + arch + "/core.7z"
|
||||
|
||||
def make():
|
||||
git_dir = base.get_script_dir() + "/../.."
|
||||
old_cur = os.getcwd()
|
||||
@ -18,16 +15,10 @@ def make():
|
||||
|
||||
os.chdir(work_dir)
|
||||
|
||||
arch = "x64"
|
||||
arch2 = "_64"
|
||||
if ("windows" == base.host_platform()) and not base.host_platform_is64():
|
||||
arch = "x86"
|
||||
arch2 = "_32"
|
||||
|
||||
url = get_core_url(arch, config.option("branch"))
|
||||
url = base.get_autobuild_version("core", "", config.option("branch"))
|
||||
data_url = base.get_file_last_modified_url(url)
|
||||
if (data_url == "" and config.option("branch") != "develop"):
|
||||
url = get_core_url(arch, "develop")
|
||||
url = base.get_autobuild_version("core", "", "develop")
|
||||
data_url = base.get_file_last_modified_url(url)
|
||||
|
||||
old_data_url = base.readFile("./core.7z.data")
|
||||
@ -49,12 +40,6 @@ def make():
|
||||
base.extract("./core.7z", "./")
|
||||
base.writeFile("./core.7z.data", data_url)
|
||||
|
||||
platform = ""
|
||||
if ("windows" == base.host_platform()):
|
||||
platform = "win" + arch2
|
||||
else:
|
||||
platform = base.host_platform() + arch2
|
||||
|
||||
base.copy_files("./core/*", "./")
|
||||
else:
|
||||
print("-----------------------------------------------------------")
|
||||
|
||||
@ -539,7 +539,9 @@ def check_mysqlServer():
|
||||
print(mysql_full_name + 'configuration is valid')
|
||||
dependence.sqlPath = info['Location']
|
||||
return dependence
|
||||
print(mysql_full_name + 'configuration is not valid')
|
||||
print(mysql_full_name + 'configuration is not valid:' + connectionResult)
|
||||
# if path exists, then further removal and installation fails(according to startup statistics). it is better to fix issue manually.
|
||||
return dependence
|
||||
|
||||
print('Valid MySQL Server not found')
|
||||
dependence.append_uninstall('MySQL Server')
|
||||
|
||||
@ -21,14 +21,15 @@
|
||||
"core/DesktopEditor/raster/JBig2",
|
||||
"core/DesktopEditor/raster/Jp2",
|
||||
"core/DesktopEditor/xml/libxml2",
|
||||
"core/DesktopEditor/xmlsec",
|
||||
"core/DesktopEditor/xmlsec",
|
||||
"core/DjVuFile/libdjvu",
|
||||
"core/DjVuFile/wasm",
|
||||
"core/EpubFile",
|
||||
"core/OOXML/PPTXFormat/Limit/pri",
|
||||
"core/Fb2File",
|
||||
"core/HtmlFile2",
|
||||
"core/HtmlFile2",
|
||||
"core/Apple",
|
||||
"core/HwpFile",
|
||||
"core/OdfFile/Common/utf8cpp",
|
||||
"core/OfficeUtils/js/emsdk",
|
||||
"core/OfficeUtils/src/zlib-1.2.11",
|
||||
|
||||
@ -43,7 +43,7 @@ if utils.is_linux():
|
||||
},
|
||||
{
|
||||
"make": "rpm",
|
||||
"src": "rpm/builddir/RPMS/*/*.rpm",
|
||||
"src": "rpm/build/RPMS/*/*.rpm",
|
||||
"dst": "builder/linux/rhel/"
|
||||
}
|
||||
]
|
||||
|
||||
@ -7,14 +7,17 @@ import package_branding as branding
|
||||
|
||||
def make():
|
||||
utils.log_h1("BUILDER")
|
||||
if not (utils.is_windows() or utils.is_macos() or utils.is_linux()):
|
||||
utils.log("Unsupported host OS")
|
||||
return
|
||||
if common.deploy:
|
||||
make_archive()
|
||||
if utils.is_windows():
|
||||
make_windows()
|
||||
elif utils.is_macos():
|
||||
make_macos()
|
||||
elif utils.is_linux():
|
||||
make_linux()
|
||||
else:
|
||||
utils.log("Unsupported host OS")
|
||||
return
|
||||
|
||||
def s3_upload(files, dst):
|
||||
@ -24,111 +27,133 @@ def s3_upload(files, dst):
|
||||
key = dst + utils.get_basename(f) if dst.endswith("/") else dst
|
||||
upload = utils.s3_upload(f, "s3://" + branding.s3_bucket + "/" + key)
|
||||
if upload:
|
||||
utils.add_deploy_data(key)
|
||||
utils.log("URL: " + branding.s3_base_url + "/" + key)
|
||||
ret &= upload
|
||||
return ret
|
||||
|
||||
def make_windows():
|
||||
global inno_file, zip_file, suffix, key_prefix
|
||||
utils.set_cwd("document-builder-package")
|
||||
def make_archive():
|
||||
utils.set_cwd(utils.get_path(
|
||||
"build_tools/out/" + common.prefix + "/" + branding.company_name.lower()))
|
||||
|
||||
prefix = common.platformPrefixes[common.platform]
|
||||
company = branding.company_name
|
||||
product = branding.builder_product_name.replace(" ","")
|
||||
source_dir = "..\\build_tools\\out\\%s\\%s\\%s" % (prefix, company, product)
|
||||
package_name = company + "-" + product
|
||||
package_version = common.version + "." + common.build
|
||||
suffix = {
|
||||
"windows_x64": "x64",
|
||||
"windows_x86": "x86"
|
||||
}[common.platform]
|
||||
zip_file = "%s-%s-%s-%s.zip" % (company, product, package_version, suffix)
|
||||
inno_file = "%s-%s-%s-%s.exe" % (company, product, package_version, suffix)
|
||||
utils.log_h2("builder archive build")
|
||||
utils.delete_file("builder.7z")
|
||||
args = ["7z", "a", "-y", "builder.7z", "./documentbuilder/*"]
|
||||
if utils.is_windows():
|
||||
ret = utils.cmd(*args, verbose=True)
|
||||
else:
|
||||
ret = utils.sh(" ".join(args), verbose=True)
|
||||
utils.set_summary("builder archive build", ret)
|
||||
|
||||
if common.clean:
|
||||
utils.log_h2("builder clean")
|
||||
utils.delete_dir("build")
|
||||
|
||||
utils.log_h2("copy arifacts")
|
||||
utils.create_dir("build\\app")
|
||||
utils.copy_dir_content(source_dir, "build\\app\\")
|
||||
|
||||
make_zip()
|
||||
make_inno()
|
||||
utils.log_h2("builder archive deploy")
|
||||
dest = "builder-" + common.prefix.replace("_","-") + ".7z"
|
||||
dest_latest = "archive/%s/latest/%s" % (common.branch, dest)
|
||||
dest_version = "archive/%s/%s/%s" % (common.branch, common.build, dest)
|
||||
ret = utils.s3_upload(
|
||||
"builder.7z", "s3://" + branding.s3_bucket + "/" + dest_version)
|
||||
utils.set_summary("builder archive deploy", ret)
|
||||
if ret:
|
||||
utils.log("URL: " + branding.s3_base_url + "/" + dest_version)
|
||||
utils.s3_copy(
|
||||
"s3://" + branding.s3_bucket + "/" + dest_version,
|
||||
"s3://" + branding.s3_bucket + "/" + dest_latest)
|
||||
utils.log("URL: " + branding.s3_base_url + "/" + dest_latest)
|
||||
|
||||
utils.set_cwd(common.workspace_dir)
|
||||
return
|
||||
|
||||
def make_zip():
|
||||
utils.log_h2("builder zip build")
|
||||
utils.log_h3(zip_file)
|
||||
def make_windows():
|
||||
global package_version, arch
|
||||
utils.set_cwd("document-builder-package")
|
||||
|
||||
ret = utils.cmd("7z", "a", "-y", zip_file, ".\\app\\*",
|
||||
chdir="build", creates="build\\" + zip_file, verbose=True)
|
||||
package_version = common.version + "." + common.build
|
||||
arch = {
|
||||
"windows_x64": "x64",
|
||||
"windows_x86": "x86"
|
||||
}[common.platform]
|
||||
|
||||
if common.clean:
|
||||
utils.log_h2("builder clean")
|
||||
utils.delete_dir("build")
|
||||
utils.delete_files("exe\\*.exe")
|
||||
utils.delete_files("zip\\*.msi")
|
||||
|
||||
if make_prepare():
|
||||
make_zip()
|
||||
make_inno()
|
||||
else:
|
||||
utils.set_summary("builder zip build", False)
|
||||
utils.set_summary("builder inno build", False)
|
||||
|
||||
utils.set_cwd(common.workspace_dir)
|
||||
return
|
||||
|
||||
def make_prepare():
|
||||
args = [
|
||||
"-Version", package_version,
|
||||
"-Arch", arch
|
||||
]
|
||||
if common.sign:
|
||||
args += ["-Sign"]
|
||||
|
||||
utils.log_h2("builder prepare")
|
||||
ret = utils.ps1("make.ps1", args, verbose=True)
|
||||
utils.set_summary("builder prepare", ret)
|
||||
return ret
|
||||
|
||||
def make_zip():
|
||||
args = [
|
||||
"-Version", package_version,
|
||||
"-Arch", arch
|
||||
]
|
||||
# if common.sign:
|
||||
# args += ["-Sign"]
|
||||
|
||||
utils.log_h2("builder zip build")
|
||||
ret = utils.ps1("make_zip.ps1", args, verbose=True)
|
||||
utils.set_summary("builder zip build", ret)
|
||||
|
||||
if common.deploy and ret:
|
||||
utils.log_h2("builder zip deploy")
|
||||
ret = s3_upload(["build\\" + zip_file], "builder/win/generic/")
|
||||
ret = s3_upload(utils.glob_path("zip/*.zip"), "builder/win/generic/")
|
||||
utils.set_summary("builder zip deploy", ret)
|
||||
return
|
||||
|
||||
def make_inno():
|
||||
utils.log_h2("builder inno build")
|
||||
utils.log_h3(inno_file)
|
||||
|
||||
args = [
|
||||
"-Arch", suffix,
|
||||
"-Version", common.version,
|
||||
"-Build", common.build
|
||||
"-Version", package_version,
|
||||
"-Arch", arch
|
||||
]
|
||||
if not branding.onlyoffice:
|
||||
args += [
|
||||
"-Branding", "%s\\%s\\document-builder-package\\exe" % (common.workspace_dir, common.branding)
|
||||
]
|
||||
args += ["-Branding", common.branding]
|
||||
if common.sign:
|
||||
args += [
|
||||
"-Sign",
|
||||
"-CertName", branding.cert_name
|
||||
]
|
||||
ret = utils.ps1(
|
||||
"make_inno.ps1", args, creates="build\\" + inno_file, verbose=True
|
||||
)
|
||||
args += ["-Sign"]
|
||||
|
||||
utils.log_h2("builder inno build")
|
||||
ret = utils.ps1("make_inno.ps1", args, verbose=True)
|
||||
utils.set_summary("builder inno build", ret)
|
||||
|
||||
if common.deploy and ret:
|
||||
utils.log_h2("builder inno deploy")
|
||||
ret = s3_upload(["build\\" + inno_file], "builder/win/inno/")
|
||||
ret = s3_upload(utils.glob_path("exe/*.exe"), "builder/win/inno/")
|
||||
utils.set_summary("builder inno deploy", ret)
|
||||
return
|
||||
|
||||
def make_macos():
|
||||
company = branding.company_name.lower()
|
||||
product = branding.builder_product_name.replace(" ","").lower()
|
||||
source_dir = "build_tools/out/%s/%s/%s" % (common.prefix, company, product)
|
||||
arch_list = {
|
||||
"darwin_x86_64": "x86_64",
|
||||
"darwin_arm64": "arm64"
|
||||
}
|
||||
suffix = arch_list[common.platform]
|
||||
builder_tar = "../%s-%s-%s-%s-%s.tar.xz" % \
|
||||
(company, product, common.version, common.build, suffix)
|
||||
utils.set_cwd("document-builder-package")
|
||||
|
||||
utils.set_cwd(source_dir)
|
||||
utils.log_h2("builder tar build")
|
||||
make_args = ["tar"]
|
||||
if common.platform == "darwin_arm64":
|
||||
make_args += ["-e", "UNAME_M=arm64"]
|
||||
if not branding.onlyoffice:
|
||||
make_args += ["-e", "BRANDING_DIR=../" + common.branding + "/document-builder-package"]
|
||||
ret = utils.sh("make clean && make " + " ".join(make_args), verbose=True)
|
||||
utils.set_summary("builder tar build", ret)
|
||||
|
||||
if common.clean:
|
||||
utils.log_h2("builder clean")
|
||||
utils.delete_files("../*.tar*")
|
||||
|
||||
utils.log_h2("builder build")
|
||||
ret = utils.sh("tar --xz -cvf %s *" % builder_tar, creates=builder_tar, verbose=True)
|
||||
utils.set_summary("builder build", ret)
|
||||
|
||||
if common.deploy and ret:
|
||||
utils.log_h2("builder deploy")
|
||||
ret = s3_upload([builder_tar], "builder/mac/generic/")
|
||||
utils.set_summary("builder deploy", ret)
|
||||
if common.deploy:
|
||||
utils.log_h2("builder tar deploy")
|
||||
ret = s3_upload(utils.glob_path("tar/*.tar.xz"), "builder/mac/generic/")
|
||||
utils.set_summary("builder tar deploy", ret)
|
||||
|
||||
utils.set_cwd(common.workspace_dir)
|
||||
return
|
||||
|
||||
@ -10,47 +10,37 @@ def make():
|
||||
utils.log("Unsupported host OS")
|
||||
return
|
||||
if common.deploy:
|
||||
make_core()
|
||||
make_archive()
|
||||
return
|
||||
|
||||
def make_core():
|
||||
prefix = common.platformPrefixes[common.platform]
|
||||
company = branding.company_name.lower()
|
||||
repos = {
|
||||
"windows_x64": { "repo": "windows", "arch": "x64", "version": common.version + "." + common.build },
|
||||
"windows_x86": { "repo": "windows", "arch": "x86", "version": common.version + "." + common.build },
|
||||
"darwin_x86_64": { "repo": "mac", "arch": "x64", "version": common.version + "-" + common.build },
|
||||
"darwin_arm64": { "repo": "mac", "arch": "arm", "version": common.version + "-" + common.build },
|
||||
"linux_x86_64": { "repo": "linux", "arch": "x64", "version": common.version + "-" + common.build },
|
||||
}
|
||||
repo = repos[common.platform]
|
||||
branch = utils.get_env("BRANCH_NAME")
|
||||
core_7z = utils.get_path("build_tools/out/%s/%s/core.7z" % (prefix, company))
|
||||
dest_version = "%s/core/%s/%s/%s" % (repo["repo"], branch, repo["version"], repo["arch"])
|
||||
dest_latest = "%s/core/%s/%s/%s" % (repo["repo"], branch, "latest", repo["arch"])
|
||||
def make_archive():
|
||||
utils.set_cwd(utils.get_path(
|
||||
"build_tools/out/" + common.prefix + "/" + branding.company_name.lower()))
|
||||
|
||||
if branch is None:
|
||||
utils.log_err("BRANCH_NAME variable is undefined")
|
||||
utils.set_summary("core deploy", False)
|
||||
return
|
||||
if not utils.is_file(core_7z):
|
||||
utils.log_err("file not exist: " + core_7z)
|
||||
utils.set_summary("core deploy", False)
|
||||
return
|
||||
utils.log_h2("core archive build")
|
||||
utils.delete_file("core.7z")
|
||||
args = ["7z", "a", "-y", "core.7z", "./core/*"]
|
||||
if utils.is_windows():
|
||||
ret = utils.cmd(*args, verbose=True)
|
||||
else:
|
||||
ret = utils.sh(" ".join(args), verbose=True)
|
||||
utils.set_summary("core archive build", ret)
|
||||
|
||||
utils.log_h2("core deploy")
|
||||
utils.log_h2("core archive deploy")
|
||||
dest = "core-" + common.prefix.replace("_","-") + ".7z"
|
||||
dest_latest = "archive/%s/latest/%s" % (common.branch, dest)
|
||||
dest_version = "archive/%s/%s/%s" % (common.branch, common.build, dest)
|
||||
ret = utils.s3_upload(
|
||||
core_7z,
|
||||
"s3://" + branding.s3_bucket + "/" + dest_version + "/core.7z")
|
||||
"core.7z", "s3://" + branding.s3_bucket + "/" + dest_version)
|
||||
utils.set_summary("core archive deploy", ret)
|
||||
if ret:
|
||||
utils.log("URL: " + branding.s3_base_url + "/" + dest_version + "/core.7z")
|
||||
utils.add_deploy_data(dest_version + "/core.7z")
|
||||
ret = utils.s3_sync(
|
||||
"s3://" + branding.s3_bucket + "/" + dest_version + "/",
|
||||
"s3://" + branding.s3_bucket + "/" + dest_latest + "/",
|
||||
delete=True)
|
||||
utils.log("URL: " + branding.s3_base_url + "/" + dest_latest + "/core.7z")
|
||||
utils.set_summary("core deploy", ret)
|
||||
utils.log("URL: " + branding.s3_base_url + "/" + dest_version)
|
||||
utils.s3_copy(
|
||||
"s3://" + branding.s3_bucket + "/" + dest_version,
|
||||
"s3://" + branding.s3_bucket + "/" + dest_latest)
|
||||
utils.log("URL: " + branding.s3_base_url + "/" + dest_latest)
|
||||
|
||||
utils.set_cwd(common.workspace_dir)
|
||||
return
|
||||
|
||||
def deploy_closuremaps_sdkjs(license):
|
||||
@ -74,7 +64,6 @@ def deploy_closuremaps_sdkjs(license):
|
||||
ret &= upload
|
||||
if upload:
|
||||
utils.log("URL: " + branding.s3_base_url + "/" + key)
|
||||
utils.add_deploy_data(key)
|
||||
utils.set_summary("sdkjs closure maps %s deploy" % license, ret)
|
||||
return
|
||||
|
||||
@ -100,6 +89,5 @@ def deploy_closuremaps_webapps(license):
|
||||
ret &= upload
|
||||
if upload:
|
||||
utils.log("URL: " + branding.s3_base_url + "/" + key)
|
||||
utils.add_deploy_data(key)
|
||||
utils.set_summary("web-apps closure maps %s deploy" % license, ret)
|
||||
return
|
||||
|
||||
@ -26,7 +26,6 @@ def s3_upload(files, dst):
|
||||
key = dst + utils.get_basename(f) if dst.endswith("/") else dst
|
||||
upload = utils.s3_upload(f, "s3://" + branding.s3_bucket + "/" + key)
|
||||
if upload:
|
||||
utils.add_deploy_data(key)
|
||||
utils.log("URL: " + branding.s3_base_url + "/" + key)
|
||||
ret &= upload
|
||||
return ret
|
||||
@ -64,6 +63,7 @@ def make_windows():
|
||||
make_zip()
|
||||
make_inno()
|
||||
make_advinst()
|
||||
make_online()
|
||||
|
||||
utils.set_cwd(common.workspace_dir)
|
||||
return
|
||||
@ -154,17 +154,6 @@ def make_inno():
|
||||
else:
|
||||
ret = False
|
||||
utils.set_summary("desktop inno update deploy", ret)
|
||||
|
||||
changes_dir = common.workspace_dir + "\\" \
|
||||
+ utils.get_path(branding.desktop_changes_dir) + "\\" + common.version
|
||||
if common.platform == "windows_x64" and \
|
||||
common.deploy and \
|
||||
utils.glob_path(changes_dir + "\\*.html"):
|
||||
utils.log_h2("desktop changelog deploy")
|
||||
ret = s3_upload(
|
||||
utils.glob_path(changes_dir + "\\*.html"),
|
||||
"desktop/win/update/%s/%s/" % (common.version, common.build))
|
||||
utils.set_summary("desktop changelog deploy", ret)
|
||||
return
|
||||
|
||||
def make_advinst():
|
||||
@ -188,6 +177,19 @@ def make_advinst():
|
||||
utils.set_summary("desktop advinst deploy", ret)
|
||||
return
|
||||
|
||||
def make_online():
|
||||
if not common.platform in ["windows_x86_xp"]:
|
||||
return
|
||||
online_file = "%s-%s-%s.exe" % ("OnlineInstaller", package_version, suffix)
|
||||
ret = utils.is_file(online_file)
|
||||
utils.set_summary("desktop online installer build", ret)
|
||||
|
||||
if common.deploy and ret:
|
||||
utils.log_h2("desktop online installer deploy")
|
||||
ret = s3_upload([online_file], "desktop/win/online/")
|
||||
utils.set_summary("desktop online installer deploy", ret)
|
||||
return
|
||||
|
||||
#
|
||||
# macOS
|
||||
#
|
||||
@ -294,11 +296,17 @@ def make_sparkle_updates():
|
||||
macos_zip = "build/" + zip_filename + ".zip"
|
||||
utils.create_dir(updates_dir)
|
||||
utils.copy_file(macos_zip, updates_dir)
|
||||
utils.copy_dir_content(released_updates_dir, updates_dir, ".zip")
|
||||
utils.sh(
|
||||
"ls -1t " + released_updates_dir + "/*.zip" \
|
||||
+ " | head -n 3" \
|
||||
+ " | while read f; do cp -fv \"$f\" " + updates_dir + "/; done",
|
||||
verbose=True)
|
||||
|
||||
for file in utils.glob_path(changes_dir + "/" + common.version + "/*.html"):
|
||||
filename = utils.get_basename(file).replace("changes", zip_filename)
|
||||
utils.copy_file(file, updates_dir + "/" + filename)
|
||||
for ext in [".html", ".ru.html"]:
|
||||
changes_src = changes_dir + "/" + common.version + "/changes" + ext
|
||||
changes_dst = updates_dir + "/" + zip_filename + ext
|
||||
if not utils.copy_file(changes_src, changes_dst):
|
||||
utils.write_file(changes_dst, "<!DOCTYPE html>placeholder")
|
||||
|
||||
sparkle_base_url = "%s/%s/updates/" % (branding.sparkle_base_url, suffix)
|
||||
ret = utils.sh(
|
||||
|
||||
@ -31,7 +31,6 @@ def make_mobile():
|
||||
key = "mobile/android/" + zip_file
|
||||
ret = utils.s3_upload(zip_file, "s3://" + branding.s3_bucket + "/" + key)
|
||||
if ret:
|
||||
utils.add_deploy_data(key)
|
||||
utils.log("URL: " + branding.s3_base_url + "/" + key)
|
||||
utils.set_summary("mobile deploy", ret)
|
||||
|
||||
|
||||
@ -21,7 +21,6 @@ def s3_upload(files, dst):
|
||||
key = dst + utils.get_basename(f) if dst.endswith("/") else dst
|
||||
upload = utils.s3_upload(f, "s3://" + branding.s3_bucket + "/" + key)
|
||||
if upload:
|
||||
utils.add_deploy_data(key)
|
||||
utils.log("URL: " + branding.s3_base_url + "/" + key)
|
||||
ret &= upload
|
||||
return ret
|
||||
@ -37,10 +36,13 @@ def make_windows(edition):
|
||||
|
||||
utils.log_h2("server " + edition + " build")
|
||||
ret = utils.cmd("make", "clean", verbose=True)
|
||||
args = ["-e", "PRODUCT_NAME=" + product_name]
|
||||
if edition == "prerequisites":
|
||||
make_args = ["exe-pr"]
|
||||
else:
|
||||
make_args = ["exe", "-e", "PRODUCT_NAME=" + product_name]
|
||||
if not branding.onlyoffice:
|
||||
args += ["-e", "BRANDING_DIR=../" + common.branding + "/document-server-package"]
|
||||
ret &= utils.cmd("make", "packages", *args, verbose=True)
|
||||
make_args += ["-e", "BRANDING_DIR=../" + common.branding + "/document-server-package"]
|
||||
ret &= utils.cmd("make", *make_args, verbose=True)
|
||||
utils.set_summary("server " + edition + " build", ret)
|
||||
|
||||
if common.deploy and ret:
|
||||
|
||||
@ -266,11 +266,6 @@ def set_summary(target, status):
|
||||
common.summary.append({target: status})
|
||||
return
|
||||
|
||||
def add_deploy_data(key):
|
||||
with open(common.deploy_data, 'a+') as f:
|
||||
f.write(key + "\n")
|
||||
return
|
||||
|
||||
def cmd(*args, **kwargs):
|
||||
if kwargs.get("verbose"):
|
||||
log("- cmd:")
|
||||
@ -385,15 +380,13 @@ def s3_upload(src, dst, **kwargs):
|
||||
ret = sh(" ".join(args), verbose=True)
|
||||
return ret
|
||||
|
||||
def s3_sync(src, dst, **kwargs):
|
||||
def s3_copy(src, dst, **kwargs):
|
||||
args = ["aws"]
|
||||
if kwargs.get("endpoint_url"):
|
||||
args += ["--endpoint-url", kwargs["endpoint_url"]]
|
||||
args += ["s3", "sync", "--no-progress"]
|
||||
args += ["s3", "cp", "--no-progress"]
|
||||
if kwargs.get("acl"):
|
||||
args += ["--acl", kwargs["acl"]]
|
||||
if kwargs.get("delete") and kwargs["delete"]:
|
||||
args += ["--delete"]
|
||||
args += [src, dst]
|
||||
if is_windows():
|
||||
ret = cmd(*args, verbose=True)
|
||||
|
||||
@ -29,7 +29,7 @@ def check_support_platform(platform):
|
||||
return False
|
||||
return True
|
||||
|
||||
def make(platform, project, qmake_config_addon=""):
|
||||
def make(platform, project, qmake_config_addon="", is_no_errors=False):
|
||||
# check platform
|
||||
if not check_support_platform(platform):
|
||||
print("THIS PLATFORM IS NOT SUPPORTED")
|
||||
@ -93,6 +93,9 @@ def make(platform, project, qmake_config_addon=""):
|
||||
qmake_app = qt_dir + "/bin/qmake"
|
||||
# non windows platform
|
||||
if not base.is_windows():
|
||||
if base.is_file(qt_dir + "/onlyoffice_qt.conf"):
|
||||
build_params.append("-qtconf")
|
||||
build_params.append(qt_dir + "/onlyoffice_qt.conf")
|
||||
base.cmd(qmake_app, build_params)
|
||||
base.correct_makefile_after_qmake(platform, makefile)
|
||||
if ("1" == config.option("clean")):
|
||||
@ -100,7 +103,7 @@ def make(platform, project, qmake_config_addon=""):
|
||||
base.cmd_and_return_cwd("make", distclean_params, True)
|
||||
base.cmd(qmake_app, build_params)
|
||||
base.correct_makefile_after_qmake(platform, makefile)
|
||||
base.cmd_and_return_cwd("make", ["-f", makefile] + get_j_num())
|
||||
base.cmd_and_return_cwd("make", ["-f", makefile] + get_j_num(), is_no_errors)
|
||||
else:
|
||||
config_params_array = base.qt_config_as_param(config_param)
|
||||
config_params_string = ""
|
||||
@ -123,7 +126,7 @@ def make(platform, project, qmake_config_addon=""):
|
||||
if ("0" != config.option("multiprocess")):
|
||||
qmake_bat.append("set CL=/MP")
|
||||
qmake_bat.append("call nmake -f " + makefile)
|
||||
base.run_as_bat(qmake_bat)
|
||||
base.run_as_bat(qmake_bat, is_no_errors)
|
||||
|
||||
if (base.is_file(stash_file)):
|
||||
base.delete_file(stash_file)
|
||||
|
||||
@ -1,16 +1,22 @@
|
||||
|
||||
# Documentation Generation Guide
|
||||
|
||||
This guide explains how to generate documentation for Onlyoffice API using the provided Python scripts, `generate_docs_json.py` and `generate_docs_md.py`. These scripts are used to create JSON and Markdown documentation for the `apiBuilder.js` files from the word, cell, and slide editors.
|
||||
This guide explains how to generate documentation for Onlyoffice Builder/Plugins API using the provided Python scripts: `generate_docs_json.py`, `generate_docs_plugins_json.py`, `generate_docs_md.py`. These scripts are used to create JSON and Markdown documentation for the `apiBuilder.js` files from the word, cell, and slide editors.
|
||||
|
||||
## Prerequisites
|
||||
## Requirements
|
||||
|
||||
1. **Node.js and npm**: Ensure you have Node.js and npm installed on your machine. You can download them from [Node.js official website](https://nodejs.org/).
|
||||
```bash
|
||||
Node.js v20 and above
|
||||
Python v3.10 and above
|
||||
```
|
||||
|
||||
2. **jsdoc**: The scripts use `jsdoc` to generate documentation. Install it using npm:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ONLYOFFICE/build_tools.git
|
||||
cd build_tools/scripts/sdkjs_common/jsdoc
|
||||
npm install
|
||||
```
|
||||
|
||||
## Scripts Overview
|
||||
|
||||
@ -24,7 +30,19 @@ This script generates JSON documentation based on the `apiBuilder.js` files.
|
||||
```
|
||||
|
||||
- **Parameters**:
|
||||
- `output_path` (optional): The directory where the JSON documentation will be saved. If not specified, the default path is `Onlyoffice/sdkjs/deploy/api_builder/json`.
|
||||
- `output_path` (optional): The directory where the JSON documentation will be saved. If not specified, the default path is `../../../../office-js-api-declarations/office-js-api`.
|
||||
|
||||
### `generate_docs_plugins_json.py`
|
||||
|
||||
This script generates JSON documentation based on the `api_plugins.js` files.
|
||||
|
||||
- **Usage**:
|
||||
```bash
|
||||
python generate_docs_plugins_json.py output_path
|
||||
```
|
||||
|
||||
- **Parameters**:
|
||||
- `output_path` (optional): The directory where the JSON documentation will be saved. If not specified, the default path is `../../../../office-js-api-declarations/office-js-api-plugins`.
|
||||
|
||||
### `generate_docs_md.py`
|
||||
|
||||
@ -36,7 +54,7 @@ This script generates Markdown documentation from the `apiBuilder.js` files.
|
||||
```
|
||||
|
||||
- **Parameters**:
|
||||
- `output_path` (optional): The directory where the Markdown documentation will be saved. If not specified, the default path is `Onlyoffice/office-js-api`.
|
||||
- `output_path` (optional): The directory where the Markdown documentation will be saved. If not specified, the default path is `../../../../office-js-api/`.
|
||||
|
||||
## Example
|
||||
|
||||
@ -45,6 +63,11 @@ To generate JSON documentation with the default output path:
|
||||
python generate_docs_json.py /path/to/save/json
|
||||
```
|
||||
|
||||
To generate JSON documentation with the default output path:
|
||||
```bash
|
||||
python generate_docs_plugins_json.py /path/to/save/json
|
||||
```
|
||||
|
||||
To generate Markdown documentation and specify a custom output path:
|
||||
```bash
|
||||
python generate_docs_md.py /path/to/save/markdown
|
||||
|
||||
16
scripts/sdkjs_common/jsdoc/config/plugins/cell.json
Normal file
16
scripts/sdkjs_common/jsdoc/config/plugins/cell.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"source": {
|
||||
"include": ["../../../../sdkjs/cell/api_plugins.js"]
|
||||
},
|
||||
"plugins": ["./correct_doclets.js"],
|
||||
"opts": {
|
||||
"destination": "./out",
|
||||
"recurse": true,
|
||||
"encoding": "utf8"
|
||||
},
|
||||
"templates": {
|
||||
"json": {
|
||||
"pretty": true
|
||||
}
|
||||
}
|
||||
}
|
||||
16
scripts/sdkjs_common/jsdoc/config/plugins/common.json
Normal file
16
scripts/sdkjs_common/jsdoc/config/plugins/common.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"source": {
|
||||
"include": ["../../../../sdkjs/common/plugins/plugin_base_api.js" ,"../../../../sdkjs/common/apiBase_plugins.js"]
|
||||
},
|
||||
"plugins": ["./correct_doclets.js"],
|
||||
"opts": {
|
||||
"destination": "./out",
|
||||
"recurse": true,
|
||||
"encoding": "utf8"
|
||||
},
|
||||
"templates": {
|
||||
"json": {
|
||||
"pretty": true
|
||||
}
|
||||
}
|
||||
}
|
||||
85
scripts/sdkjs_common/jsdoc/config/plugins/correct_doclets.js
Normal file
85
scripts/sdkjs_common/jsdoc/config/plugins/correct_doclets.js
Normal file
@ -0,0 +1,85 @@
|
||||
exports.handlers = {
|
||||
processingComplete: function(e) {
|
||||
const filteredDoclets = [];
|
||||
|
||||
function checkNullProps(oDoclet) {
|
||||
for (let key of Object.keys(oDoclet)) {
|
||||
if (oDoclet[key] == null) {
|
||||
delete oDoclet[key];
|
||||
}
|
||||
if (typeof(oDoclet[key]) == "object") {
|
||||
checkNullProps(oDoclet[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < e.doclets.length; i++) {
|
||||
const doclet = e.doclets[i];
|
||||
if (true == doclet.undocumented || doclet.kind == 'package') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const filteredDoclet = {
|
||||
comment: doclet.comment,
|
||||
|
||||
meta: doclet.meta ? {
|
||||
lineno: doclet.meta.lineno,
|
||||
columnno: doclet.meta.columnno
|
||||
} : doclet.meta,
|
||||
|
||||
kind: doclet.kind,
|
||||
since: doclet.since,
|
||||
name: doclet.name,
|
||||
type: doclet.type ? {
|
||||
names: doclet.type.names,
|
||||
parsedType: doclet.type.parsedType
|
||||
} : doclet.type,
|
||||
|
||||
description: doclet.description,
|
||||
memberof: doclet.memberof,
|
||||
|
||||
properties: doclet.properties ? doclet.properties.map(property => ({
|
||||
type: property.type ? {
|
||||
names: property.type.names,
|
||||
parsedType: property.type.parsedType
|
||||
} : property.type,
|
||||
|
||||
name: property.name,
|
||||
description: property.description,
|
||||
optional: property.optional,
|
||||
defaultvalue: property.defaultvalue
|
||||
})) : doclet.properties,
|
||||
|
||||
longname: doclet.longname,
|
||||
scope: doclet.scope,
|
||||
alias: doclet.alias,
|
||||
|
||||
params: doclet.params ? doclet.params.map(param => ({
|
||||
type: param.type ? {
|
||||
names: param.type.names,
|
||||
parsedType: param.type.parsedType
|
||||
} : param.type,
|
||||
|
||||
name: param.name,
|
||||
description: param.description,
|
||||
optional: param.optional,
|
||||
defaultvalue: param.defaultvalue
|
||||
})) : doclet.params,
|
||||
|
||||
returns: doclet.returns ? doclet.returns.map(returnObj => ({
|
||||
type: {
|
||||
names: returnObj.type.names,
|
||||
parsedType: returnObj.type.parsedType
|
||||
}
|
||||
})) : doclet.returns,
|
||||
see: doclet.see
|
||||
};
|
||||
|
||||
checkNullProps(filteredDoclet)
|
||||
|
||||
filteredDoclets.push(filteredDoclet);
|
||||
}
|
||||
|
||||
e.doclets.splice(0, e.doclets.length, ...filteredDoclets);
|
||||
}
|
||||
};
|
||||
16
scripts/sdkjs_common/jsdoc/config/plugins/forms.json
Normal file
16
scripts/sdkjs_common/jsdoc/config/plugins/forms.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"source": {
|
||||
"include": ["../../../../sdkjs-forms/apiPlugins.js"]
|
||||
},
|
||||
"plugins": ["./correct_doclets.js"],
|
||||
"opts": {
|
||||
"destination": "./out",
|
||||
"recurse": true,
|
||||
"encoding": "utf8"
|
||||
},
|
||||
"templates": {
|
||||
"json": {
|
||||
"pretty": true
|
||||
}
|
||||
}
|
||||
}
|
||||
16
scripts/sdkjs_common/jsdoc/config/plugins/slide.json
Normal file
16
scripts/sdkjs_common/jsdoc/config/plugins/slide.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"source": {
|
||||
"include": ["../../../../sdkjs/slide/api_plugins.js"]
|
||||
},
|
||||
"plugins": ["./correct_doclets.js"],
|
||||
"opts": {
|
||||
"destination": "./out",
|
||||
"recurse": true,
|
||||
"encoding": "utf8"
|
||||
},
|
||||
"templates": {
|
||||
"json": {
|
||||
"pretty": true
|
||||
}
|
||||
}
|
||||
}
|
||||
16
scripts/sdkjs_common/jsdoc/config/plugins/word.json
Normal file
16
scripts/sdkjs_common/jsdoc/config/plugins/word.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"source": {
|
||||
"include": ["../../../../sdkjs/word/api_plugins.js", "../../../../sdkjs-forms/apiPlugins.js"]
|
||||
},
|
||||
"plugins": ["./correct_doclets.js"],
|
||||
"opts": {
|
||||
"destination": "./out",
|
||||
"recurse": true,
|
||||
"encoding": "utf8"
|
||||
},
|
||||
"templates": {
|
||||
"json": {
|
||||
"pretty": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,13 +3,16 @@ import subprocess
|
||||
import json
|
||||
import argparse
|
||||
import re
|
||||
import platform
|
||||
|
||||
root = '../../../..'
|
||||
|
||||
# Configuration files
|
||||
configs = [
|
||||
"./config/word.json",
|
||||
"./config/cell.json",
|
||||
"./config/slide.json",
|
||||
"./config/forms.json"
|
||||
"./config/builder/word.json",
|
||||
"./config/builder/cell.json",
|
||||
"./config/builder/slide.json",
|
||||
"./config/builder/forms.json"
|
||||
]
|
||||
|
||||
editors_maps = {
|
||||
@ -20,20 +23,17 @@ editors_maps = {
|
||||
}
|
||||
|
||||
def generate(output_dir, md=False):
|
||||
missing_examples_file = f'{output_dir}/missing_examples.txt'
|
||||
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
# Recreate missing_examples.txt file
|
||||
with open(missing_examples_file, 'w', encoding='utf-8') as f:
|
||||
f.write('')
|
||||
|
||||
# Generate JSON documentation
|
||||
for config in configs:
|
||||
editor_name = config.split('/')[-1].replace('.json', '')
|
||||
output_file = os.path.join(output_dir, editor_name + ".json")
|
||||
command = f"set EDITOR={editors_maps[editor_name]} && npx jsdoc -c {config} -X > {output_file}"
|
||||
command_set_env = "export"
|
||||
if (platform.system().lower() == "windows"):
|
||||
command_set_env = "set"
|
||||
command = f"{command_set_env} EDITOR={editors_maps[editor_name]} && npx jsdoc -c {config} -X > {output_file}"
|
||||
print(f"Generating {editor_name}.json: {command}")
|
||||
subprocess.run(command, shell=True)
|
||||
|
||||
@ -55,7 +55,7 @@ def generate(output_dir, md=False):
|
||||
else:
|
||||
doclet['see'][0] = doclet['see'][0].replace('{Editor}', editor_name.title())
|
||||
|
||||
file_path = '../../../../' + doclet['see'][0]
|
||||
file_path = f'{root}/' + doclet['see'][0]
|
||||
|
||||
if os.path.exists(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as see_file:
|
||||
@ -70,22 +70,20 @@ def generate(output_dir, md=False):
|
||||
comment = ''
|
||||
code_content = example_content
|
||||
|
||||
# Format content for doclet['example']
|
||||
doclet['example'] = remove_js_comments(comment) + "```js\n" + code_content + "\n```"
|
||||
if md == True:
|
||||
doclet['example'] = remove_js_comments(comment) + "```js\n" + code_content + "\n```"
|
||||
|
||||
if md == False:
|
||||
doclet['description'] = doclet['description'] + f'\n\n## Try it\n\n ```js document-builder={{"documentType": "{editor_name.title()}"}}\n{code_content}\n```'
|
||||
|
||||
else:
|
||||
# Record missing examples in missing_examples.txt
|
||||
with open(missing_examples_file, 'a', encoding='utf-8') as missing_file:
|
||||
missing_file.write(f"{file_path}\n")
|
||||
document_type = editor_name
|
||||
if "forms" == document_type:
|
||||
document_type = "pdf"
|
||||
doclet['description'] = doclet['description'] + f'\n\n## Try it\n\n ```js document-builder={{"documentType": "{document_type}"}}\n{code_content}\n```'
|
||||
|
||||
# Write the modified JSON file back
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
print("Documentation generation completed.")
|
||||
print("Documentation generation for builder completed.")
|
||||
|
||||
def remove_builder_lines(text):
|
||||
lines = text.splitlines() # Split text into lines
|
||||
@ -98,25 +96,6 @@ def remove_js_comments(text):
|
||||
# Remove multi-line comments, leaving text after /*
|
||||
text = re.sub(r'/\*\s*|\s*\*/', '', text, flags=re.DOTALL)
|
||||
return text.strip()
|
||||
|
||||
def get_current_branch(path):
|
||||
try:
|
||||
# Navigate to the specified directory and get the current branch name
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
||||
cwd=path,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
else:
|
||||
print(f"Error: {result.stderr}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Exception: {e}")
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Generate documentation")
|
||||
@ -125,12 +104,7 @@ if __name__ == "__main__":
|
||||
type=str,
|
||||
help="Destination directory for the generated documentation",
|
||||
nargs='?', # Indicates the argument is optional
|
||||
default="../../../../document-builder-declarations/document-builder" # Default value
|
||||
default=f"{root}/office-js-api-declarations/office-js-api"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
branch_name = get_current_branch("../../../../sdkjs")
|
||||
if branch_name:
|
||||
args.destination = f"{args.destination}/{branch_name}"
|
||||
|
||||
generate(args.destination)
|
||||
|
||||
@ -6,12 +6,17 @@ import argparse
|
||||
import generate_docs_json
|
||||
|
||||
# Configuration files
|
||||
editors = [
|
||||
"word",
|
||||
"cell",
|
||||
"slide",
|
||||
"forms"
|
||||
]
|
||||
editors = {
|
||||
"word": "text-document-api",
|
||||
"cell": "spreadsheet-api",
|
||||
"slide": "presentation-api",
|
||||
"forms": "form-api"
|
||||
}
|
||||
|
||||
missing_examples = []
|
||||
used_enumerations = set()
|
||||
|
||||
cur_editor_name = None
|
||||
|
||||
def load_json(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
@ -22,75 +27,301 @@ def write_markdown_file(file_path, content):
|
||||
md_file.write(content)
|
||||
|
||||
def remove_js_comments(text):
|
||||
# Remove single-line comments, leaving text after //
|
||||
text = re.sub(r'^\s*//\s?', '', text, flags=re.MULTILINE)
|
||||
# Remove multi-line comments, leaving text after /*
|
||||
text = re.sub(r'/\*\s*|\s*\*/', '', text, flags=re.DOTALL)
|
||||
text = re.sub(r'^\s*//.*$', '', text, flags=re.MULTILINE) # single-line
|
||||
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) # multi-line
|
||||
return text.strip()
|
||||
|
||||
def correct_description(string):
|
||||
def process_link_tags(text, root=''):
|
||||
"""
|
||||
Finds patterns like {@link ...} and replaces them with Markdown links.
|
||||
If the prefix 'global#' is found, a link to a typedef is generated,
|
||||
otherwise, a link to a class method is created.
|
||||
For a method, if an alias is not specified, the name is left in the format 'Class#Method'.
|
||||
"""
|
||||
reserved_links = {
|
||||
'/docbuilder/global#ShapeType': f"{'../../../' if root == '' else '../../' if root == '../' else root}text-document-api/Enumeration/ShapeType.md",
|
||||
'/plugin/config': 'https://api.onlyoffice.com/docs/plugin-and-macros/structure/manifest/',
|
||||
'/docbuilder/basic': 'https://api.onlyoffice.com/docs/office-api/usage-api/text-document-api/'
|
||||
}
|
||||
|
||||
def replace_link(match):
|
||||
content = match.group(1).strip() # Example: "/docbuilder/global#ShapeType shape type" or "global#ErrorValue ErrorValue"
|
||||
parts = content.split()
|
||||
ref = parts[0]
|
||||
label = parts[1] if len(parts) > 1 else None
|
||||
|
||||
if ref.startswith('/'):
|
||||
# Handle reserved links using mapping
|
||||
if ref in reserved_links:
|
||||
url = reserved_links[ref]
|
||||
display_text = label if label else ref
|
||||
return f"[{display_text}]({url})"
|
||||
else:
|
||||
# If the link is not in the mapping, return the original construction
|
||||
return match.group(0)
|
||||
elif ref.startswith("global#"):
|
||||
# Handle links to typedef (similar logic as before)
|
||||
typedef_name = ref.split("#")[1]
|
||||
used_enumerations.add(typedef_name)
|
||||
display_text = label if label else typedef_name
|
||||
return f"[{display_text}]({root}Enumeration/{typedef_name}.md)"
|
||||
else:
|
||||
# Handle links to class methods like ClassName#MethodName
|
||||
try:
|
||||
class_name, method_name = ref.split("#")
|
||||
except ValueError:
|
||||
return match.group(0)
|
||||
display_text = label if label else ref # Keep the full notation, e.g., "Api#CreateSlide"
|
||||
return f"[{display_text}]({root}{class_name}/Methods/{method_name}.md)"
|
||||
|
||||
return re.sub(r'{@link\s+([^}]+)}', replace_link, text)
|
||||
|
||||
def correct_description(string, root=''):
|
||||
"""
|
||||
Cleans or transforms specific tags in the doclet description:
|
||||
- <b> => ** (bold text)
|
||||
- <note>...</note> => 💡 ...
|
||||
- {@link ...} is replaced with a Markdown link
|
||||
- If the description is missing, returns a default value.
|
||||
- All '\r' characters are replaced with '\n'.
|
||||
"""
|
||||
if string is None:
|
||||
return 'No description provided.'
|
||||
|
||||
# Line breaks
|
||||
string = string.replace('\r', '\\\n')
|
||||
|
||||
# Replace opening <b> tag with **
|
||||
string = re.sub(r'<b>', '**', string)
|
||||
# Replace closing </b> tag with **
|
||||
# Replace <b> tags with Markdown bold formatting
|
||||
string = re.sub(r'<b>', '-**', string)
|
||||
string = re.sub(r'</b>', '**', string)
|
||||
# Note
|
||||
return re.sub(r'<note>(.*?)</note>', r'💡 \1', string, flags=re.DOTALL)
|
||||
|
||||
# Replace <note> tags with an icon and text
|
||||
string = re.sub(r'<note>(.*?)</note>', r'💡 \1', string, flags=re.DOTALL)
|
||||
|
||||
# Process {@link ...} constructions
|
||||
string = process_link_tags(string, root)
|
||||
|
||||
return string
|
||||
|
||||
def correct_default_value(value, enumerations, classes):
|
||||
if value is None:
|
||||
if value is None or value == '':
|
||||
return ''
|
||||
|
||||
if value == True:
|
||||
value = "true"
|
||||
elif value == False:
|
||||
value = "false"
|
||||
if isinstance(value, bool):
|
||||
value = "true" if value else "false"
|
||||
else:
|
||||
value = str(value)
|
||||
|
||||
return generate_data_types_markdown([value], enumerations, classes)
|
||||
|
||||
def remove_line_breaks(string):
|
||||
return re.sub(r'[\r\n]', '', string)
|
||||
return re.sub(r'[\r\n]+', ' ', string)
|
||||
|
||||
# Convert Array.<T> => T[] (including nested arrays).
|
||||
def convert_jsdoc_array_to_ts(type_str: str) -> str:
|
||||
"""
|
||||
Recursively replaces 'Array.<T>' with 'T[]',
|
||||
handling nested arrays like 'Array.<Array.<string>>' => 'string[][]'.
|
||||
"""
|
||||
pattern = re.compile(r'Array\.<([^>]+)>')
|
||||
|
||||
while True:
|
||||
match = pattern.search(type_str)
|
||||
if not match:
|
||||
break
|
||||
|
||||
inner_type = match.group(1).strip()
|
||||
# Recursively convert inner parts
|
||||
inner_type = convert_jsdoc_array_to_ts(inner_type)
|
||||
|
||||
# Replace the outer Array.<...> with ...[]
|
||||
type_str = (
|
||||
type_str[:match.start()]
|
||||
+ f"{inner_type}[]"
|
||||
+ type_str[match.end():]
|
||||
)
|
||||
|
||||
return type_str
|
||||
|
||||
def escape_text_outside_code_blocks(markdown: str) -> str:
|
||||
"""
|
||||
Splits content by fenced code blocks, escapes MDX-unsafe characters
|
||||
(<, >, {, }) only in the text outside those code blocks.
|
||||
"""
|
||||
# A regex to capture fenced code blocks with ```
|
||||
parts = re.split(r'(```.*?```)', markdown, flags=re.DOTALL)
|
||||
|
||||
# Even indices (0, 2, 4, ...) are outside code blocks,
|
||||
# odd indices (1, 3, 5, ...) are actual code blocks.
|
||||
for i in range(0, len(parts), 2):
|
||||
# Only escape in parts outside code blocks
|
||||
parts[i] = (parts[i]
|
||||
.replace('<', '<')
|
||||
.replace('>', '>')
|
||||
.replace('{', '{')
|
||||
.replace('}', '}')
|
||||
)
|
||||
return "".join(parts)
|
||||
|
||||
def get_base_type(ts_type: str) -> str:
|
||||
"""
|
||||
Given a TypeScript-like type (e.g. "Drawing[][]"), return the
|
||||
'base' portion by stripping trailing "[]". For "Drawing[][]",
|
||||
returns "Drawing". For "Array.<Drawing>", you'd convert it first
|
||||
to "Drawing[]" then return "Drawing".
|
||||
"""
|
||||
while ts_type.endswith('[]'):
|
||||
ts_type = ts_type[:-2]
|
||||
return ts_type
|
||||
|
||||
def generate_data_types_markdown(types, enumerations, classes, root='../../'):
|
||||
param_types_md = ' |'.join(types)
|
||||
"""
|
||||
1) Converts each type from JSDoc (e.g., Array.<T>) to T[].
|
||||
2) Processes union types by splitting them using '|'.
|
||||
3) Supports multidimensional arrays, e.g., (string|ApiRange|number)[].
|
||||
4) If the base type matches the name of an enumeration or class, generates a link.
|
||||
5) The final types are joined using " | ".
|
||||
"""
|
||||
# Convert each type from JSDoc format to TypeScript format (e.g., T[])
|
||||
converted = [convert_jsdoc_array_to_ts(t) for t in types]
|
||||
|
||||
for enum in enumerations:
|
||||
if enum['name'] in types:
|
||||
param_types_md = param_types_md.replace(enum['name'], f"[{enum['name']}]({root}Enumeration/{enum['name']}.md)")
|
||||
for cls in classes:
|
||||
if cls in types:
|
||||
param_types_md = param_types_md.replace(cls, f"[{cls}]({root}{cls}/{cls}.md)")
|
||||
# Set of primitive types
|
||||
primitive_types = {"string", "number", "boolean", "null", "undefined", "any", "object", "false", "true", "json", "function", "{}"}
|
||||
|
||||
def replace_with_links(match):
|
||||
def is_primitive(type):
|
||||
if (type.lower() in primitive_types or
|
||||
(type.startswith('"') and type.endswith('"')) or
|
||||
(type.startswith("'") and type.endswith("'")) or
|
||||
type.replace('.', '', 1).isdigit() or
|
||||
(type.startswith('-') and type[1:].replace('.', '', 1).isdigit())):
|
||||
return True
|
||||
return False
|
||||
|
||||
def link_if_known(ts_type):
|
||||
ts_type = ts_type.strip()
|
||||
# Count the number of array dimensions, e.g., "[][]" has 2 dimensions
|
||||
array_dims = 0
|
||||
while ts_type.endswith("[]"):
|
||||
array_dims += 1
|
||||
ts_type = ts_type[:-2].strip()
|
||||
|
||||
# Process generic types, e.g., Object.<string, editorType>
|
||||
if ".<" in ts_type and ts_type.endswith(">"):
|
||||
import re
|
||||
m = re.match(r'^(.*?)\.<(.*)>$', ts_type)
|
||||
if m:
|
||||
base_part = m.group(1).strip()
|
||||
generic_args_str = m.group(2).strip()
|
||||
# Process the base part of the type
|
||||
found = False
|
||||
for enum in enumerations:
|
||||
if enum['name'] == base_part:
|
||||
used_enumerations.add(base_part)
|
||||
base_result = f"[{base_part}]({root}Enumeration/{base_part}.md)"
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
if base_part in classes:
|
||||
base_result = f"[{base_part}]({root}{base_part}/{base_part}.md)"
|
||||
elif is_primitive(base_part):
|
||||
base_result = base_part
|
||||
elif cur_editor_name == "forms":
|
||||
base_result = f"[{base_part}]({root}../text-document-api/{base_part}/{base_part}.md)"
|
||||
else:
|
||||
print(f"Unknown type encountered: {base_part}")
|
||||
base_result = base_part
|
||||
# Split the generic parameters by commas and process each recursively
|
||||
generic_args = [link_if_known(x) for x in generic_args_str.split(",")]
|
||||
result = base_result + ".<" + ", ".join(generic_args) + ">"
|
||||
result += "[]" * array_dims
|
||||
return result
|
||||
|
||||
# Process union types: if the type is enclosed in parentheses
|
||||
if ts_type.startswith("(") and ts_type.endswith(")"):
|
||||
inner = ts_type[1:-1].strip()
|
||||
subtypes = [sub.strip() for sub in inner.split("|")]
|
||||
if len(subtypes) == 1:
|
||||
result = link_if_known(subtypes[0])
|
||||
else:
|
||||
processed = [link_if_known(subtype) for subtype in subtypes]
|
||||
result = "(" + " | ".join(processed) + ")"
|
||||
result += "[]" * array_dims
|
||||
return result
|
||||
|
||||
# If not a generic or union type – process the base type
|
||||
else:
|
||||
base = ts_type
|
||||
found = False
|
||||
for enum in enumerations:
|
||||
if enum['name'] == base:
|
||||
used_enumerations.add(base)
|
||||
result = f"[{base}]({root}Enumeration/{base}.md)"
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
if base in classes:
|
||||
result = f"[{base}]({root}{base}/{base}.md)"
|
||||
elif is_primitive(base):
|
||||
result = base
|
||||
elif cur_editor_name == "forms":
|
||||
result = f"[{base}]({root}../text-document-api/{base}/{base}.md)"
|
||||
else:
|
||||
print(f"Unknown type encountered: {base}")
|
||||
result = base
|
||||
result += "[]" * array_dims
|
||||
return result
|
||||
|
||||
# Apply link_if_known to each converted type
|
||||
linked = [link_if_known(ts_t) for ts_t in converted]
|
||||
|
||||
# Join results using " | "
|
||||
param_types_md = r' | '.join(linked)
|
||||
param_types_md = param_types_md.replace("|", r"\|")
|
||||
|
||||
# Escape remaining angle brackets for generics
|
||||
def replace_leftover_generics(match):
|
||||
element = match.group(1).strip()
|
||||
base_type = element.split('.')[0] # Take only the first part before the dot, if any
|
||||
if any(enum['name'] == base_type for enum in enumerations):
|
||||
return f"<[{element}](../../Enumeration/{base_type}.md)>"
|
||||
elif base_type in classes:
|
||||
return f"<[{element}](../../{base_type}/{base_type}.md)>"
|
||||
return f"<{element}>"
|
||||
|
||||
return re.sub(r'<([^<>]+)>', replace_with_links, param_types_md)
|
||||
return f"<{element}>"
|
||||
|
||||
param_types_md = re.sub(r'<([^<>]+)>', replace_leftover_generics, param_types_md)
|
||||
|
||||
return param_types_md
|
||||
|
||||
|
||||
def generate_class_markdown(class_name, methods, properties, enumerations, classes):
|
||||
content = f"# {class_name}\n\nRepresents the {class_name} class.\n\n"
|
||||
|
||||
content += generate_properties_markdown(properties, enumerations, classes, '../')
|
||||
content += generate_properties_markdown(properties, enumerations, classes)
|
||||
|
||||
content += "## Methods\n\n"
|
||||
for method in methods:
|
||||
content += "\n## Methods\n\n"
|
||||
content += "| Method | Returns | Description |\n"
|
||||
content += "| ------ | ------- | ----------- |\n"
|
||||
|
||||
for method in sorted(methods, key=lambda m: m['name']):
|
||||
method_name = method['name']
|
||||
content += f"- [{method_name}](./Methods/{method_name}.md)\n"
|
||||
return content
|
||||
|
||||
# Get the type of return values
|
||||
returns = method.get('returns', [])
|
||||
if returns:
|
||||
return_type_list = returns[0].get('type', {}).get('names', [])
|
||||
returns_markdown = generate_data_types_markdown(return_type_list, enumerations, classes, '../')
|
||||
else:
|
||||
returns_markdown = "None"
|
||||
|
||||
# Processing the method description
|
||||
description = remove_line_breaks(correct_description(method.get('description', 'No description provided.'), '../'))
|
||||
|
||||
# Form a link to the method document
|
||||
method_link = f"[{method_name}](./Methods/{method_name}.md)"
|
||||
|
||||
content += f"| {method_link} | {returns_markdown} | {description} |\n"
|
||||
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def generate_method_markdown(method, enumerations, classes):
|
||||
def generate_method_markdown(method, enumerations, classes, example_editor_name):
|
||||
method_name = method['name']
|
||||
description = method.get('description', 'No description provided.')
|
||||
description = correct_description(description)
|
||||
description = correct_description(description, '../../')
|
||||
params = method.get('params', [])
|
||||
returns = method.get('returns', [])
|
||||
example = method.get('example', '')
|
||||
@ -98,14 +329,14 @@ def generate_method_markdown(method, enumerations, classes):
|
||||
|
||||
content = f"# {method_name}\n\n{description}\n\n"
|
||||
|
||||
# Syntax section
|
||||
param_list = ', '.join([param['name'] for param in params]) if params else ''
|
||||
content += f"## Syntax\n\nexpression.{method_name}({param_list});\n\n"
|
||||
# Syntax
|
||||
param_list = ', '.join([param['name'] for param in params if '.' not in param['name']]) if params else ''
|
||||
content += f"## Syntax\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n"
|
||||
if memberof:
|
||||
content += f"`expression` - A variable that represents a [{memberof}](../{memberof}.md) class.\n\n"
|
||||
|
||||
# Parameters
|
||||
content += "## Parameters\n\n"
|
||||
|
||||
if params:
|
||||
content += "| **Name** | **Required/Optional** | **Data type** | **Default** | **Description** |\n"
|
||||
content += "| ------------- | ------------- | ------------- | ------------- | ------------- |\n"
|
||||
@ -113,7 +344,7 @@ def generate_method_markdown(method, enumerations, classes):
|
||||
param_name = param.get('name', 'Unnamed')
|
||||
param_types = param.get('type', {}).get('names', []) if param.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(param_types, enumerations, classes)
|
||||
param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.')))
|
||||
param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.'), '../../'))
|
||||
param_required = "Required" if not param.get('optional') else "Optional"
|
||||
param_default = correct_default_value(param.get('defaultvalue', ''), enumerations, classes)
|
||||
|
||||
@ -121,91 +352,133 @@ def generate_method_markdown(method, enumerations, classes):
|
||||
else:
|
||||
content += "This method doesn't have any parameters.\n"
|
||||
|
||||
# Returns
|
||||
content += "\n## Returns\n\n"
|
||||
if returns:
|
||||
return_type = ', '.join(returns[0].get('type', {}).get('names', [])) if returns[0].get('type') else 'Unknown'
|
||||
|
||||
# Check for enumerations and classes in return type and add links if they exist
|
||||
return_type_md = generate_data_types_markdown([return_type], enumerations, classes)
|
||||
return_type_list = returns[0].get('type', {}).get('names', [])
|
||||
return_type_md = generate_data_types_markdown(return_type_list, enumerations, classes)
|
||||
content += return_type_md
|
||||
else:
|
||||
content += "This method doesn't return any data."
|
||||
|
||||
# Example
|
||||
if example:
|
||||
# Separate comment and code, and remove comment symbols
|
||||
comment, code = example.split('```js', 1)
|
||||
comment = remove_js_comments(comment)
|
||||
content += f"\n\n## Example\n\n{comment}\n\n```javascript\n{code.strip()}\n"
|
||||
# Separate comment and code, remove JS comments
|
||||
if '```js' in example:
|
||||
comment, code = example.split('```js', 1)
|
||||
comment = remove_js_comments(comment)
|
||||
content += f"\n\n## Example\n\n{comment}\n\n```javascript {example_editor_name}\n{code.strip()}\n"
|
||||
else:
|
||||
# If there's no triple-backtick structure, just show it as code
|
||||
cleaned_example = remove_js_comments(example)
|
||||
content += f"\n\n## Example\n\n```javascript {example_editor_name}\n{cleaned_example}\n```\n"
|
||||
|
||||
return content
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def generate_properties_markdown(properties, enumerations, classes, root='../../'):
|
||||
if (properties is None):
|
||||
def generate_properties_markdown(properties, enumerations, classes, root='../'):
|
||||
if properties is None:
|
||||
return ''
|
||||
|
||||
content = "## Properties\n\n"
|
||||
content += "| Name | Type | Description |\n"
|
||||
content += "| ---- | ---- | ----------- |\n"
|
||||
for prop in properties:
|
||||
|
||||
for prop in sorted(properties, key=lambda m: m['name']):
|
||||
prop_name = prop['name']
|
||||
prop_description = prop.get('description', 'No description provided.')
|
||||
prop_description = remove_line_breaks(correct_description(prop_description))
|
||||
param_types_md = generate_data_types_markdown(prop['type']['names'], enumerations, classes, root)
|
||||
prop_description = remove_line_breaks(correct_description(prop_description, root))
|
||||
prop_types = prop['type']['names'] if prop.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(prop_types, enumerations, classes, root)
|
||||
content += f"| {prop_name} | {param_types_md} | {prop_description} |\n"
|
||||
content += "\n"
|
||||
|
||||
return content
|
||||
|
||||
# Escape outside code blocks
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def generate_enumeration_markdown(enumeration, enumerations, classes):
|
||||
def generate_enumeration_markdown(enumeration, enumerations, classes, example_editor_name):
|
||||
enum_name = enumeration['name']
|
||||
|
||||
if enum_name not in used_enumerations:
|
||||
return None
|
||||
|
||||
description = enumeration.get('description', 'No description provided.')
|
||||
description = correct_description(description)
|
||||
description = correct_description(description, '../')
|
||||
example = enumeration.get('example', '')
|
||||
|
||||
content = f"# {enum_name}\n\n{description}\n\n"
|
||||
|
||||
if 'TypeUnion' == enumeration['type']['parsedType']['type']:
|
||||
ptype = enumeration['type']['parsedType']
|
||||
if ptype['type'] == 'TypeUnion':
|
||||
enum_empty = True # is empty enum
|
||||
|
||||
content += "## Type\n\nEnumeration\n\n"
|
||||
content += "## Values\n\n"
|
||||
elements = enumeration['type']['parsedType']['elements']
|
||||
for element in elements:
|
||||
element_name = element['name'] if element['type'] != 'NullLiteral' else 'null'
|
||||
# Check if element is in enumerations or classes before adding link
|
||||
if any(enum['name'] == element_name for enum in enumerations):
|
||||
content += f"- [{element_name}](../../Enumeration/{element_name}.md)\n"
|
||||
elif element_name in classes:
|
||||
content += f"- [{element_name}](../../{element_name}/{element_name}.md)\n"
|
||||
else:
|
||||
content += f"- {element_name}\n"
|
||||
# Each top-level name in the union
|
||||
for raw_t in enumeration['type']['names']:
|
||||
ts_t = convert_jsdoc_array_to_ts(raw_t)
|
||||
|
||||
# Attempt linking: we compare the raw type to enumerations/classes
|
||||
if any(enum['name'] == raw_t for enum in enumerations):
|
||||
used_enumerations.add(raw_t)
|
||||
content += f"- [{ts_t}](../Enumeration/{raw_t}.md)\n"
|
||||
enum_empty = False
|
||||
elif raw_t in classes:
|
||||
content += f"- [{ts_t}](../{raw_t}/{raw_t}.md)\n"
|
||||
enum_empty = False
|
||||
elif ts_t.find('Api') == -1:
|
||||
content += f"- {ts_t}\n"
|
||||
enum_empty = False
|
||||
|
||||
if enum_empty == True:
|
||||
return None
|
||||
elif enumeration['properties'] is not None:
|
||||
content += "## Type\n\nObject\n\n"
|
||||
content += generate_properties_markdown(enumeration['properties'], enumerations, classes)
|
||||
else:
|
||||
content += "## Type\n\n"
|
||||
# If it's not a union and has no properties, simply print the type(s).
|
||||
types = enumeration['type']['names']
|
||||
for t in types:
|
||||
t = generate_data_types_markdown([t], enumerations, classes)
|
||||
content += t + "\n\n"
|
||||
t_md = generate_data_types_markdown(types, enumerations, classes)
|
||||
content += t_md + "\n\n"
|
||||
|
||||
# Example
|
||||
if example:
|
||||
# Separate comment and code, and remove comment symbols
|
||||
comment, code = example.split('```js', 1)
|
||||
comment = remove_js_comments(comment)
|
||||
content += f"\n\n## Example\n\n{comment}\n\n```javascript\n{code.strip()}\n"
|
||||
if '```js' in example:
|
||||
comment, code = example.split('```js', 1)
|
||||
comment = remove_js_comments(comment)
|
||||
content += f"\n\n## Example\n\n{comment}\n\n```javascript {example_editor_name}\n{code.strip()}\n"
|
||||
else:
|
||||
# If there's no triple-backtick structure
|
||||
cleaned_example = remove_js_comments(example)
|
||||
content += f"\n\n## Example\n\n```javascript {example_editor_name}\n{cleaned_example}\n```\n"
|
||||
|
||||
return content
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def process_doclets(data, output_dir, editor_name):
|
||||
global cur_editor_name
|
||||
cur_editor_name = editor_name
|
||||
|
||||
def process_doclets(data, output_dir):
|
||||
classes = {}
|
||||
classes_props = {}
|
||||
enumerations = []
|
||||
editor_dir = os.path.join(output_dir, editors[editor_name])
|
||||
example_editor_name = 'editor-'
|
||||
|
||||
if editor_name == 'word':
|
||||
example_editor_name += 'docx'
|
||||
elif editor_name == 'forms':
|
||||
example_editor_name += 'pdf'
|
||||
elif editor_name == 'slide':
|
||||
example_editor_name += 'pptx'
|
||||
elif editor_name == 'cell':
|
||||
example_editor_name += 'xlsx'
|
||||
|
||||
for doclet in data:
|
||||
if doclet['kind'] == 'class':
|
||||
class_name = doclet['name']
|
||||
classes[class_name] = []
|
||||
classes_props[class_name] = doclet.get('properties', None)
|
||||
if class_name:
|
||||
if class_name not in classes:
|
||||
classes[class_name] = []
|
||||
classes_props[class_name] = doclet.get('properties', None)
|
||||
elif doclet['kind'] == 'function':
|
||||
class_name = doclet.get('memberof')
|
||||
if class_name:
|
||||
@ -217,37 +490,66 @@ def process_doclets(data, output_dir):
|
||||
|
||||
# Process classes
|
||||
for class_name, methods in classes.items():
|
||||
class_dir = os.path.join(output_dir, class_name)
|
||||
if (len(methods) == 0):
|
||||
continue
|
||||
|
||||
class_dir = os.path.join(editor_dir, class_name)
|
||||
methods_dir = os.path.join(class_dir, 'Methods')
|
||||
os.makedirs(methods_dir, exist_ok=True)
|
||||
|
||||
# Write class file
|
||||
class_content = generate_class_markdown(class_name, methods, classes_props[class_name], enumerations, classes)
|
||||
class_content = generate_class_markdown(
|
||||
class_name,
|
||||
methods,
|
||||
classes_props[class_name],
|
||||
enumerations,
|
||||
classes
|
||||
)
|
||||
write_markdown_file(os.path.join(class_dir, f"{class_name}.md"), class_content)
|
||||
|
||||
# Write method files
|
||||
for method in methods:
|
||||
method_content = generate_method_markdown(method, enumerations, classes)
|
||||
write_markdown_file(os.path.join(methods_dir, f"{method['name']}.md"), method_content)
|
||||
method_file_path = os.path.join(methods_dir, f"{method['name']}.md")
|
||||
method_content = generate_method_markdown(method, enumerations, classes, example_editor_name)
|
||||
write_markdown_file(method_file_path, method_content)
|
||||
|
||||
if not method.get('example', ''):
|
||||
missing_examples.append(os.path.relpath(method_file_path, output_dir))
|
||||
|
||||
# Process enumerations
|
||||
enum_dir = os.path.join(output_dir, 'Enumeration')
|
||||
enum_dir = os.path.join(editor_dir, 'Enumeration')
|
||||
os.makedirs(enum_dir, exist_ok=True)
|
||||
|
||||
# idle run
|
||||
prev_used_count = -1
|
||||
while len(used_enumerations) != prev_used_count:
|
||||
prev_used_count = len(used_enumerations)
|
||||
for enum in [e for e in enumerations if e['name'] in used_enumerations]:
|
||||
enum_content = generate_enumeration_markdown(enum, enumerations, classes, example_editor_name)
|
||||
|
||||
for enum in enumerations:
|
||||
enum_content = generate_enumeration_markdown(enum, enumerations, classes)
|
||||
write_markdown_file(os.path.join(enum_dir, f"{enum['name']}.md"), enum_content)
|
||||
enum_file_path = os.path.join(enum_dir, f"{enum['name']}.md")
|
||||
enum_content = generate_enumeration_markdown(enum, enumerations, classes, example_editor_name)
|
||||
if enum_content is None:
|
||||
continue
|
||||
|
||||
write_markdown_file(enum_file_path, enum_content)
|
||||
if not enum.get('example', ''):
|
||||
missing_examples.append(os.path.relpath(enum_file_path, output_dir))
|
||||
|
||||
def generate(output_dir):
|
||||
print('Generating Markdown documentation...')
|
||||
|
||||
generate_docs_json.generate(output_dir + 'tmp_json', md=True)
|
||||
for editor_name in editors:
|
||||
input_file = os.path.join(output_dir + 'tmp_json', editor_name + ".json")
|
||||
os.makedirs(output_dir + f'/{editor_name.title()}', exist_ok=True)
|
||||
for editor_name, folder_name in editors.items():
|
||||
input_file = os.path.join(output_dir + '/tmp_json', editor_name + ".json")
|
||||
|
||||
shutil.rmtree(output_dir + f'/{folder_name}', ignore_errors=True)
|
||||
os.makedirs(output_dir + f'/{folder_name}')
|
||||
|
||||
data = load_json(input_file)
|
||||
process_doclets(data, output_dir + f'/{editor_name}')
|
||||
used_enumerations.clear()
|
||||
process_doclets(data, output_dir, editor_name)
|
||||
|
||||
shutil.rmtree(output_dir + 'tmp_json')
|
||||
print('Done')
|
||||
@ -262,5 +564,7 @@ if __name__ == "__main__":
|
||||
default="../../../../office-js-api/" # Default value
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
generate(args.destination)
|
||||
print("START_MISSING_EXAMPLES")
|
||||
print(",".join(missing_examples))
|
||||
print("END_MISSING_EXAMPLES")
|
||||
|
||||
573
scripts/sdkjs_common/jsdoc/generate_docs_md_site.py
Normal file
573
scripts/sdkjs_common/jsdoc/generate_docs_md_site.py
Normal file
@ -0,0 +1,573 @@
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import argparse
|
||||
import generate_docs_json
|
||||
|
||||
# Configuration files
|
||||
editors = {
|
||||
"word": "text-document-api",
|
||||
"cell": "spreadsheet-api",
|
||||
"slide": "presentation-api",
|
||||
"forms": "form-api"
|
||||
}
|
||||
|
||||
missing_examples = []
|
||||
used_enumerations = set()
|
||||
|
||||
cur_editor_name = None
|
||||
|
||||
def load_json(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
def write_markdown_file(file_path, content):
|
||||
with open(file_path, 'w', encoding='utf-8') as md_file:
|
||||
md_file.write(content)
|
||||
|
||||
def remove_js_comments(text):
|
||||
text = re.sub(r'^\s*//.*$', '', text, flags=re.MULTILINE) # single-line
|
||||
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) # multi-line
|
||||
return text.strip()
|
||||
|
||||
def process_link_tags(text, root=''):
|
||||
"""
|
||||
Finds patterns like {@link ...} and replaces them with Markdown links.
|
||||
If the prefix 'global#' is found, a link to a typedef is generated,
|
||||
otherwise, a link to a class method is created.
|
||||
For a method, if an alias is not specified, the name is left in the format 'Class#Method'.
|
||||
"""
|
||||
reserved_links = {
|
||||
'/docbuilder/global#ShapeType': f"{'../../../../../../' if root == '' else '../../../../../' if root == '../' else root}docs/office-api/usage-api/text-document-api/Enumeration/ShapeType.md",
|
||||
'/plugin/config': 'https://api.onlyoffice.com/docs/plugin-and-macros/structure/manifest/',
|
||||
'/docbuilder/basic': 'https://api.onlyoffice.com/docs/office-api/usage-api/text-document-api/'
|
||||
}
|
||||
|
||||
def replace_link(match):
|
||||
content = match.group(1).strip() # Example: "/docbuilder/global#ShapeType shape type" or "global#ErrorValue ErrorValue"
|
||||
parts = content.split()
|
||||
ref = parts[0]
|
||||
label = parts[1] if len(parts) > 1 else None
|
||||
|
||||
if ref.startswith('/'):
|
||||
# Handle reserved links using mapping
|
||||
if ref in reserved_links:
|
||||
url = reserved_links[ref]
|
||||
display_text = label if label else ref
|
||||
return f"[{display_text}]({url})"
|
||||
else:
|
||||
# If the link is not in the mapping, return the original construction
|
||||
return match.group(0)
|
||||
elif ref.startswith("global#"):
|
||||
# Handle links to typedef (similar logic as before)
|
||||
typedef_name = ref.split("#")[1]
|
||||
used_enumerations.add(typedef_name)
|
||||
display_text = label if label else typedef_name
|
||||
return f"[{display_text}]({root}Enumeration/{typedef_name}.md)"
|
||||
else:
|
||||
# Handle links to class methods like ClassName#MethodName
|
||||
try:
|
||||
class_name, method_name = ref.split("#")
|
||||
except ValueError:
|
||||
return match.group(0)
|
||||
display_text = label if label else ref # Keep the full notation, e.g., "Api#CreateSlide"
|
||||
return f"[{display_text}]({root}{class_name}/Methods/{method_name}.md)"
|
||||
|
||||
return re.sub(r'{@link\s+([^}]+)}', replace_link, text)
|
||||
|
||||
def correct_description(string, root=''):
|
||||
"""
|
||||
Cleans or transforms specific tags in the doclet description:
|
||||
- <b> => ** (bold text)
|
||||
- <note>...</note> => 💡 ...
|
||||
- {@link ...} is replaced with a Markdown link
|
||||
- If the description is missing, returns a default value.
|
||||
- All '\r' characters are replaced with '\n'.
|
||||
"""
|
||||
if string is None:
|
||||
return 'No description provided.'
|
||||
|
||||
# Line breaks
|
||||
string = string.replace('\r', '\\\n')
|
||||
|
||||
# Replace <b> tags with Markdown bold formatting
|
||||
string = re.sub(r'<b>', '-**', string)
|
||||
string = re.sub(r'</b>', '**', string)
|
||||
|
||||
# Replace <note> tags with an icon and text
|
||||
string = re.sub(r'<note>(.*?)</note>', r'💡 \1', string, flags=re.DOTALL)
|
||||
|
||||
# Process {@link ...} constructions
|
||||
string = process_link_tags(string, root)
|
||||
|
||||
return string
|
||||
|
||||
def correct_default_value(value, enumerations, classes):
|
||||
if value is None or value == '':
|
||||
return ''
|
||||
|
||||
if isinstance(value, bool):
|
||||
value = "true" if value else "false"
|
||||
else:
|
||||
value = str(value)
|
||||
|
||||
return generate_data_types_markdown([value], enumerations, classes)
|
||||
|
||||
def remove_line_breaks(string):
|
||||
return re.sub(r'[\r\n]+', ' ', string)
|
||||
|
||||
# Convert Array.<T> => T[] (including nested arrays).
|
||||
def convert_jsdoc_array_to_ts(type_str: str) -> str:
|
||||
"""
|
||||
Recursively replaces 'Array.<T>' with 'T[]',
|
||||
handling nested arrays like 'Array.<Array.<string>>' => 'string[][]'.
|
||||
"""
|
||||
pattern = re.compile(r'Array\.<([^>]+)>')
|
||||
|
||||
while True:
|
||||
match = pattern.search(type_str)
|
||||
if not match:
|
||||
break
|
||||
|
||||
inner_type = match.group(1).strip()
|
||||
# Recursively convert inner parts
|
||||
inner_type = convert_jsdoc_array_to_ts(inner_type)
|
||||
|
||||
# Replace the outer Array.<...> with ...[]
|
||||
type_str = (
|
||||
type_str[:match.start()]
|
||||
+ f"{inner_type}[]"
|
||||
+ type_str[match.end():]
|
||||
)
|
||||
|
||||
return type_str
|
||||
|
||||
def escape_text_outside_code_blocks(markdown: str) -> str:
|
||||
"""
|
||||
Splits content by fenced code blocks, escapes MDX-unsafe characters
|
||||
(<, >, {, }) only in the text outside those code blocks.
|
||||
"""
|
||||
# A regex to capture fenced code blocks with ```
|
||||
parts = re.split(r'(```.*?```)', markdown, flags=re.DOTALL)
|
||||
|
||||
# Even indices (0, 2, 4, ...) are outside code blocks,
|
||||
# odd indices (1, 3, 5, ...) are actual code blocks.
|
||||
for i in range(0, len(parts), 2):
|
||||
# Only escape in parts outside code blocks
|
||||
parts[i] = (parts[i]
|
||||
.replace('<', '<')
|
||||
.replace('>', '>')
|
||||
.replace('{', '{')
|
||||
.replace('}', '}')
|
||||
)
|
||||
return "".join(parts)
|
||||
|
||||
def get_base_type(ts_type: str) -> str:
|
||||
"""
|
||||
Given a TypeScript-like type (e.g. "Drawing[][]"), return the
|
||||
'base' portion by stripping trailing "[]". For "Drawing[][]",
|
||||
returns "Drawing". For "Array.<Drawing>", you'd convert it first
|
||||
to "Drawing[]" then return "Drawing".
|
||||
"""
|
||||
while ts_type.endswith('[]'):
|
||||
ts_type = ts_type[:-2]
|
||||
return ts_type
|
||||
|
||||
def generate_data_types_markdown(types, enumerations, classes, root='../../'):
|
||||
"""
|
||||
1) Converts each type from JSDoc (e.g., Array.<T>) to T[].
|
||||
2) Processes union types by splitting them using '|'.
|
||||
3) Supports multidimensional arrays, e.g., (string|ApiRange|number)[].
|
||||
4) If the base type matches the name of an enumeration or class, generates a link.
|
||||
5) The final types are joined using " | ".
|
||||
"""
|
||||
# Convert each type from JSDoc format to TypeScript format (e.g., T[])
|
||||
converted = [convert_jsdoc_array_to_ts(t) for t in types]
|
||||
|
||||
# Set of primitive types
|
||||
primitive_types = {"string", "number", "boolean", "null", "undefined", "any", "object", "false", "true", "json", "function", "{}"}
|
||||
|
||||
def is_primitive(type):
|
||||
if (type.lower() in primitive_types or
|
||||
(type.startswith('"') and type.endswith('"')) or
|
||||
(type.startswith("'") and type.endswith("'")) or
|
||||
type.replace('.', '', 1).isdigit() or
|
||||
(type.startswith('-') and type[1:].replace('.', '', 1).isdigit())):
|
||||
return True
|
||||
return False
|
||||
|
||||
def link_if_known(ts_type):
|
||||
ts_type = ts_type.strip()
|
||||
# Count the number of array dimensions, e.g., "[][]" has 2 dimensions
|
||||
array_dims = 0
|
||||
while ts_type.endswith("[]"):
|
||||
array_dims += 1
|
||||
ts_type = ts_type[:-2].strip()
|
||||
|
||||
# Process generic types, e.g., Object.<string, editorType>
|
||||
if ".<" in ts_type and ts_type.endswith(">"):
|
||||
import re
|
||||
m = re.match(r'^(.*?)\.<(.*)>$', ts_type)
|
||||
if m:
|
||||
base_part = m.group(1).strip()
|
||||
generic_args_str = m.group(2).strip()
|
||||
# Process the base part of the type
|
||||
found = False
|
||||
for enum in enumerations:
|
||||
if enum['name'] == base_part:
|
||||
used_enumerations.add(base_part)
|
||||
base_result = f"[{base_part}]({root}Enumeration/{base_part}.md)"
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
if base_part in classes:
|
||||
base_result = f"[{base_part}]({root}{base_part}/{base_part}.md)"
|
||||
elif is_primitive(base_part):
|
||||
base_result = base_part
|
||||
elif cur_editor_name == "forms":
|
||||
base_result = f"[{base_part}]({root}../text-document-api/{base_part}/{base_part}.md)"
|
||||
else:
|
||||
print(f"Unknown type encountered: {base_part}")
|
||||
base_result = base_part
|
||||
# Split the generic parameters by commas and process each recursively
|
||||
generic_args = [link_if_known(x) for x in generic_args_str.split(",")]
|
||||
result = base_result + ".<" + ", ".join(generic_args) + ">"
|
||||
result += "[]" * array_dims
|
||||
return result
|
||||
|
||||
# Process union types: if the type is enclosed in parentheses
|
||||
if ts_type.startswith("(") and ts_type.endswith(")"):
|
||||
inner = ts_type[1:-1].strip()
|
||||
subtypes = [sub.strip() for sub in inner.split("|")]
|
||||
if len(subtypes) == 1:
|
||||
result = link_if_known(subtypes[0])
|
||||
else:
|
||||
processed = [link_if_known(subtype) for subtype in subtypes]
|
||||
result = "(" + " | ".join(processed) + ")"
|
||||
result += "[]" * array_dims
|
||||
return result
|
||||
|
||||
# If not a generic or union type – process the base type
|
||||
else:
|
||||
base = ts_type
|
||||
found = False
|
||||
for enum in enumerations:
|
||||
if enum['name'] == base:
|
||||
used_enumerations.add(base)
|
||||
result = f"[{base}]({root}Enumeration/{base}.md)"
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
if base in classes:
|
||||
result = f"[{base}]({root}{base}/{base}.md)"
|
||||
elif is_primitive(base):
|
||||
result = base
|
||||
elif cur_editor_name == "forms":
|
||||
result = f"[{base}]({root}../text-document-api/{base}/{base}.md)"
|
||||
else:
|
||||
print(f"Unknown type encountered: {base}")
|
||||
result = base
|
||||
result += "[]" * array_dims
|
||||
return result
|
||||
|
||||
# Apply link_if_known to each converted type
|
||||
linked = [link_if_known(ts_t) for ts_t in converted]
|
||||
|
||||
# Join results using " | "
|
||||
param_types_md = r' | '.join(linked)
|
||||
param_types_md = param_types_md.replace("|", r"\|")
|
||||
|
||||
# Escape remaining angle brackets for generics
|
||||
def replace_leftover_generics(match):
|
||||
element = match.group(1).strip()
|
||||
return f"<{element}>"
|
||||
|
||||
param_types_md = re.sub(r'<([^<>]+)>', replace_leftover_generics, param_types_md)
|
||||
|
||||
return param_types_md
|
||||
|
||||
|
||||
def generate_class_markdown(class_name, methods, properties, enumerations, classes):
|
||||
content = f"# {class_name}\n\nRepresents the {class_name} class.\n\n"
|
||||
|
||||
content += generate_properties_markdown(properties, enumerations, classes)
|
||||
|
||||
content += "\n## Methods\n\n"
|
||||
content += "| Method | Returns | Description |\n"
|
||||
content += "| ------ | ------- | ----------- |\n"
|
||||
|
||||
for method in sorted(methods, key=lambda m: m['name']):
|
||||
method_name = method['name']
|
||||
|
||||
# Get the type of return values
|
||||
returns = method.get('returns', [])
|
||||
if returns:
|
||||
return_type_list = returns[0].get('type', {}).get('names', [])
|
||||
returns_markdown = generate_data_types_markdown(return_type_list, enumerations, classes, '../')
|
||||
else:
|
||||
returns_markdown = "None"
|
||||
|
||||
# Processing the method description
|
||||
description = remove_line_breaks(correct_description(method.get('description', 'No description provided.'), '../'))
|
||||
|
||||
# Form a link to the method document
|
||||
method_link = f"[{method_name}](./Methods/{method_name}.md)"
|
||||
|
||||
content += f"| {method_link} | {returns_markdown} | {description} |\n"
|
||||
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def generate_method_markdown(method, enumerations, classes, example_editor_name):
|
||||
method_name = method['name']
|
||||
description = method.get('description', 'No description provided.')
|
||||
description = correct_description(description, '../../')
|
||||
params = method.get('params', [])
|
||||
returns = method.get('returns', [])
|
||||
example = method.get('example', '')
|
||||
memberof = method.get('memberof', '')
|
||||
|
||||
content = f"# {method_name}\n\n{description}\n\n"
|
||||
|
||||
# Syntax
|
||||
param_list = ', '.join([param['name'] for param in params if '.' not in param['name']]) if params else ''
|
||||
content += f"## Syntax\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n"
|
||||
if memberof:
|
||||
content += f"`expression` - A variable that represents a [{memberof}](../{memberof}.md) class.\n\n"
|
||||
|
||||
# Parameters
|
||||
content += "## Parameters\n\n"
|
||||
if params:
|
||||
content += "| **Name** | **Required/Optional** | **Data type** | **Default** | **Description** |\n"
|
||||
content += "| ------------- | ------------- | ------------- | ------------- | ------------- |\n"
|
||||
for param in params:
|
||||
param_name = param.get('name', 'Unnamed')
|
||||
param_types = param.get('type', {}).get('names', []) if param.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(param_types, enumerations, classes)
|
||||
param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.'), '../../'))
|
||||
param_required = "Required" if not param.get('optional') else "Optional"
|
||||
param_default = correct_default_value(param.get('defaultvalue', ''), enumerations, classes)
|
||||
|
||||
content += f"| {param_name} | {param_required} | {param_types_md} | {param_default} | {param_desc} |\n"
|
||||
else:
|
||||
content += "This method doesn't have any parameters.\n"
|
||||
|
||||
# Returns
|
||||
content += "\n## Returns\n\n"
|
||||
if returns:
|
||||
return_type_list = returns[0].get('type', {}).get('names', [])
|
||||
return_type_md = generate_data_types_markdown(return_type_list, enumerations, classes)
|
||||
content += return_type_md
|
||||
else:
|
||||
content += "This method doesn't return any data."
|
||||
|
||||
# Example
|
||||
if example:
|
||||
# Separate comment and code, remove JS comments
|
||||
if '```js' in example:
|
||||
comment, code = example.split('```js', 1)
|
||||
comment = remove_js_comments(comment)
|
||||
content += f"\n\n## Example\n\n{comment}\n\n```javascript {example_editor_name}\n{code.strip()}\n"
|
||||
else:
|
||||
# If there's no triple-backtick structure, just show it as code
|
||||
cleaned_example = remove_js_comments(example)
|
||||
content += f"\n\n## Example\n\n```javascript {example_editor_name}\n{cleaned_example}\n```\n"
|
||||
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def generate_properties_markdown(properties, enumerations, classes, root='../'):
|
||||
if properties is None:
|
||||
return ''
|
||||
|
||||
content = "## Properties\n\n"
|
||||
content += "| Name | Type | Description |\n"
|
||||
content += "| ---- | ---- | ----------- |\n"
|
||||
|
||||
for prop in sorted(properties, key=lambda m: m['name']):
|
||||
prop_name = prop['name']
|
||||
prop_description = prop.get('description', 'No description provided.')
|
||||
prop_description = remove_line_breaks(correct_description(prop_description, root))
|
||||
prop_types = prop['type']['names'] if prop.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(prop_types, enumerations, classes, root)
|
||||
content += f"| {prop_name} | {param_types_md} | {prop_description} |\n"
|
||||
|
||||
# Escape outside code blocks
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def generate_enumeration_markdown(enumeration, enumerations, classes, example_editor_name):
|
||||
enum_name = enumeration['name']
|
||||
|
||||
if enum_name not in used_enumerations:
|
||||
return None
|
||||
|
||||
description = enumeration.get('description', 'No description provided.')
|
||||
description = correct_description(description, '../')
|
||||
example = enumeration.get('example', '')
|
||||
|
||||
content = f"# {enum_name}\n\n{description}\n\n"
|
||||
|
||||
ptype = enumeration['type']['parsedType']
|
||||
if ptype['type'] == 'TypeUnion':
|
||||
enum_empty = True # is empty enum
|
||||
|
||||
content += "## Type\n\nEnumeration\n\n"
|
||||
content += "## Values\n\n"
|
||||
# Each top-level name in the union
|
||||
for raw_t in enumeration['type']['names']:
|
||||
ts_t = convert_jsdoc_array_to_ts(raw_t)
|
||||
|
||||
# Attempt linking: we compare the raw type to enumerations/classes
|
||||
if any(enum['name'] == raw_t for enum in enumerations):
|
||||
used_enumerations.add(raw_t)
|
||||
content += f"- [{ts_t}](../Enumeration/{raw_t}.md)\n"
|
||||
enum_empty = False
|
||||
elif raw_t in classes:
|
||||
content += f"- [{ts_t}](../{raw_t}/{raw_t}.md)\n"
|
||||
enum_empty = False
|
||||
elif ts_t.find('Api') == -1:
|
||||
content += f"- {ts_t}\n"
|
||||
enum_empty = False
|
||||
|
||||
if enum_empty == True:
|
||||
return None
|
||||
elif enumeration['properties'] is not None:
|
||||
content += "## Type\n\nObject\n\n"
|
||||
content += generate_properties_markdown(enumeration['properties'], enumerations, classes)
|
||||
else:
|
||||
content += "## Type\n\n"
|
||||
# If it's not a union and has no properties, simply print the type(s).
|
||||
types = enumeration['type']['names']
|
||||
t_md = generate_data_types_markdown(types, enumerations, classes)
|
||||
content += t_md + "\n\n"
|
||||
|
||||
# Example
|
||||
if example:
|
||||
if '```js' in example:
|
||||
comment, code = example.split('```js', 1)
|
||||
comment = remove_js_comments(comment)
|
||||
content += f"\n\n## Example\n\n{comment}\n\n```javascript {example_editor_name}\n{code.strip()}\n"
|
||||
else:
|
||||
# If there's no triple-backtick structure
|
||||
cleaned_example = remove_js_comments(example)
|
||||
content += f"\n\n## Example\n\n```javascript {example_editor_name}\n{cleaned_example}\n```\n"
|
||||
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def process_doclets(data, output_dir, editor_name):
|
||||
global cur_editor_name
|
||||
cur_editor_name = editor_name
|
||||
|
||||
classes = {}
|
||||
classes_props = {}
|
||||
enumerations = []
|
||||
editor_dir = os.path.join(output_dir, editors[editor_name])
|
||||
example_editor_name = 'editor-'
|
||||
|
||||
if editor_name == 'word':
|
||||
example_editor_name += 'docx'
|
||||
elif editor_name == 'forms':
|
||||
example_editor_name += 'pdf'
|
||||
elif editor_name == 'slide':
|
||||
example_editor_name += 'pptx'
|
||||
elif editor_name == 'cell':
|
||||
example_editor_name += 'xlsx'
|
||||
|
||||
for doclet in data:
|
||||
if doclet['kind'] == 'class':
|
||||
class_name = doclet['name']
|
||||
if class_name:
|
||||
if class_name not in classes:
|
||||
classes[class_name] = []
|
||||
classes_props[class_name] = doclet.get('properties', None)
|
||||
elif doclet['kind'] == 'function':
|
||||
class_name = doclet.get('memberof')
|
||||
if class_name:
|
||||
if class_name not in classes:
|
||||
classes[class_name] = []
|
||||
classes[class_name].append(doclet)
|
||||
elif doclet['kind'] == 'typedef':
|
||||
enumerations.append(doclet)
|
||||
|
||||
# Process classes
|
||||
for class_name, methods in classes.items():
|
||||
if (len(methods) == 0):
|
||||
continue
|
||||
|
||||
class_dir = os.path.join(editor_dir, class_name)
|
||||
methods_dir = os.path.join(class_dir, 'Methods')
|
||||
os.makedirs(methods_dir, exist_ok=True)
|
||||
|
||||
# Write class file
|
||||
class_content = generate_class_markdown(
|
||||
class_name,
|
||||
methods,
|
||||
classes_props[class_name],
|
||||
enumerations,
|
||||
classes
|
||||
)
|
||||
write_markdown_file(os.path.join(class_dir, f"{class_name}.md"), class_content)
|
||||
|
||||
# Write method files
|
||||
for method in methods:
|
||||
method_file_path = os.path.join(methods_dir, f"{method['name']}.md")
|
||||
method_content = generate_method_markdown(method, enumerations, classes, example_editor_name)
|
||||
write_markdown_file(method_file_path, method_content)
|
||||
|
||||
if not method.get('example', ''):
|
||||
missing_examples.append(os.path.relpath(method_file_path, output_dir))
|
||||
|
||||
# Process enumerations
|
||||
enum_dir = os.path.join(editor_dir, 'Enumeration')
|
||||
os.makedirs(enum_dir, exist_ok=True)
|
||||
|
||||
# idle run
|
||||
prev_used_count = -1
|
||||
while len(used_enumerations) != prev_used_count:
|
||||
prev_used_count = len(used_enumerations)
|
||||
for enum in [e for e in enumerations if e['name'] in used_enumerations]:
|
||||
enum_content = generate_enumeration_markdown(enum, enumerations, classes, example_editor_name)
|
||||
|
||||
for enum in enumerations:
|
||||
enum_file_path = os.path.join(enum_dir, f"{enum['name']}.md")
|
||||
enum_content = generate_enumeration_markdown(enum, enumerations, classes, example_editor_name)
|
||||
if enum_content is None:
|
||||
continue
|
||||
|
||||
write_markdown_file(enum_file_path, enum_content)
|
||||
if not enum.get('example', ''):
|
||||
missing_examples.append(os.path.relpath(enum_file_path, output_dir))
|
||||
|
||||
def generate(output_dir):
|
||||
print('Generating Markdown documentation...')
|
||||
|
||||
generate_docs_json.generate(output_dir + 'tmp_json', md=True)
|
||||
for editor_name, folder_name in editors.items():
|
||||
input_file = os.path.join(output_dir + '/tmp_json', editor_name + ".json")
|
||||
|
||||
editor_folder_path = os.path.join(output_dir, folder_name)
|
||||
for folder_name in os.listdir(editor_folder_path):
|
||||
folder_path_to_del = os.path.join(editor_folder_path, folder_name)
|
||||
if os.path.isdir(folder_path_to_del):
|
||||
shutil.rmtree(folder_path_to_del, ignore_errors=True)
|
||||
|
||||
data = load_json(input_file)
|
||||
used_enumerations.clear()
|
||||
process_doclets(data, output_dir, editor_name)
|
||||
|
||||
shutil.rmtree(output_dir + 'tmp_json')
|
||||
print('Done')
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Generate documentation")
|
||||
parser.add_argument(
|
||||
"destination",
|
||||
type=str,
|
||||
help="Destination directory for the generated documentation",
|
||||
nargs='?', # Indicates the argument is optional
|
||||
default="../../../../api.onlyoffice.com/site/docs/office-api/usage-api/" # Default value
|
||||
)
|
||||
args = parser.parse_args()
|
||||
generate(args.destination)
|
||||
print("START_MISSING_EXAMPLES")
|
||||
print(",".join(missing_examples))
|
||||
print("END_MISSING_EXAMPLES")
|
||||
111
scripts/sdkjs_common/jsdoc/generate_docs_plugins_json.py
Normal file
111
scripts/sdkjs_common/jsdoc/generate_docs_plugins_json.py
Normal file
@ -0,0 +1,111 @@
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
import argparse
|
||||
import re
|
||||
|
||||
# Configuration files
|
||||
configs = [
|
||||
"./config/plugins/common.json",
|
||||
"./config/plugins/word.json",
|
||||
"./config/plugins/cell.json",
|
||||
"./config/plugins/slide.json",
|
||||
"./config/plugins/forms.json"
|
||||
]
|
||||
|
||||
root = '../../../..'
|
||||
|
||||
def generate(output_dir, md=False):
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
# Generate JSON documentation
|
||||
for config in configs:
|
||||
editor_name = config.split('/')[-1].replace('.json', '')
|
||||
output_file = os.path.join(output_dir, editor_name + ".json")
|
||||
command = f"npx jsdoc -c {config} -X > {output_file}"
|
||||
print(f"Generating {editor_name}.json: {command}")
|
||||
subprocess.run(command, shell=True)
|
||||
|
||||
common_doclets_file = os.path.join(output_dir, 'common.json')
|
||||
with open(common_doclets_file, 'r', encoding='utf-8') as f:
|
||||
common_doclets_json = json.dumps(json.load(f))
|
||||
os.remove(common_doclets_file)
|
||||
|
||||
# Append examples to JSON documentation
|
||||
for config in configs:
|
||||
if (config.find('common') != -1):
|
||||
continue
|
||||
|
||||
editor_name = config.split('/')[-1].replace('.json', '')
|
||||
example_folder_name = editor_name # name of folder with examples
|
||||
output_file = os.path.join(output_dir, editor_name + ".json")
|
||||
|
||||
# Read the JSON file
|
||||
with open(output_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
start_common_doclet_idx = len(data)
|
||||
data += json.loads(common_doclets_json)
|
||||
|
||||
# Modify JSON data
|
||||
for idx, doclet in enumerate(data):
|
||||
if idx == start_common_doclet_idx:
|
||||
example_folder_name = 'common'
|
||||
elif editor_name == 'forms':
|
||||
example_folder_name = 'word'
|
||||
|
||||
if 'see' in doclet:
|
||||
if doclet['see'] is not None:
|
||||
doclet['see'][0] = doclet['see'][0].replace('{Editor}', example_folder_name.title())
|
||||
file_path = f'{root}/' + doclet['see'][0]
|
||||
|
||||
if os.path.exists(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as see_file:
|
||||
example_content = see_file.read()
|
||||
|
||||
# Extract the first line as a comment if it exists
|
||||
lines = example_content.split('\n')
|
||||
if lines[0].startswith('//'):
|
||||
comment = lines[0] + '\n'
|
||||
code_content = '\n'.join(lines[1:])
|
||||
else:
|
||||
comment = ''
|
||||
code_content = example_content
|
||||
|
||||
doclet['examples'] = [remove_js_comments(comment) + code_content]
|
||||
|
||||
if md == False:
|
||||
document_type = editor_name
|
||||
if "forms" == document_type:
|
||||
document_type = "pdf"
|
||||
doclet['description'] = doclet['description'] + f'\n\n## Try it\n\n ```js document-builder={{"documentType": "{document_type}"}}\n{code_content}\n```'
|
||||
|
||||
# Write the modified JSON file back
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
print("Documentation generation for builder completed.")
|
||||
|
||||
def remove_builder_lines(text):
|
||||
lines = text.splitlines() # Split text into lines
|
||||
filtered_lines = [line for line in lines if not line.strip().startswith("builder.")]
|
||||
return "\n".join(filtered_lines)
|
||||
|
||||
def remove_js_comments(text):
|
||||
# Remove single-line comments, leaving text after //
|
||||
text = re.sub(r'^\s*//\s?', '', text, flags=re.MULTILINE)
|
||||
# Remove multi-line comments, leaving text after /*
|
||||
text = re.sub(r'/\*\s*|\s*\*/', '', text, flags=re.DOTALL)
|
||||
return text.strip()
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Generate documentation")
|
||||
parser.add_argument(
|
||||
"destination",
|
||||
type=str,
|
||||
help="Destination directory for the generated documentation",
|
||||
nargs='?', # Indicates the argument is optional
|
||||
default=f"{root}/office-js-api-declarations/office-js-api-plugins"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
generate(args.destination)
|
||||
618
scripts/sdkjs_common/jsdoc/generate_docs_plugins_md.py
Normal file
618
scripts/sdkjs_common/jsdoc/generate_docs_plugins_md.py
Normal file
@ -0,0 +1,618 @@
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import argparse
|
||||
import generate_docs_plugins_json
|
||||
|
||||
# Configuration files
|
||||
editors = {
|
||||
"word": "text-document-api",
|
||||
"cell": "spreadsheet-api",
|
||||
"slide": "presentation-api",
|
||||
"forms": "form-api"
|
||||
}
|
||||
|
||||
missing_examples = []
|
||||
used_enumerations = set()
|
||||
|
||||
cur_editor_name = None
|
||||
|
||||
def load_json(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
def write_markdown_file(file_path, content):
|
||||
with open(file_path, 'w', encoding='utf-8') as md_file:
|
||||
md_file.write(content)
|
||||
|
||||
def remove_js_comments(text):
|
||||
text = re.sub(r'^\s*//.*$', '', text, flags=re.MULTILINE) # single-line
|
||||
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) # multi-line
|
||||
return text.strip()
|
||||
|
||||
def process_link_tags(text, root=''):
|
||||
"""
|
||||
Finds patterns like {@link ...} and replaces them with Markdown links.
|
||||
If the prefix 'global#' is found, a link to a typedef is generated,
|
||||
otherwise, a link to a class method is created.
|
||||
For a method, if an alias is not specified, the name is left in the format 'Class#Method'.
|
||||
"""
|
||||
reserved_links = {
|
||||
'/docbuilder/global#ShapeType': f"{'../../../' if root == '' else '../../' if root == '../' else root}text-document-api/Enumeration/ShapeType.md",
|
||||
'/plugin/config': 'https://api.onlyoffice.com/docs/plugin-and-macros/structure/manifest/',
|
||||
'/docbuilder/basic': 'https://api.onlyoffice.com/docs/office-api/usage-api/text-document-api/'
|
||||
}
|
||||
|
||||
def replace_link(match):
|
||||
content = match.group(1).strip() # Example: "/docbuilder/global#ShapeType shape type" or "global#ErrorValue ErrorValue"
|
||||
parts = content.split()
|
||||
ref = parts[0]
|
||||
label = parts[1] if len(parts) > 1 else None
|
||||
|
||||
if ref.startswith('/'):
|
||||
# Handle reserved links using mapping
|
||||
if ref in reserved_links:
|
||||
url = reserved_links[ref]
|
||||
display_text = label if label else ref
|
||||
return f"[{display_text}]({url})"
|
||||
else:
|
||||
# If the link is not in the mapping, return the original construction
|
||||
return match.group(0)
|
||||
elif ref.startswith("global#"):
|
||||
# Handle links to typedef (similar logic as before)
|
||||
typedef_name = ref.split("#")[1]
|
||||
used_enumerations.add(typedef_name)
|
||||
display_text = label if label else typedef_name
|
||||
return f"[{display_text}]({root}Enumeration/{typedef_name}.md)"
|
||||
else:
|
||||
# Handle links to class methods like ClassName#MethodName
|
||||
try:
|
||||
class_name, method_name = ref.split("#")
|
||||
except ValueError:
|
||||
return match.group(0)
|
||||
display_text = label if label else ref # Keep the full notation, e.g., "Api#CreateSlide"
|
||||
return f"[{display_text}]({root}{class_name}/Methods/{method_name}.md)"
|
||||
|
||||
return re.sub(r'{@link\s+([^}]+)}', replace_link, text)
|
||||
|
||||
def correct_description(string, root=''):
|
||||
"""
|
||||
Cleans or transforms specific tags in the doclet description:
|
||||
- <b> => ** (bold text)
|
||||
- <note>...</note> => 💡 ...
|
||||
- {@link ...} is replaced with a Markdown link
|
||||
- If the description is missing, returns a default value.
|
||||
- All '\r' characters are replaced with '\n'.
|
||||
"""
|
||||
if string is None:
|
||||
return 'No description provided.'
|
||||
|
||||
# Line breaks
|
||||
string = string.replace('\r', '\\\n')
|
||||
|
||||
# Replace <b> tags with Markdown bold formatting
|
||||
string = re.sub(r'<b>', '-**', string)
|
||||
string = re.sub(r'</b>', '**', string)
|
||||
|
||||
# Replace <note> tags with an icon and text
|
||||
string = re.sub(r'<note>(.*?)</note>', r'💡 \1', string, flags=re.DOTALL)
|
||||
|
||||
# Process {@link ...} constructions
|
||||
string = process_link_tags(string, root)
|
||||
|
||||
return string
|
||||
|
||||
def correct_default_value(value, enumerations, classes):
|
||||
if value is None or value == '':
|
||||
return ''
|
||||
|
||||
if value == True:
|
||||
value = "true"
|
||||
elif value == False:
|
||||
value = "false"
|
||||
else:
|
||||
value = str(value)
|
||||
|
||||
return generate_data_types_markdown([value], enumerations, classes)
|
||||
|
||||
def remove_line_breaks(string):
|
||||
return re.sub(r'[\r\n]+', ' ', string)
|
||||
|
||||
# Convert Array.<T> => T[] (including nested arrays).
|
||||
def convert_jsdoc_array_to_ts(type_str: str) -> str:
|
||||
"""
|
||||
Recursively replaces 'Array.<T>' with 'T[]',
|
||||
handling nested arrays like 'Array.<Array.<string>>' => 'string[][]'.
|
||||
"""
|
||||
pattern = re.compile(r'Array\.<([^>]+)>')
|
||||
|
||||
while True:
|
||||
match = pattern.search(type_str)
|
||||
if not match:
|
||||
break
|
||||
|
||||
inner_type = match.group(1).strip()
|
||||
# Recursively convert inner parts
|
||||
inner_type = convert_jsdoc_array_to_ts(inner_type)
|
||||
|
||||
# Replace the outer Array.<...> with ...[]
|
||||
type_str = (
|
||||
type_str[:match.start()]
|
||||
+ f"{inner_type}[]"
|
||||
+ type_str[match.end():]
|
||||
)
|
||||
|
||||
return type_str
|
||||
|
||||
def escape_text_outside_code_blocks(markdown: str) -> str:
|
||||
"""
|
||||
Splits content by fenced code blocks, escapes MDX-unsafe characters
|
||||
(<, >, {, }) only in the text outside those code blocks.
|
||||
"""
|
||||
# A regex to capture fenced code blocks with ```
|
||||
parts = re.split(r'(```.*?```)', markdown, flags=re.DOTALL)
|
||||
|
||||
# Even indices (0, 2, 4, ...) are outside code blocks,
|
||||
# odd indices (1, 3, 5, ...) are actual code blocks.
|
||||
for i in range(0, len(parts), 2):
|
||||
# Only escape in parts outside code blocks
|
||||
parts[i] = (parts[i]
|
||||
.replace('<', '<')
|
||||
.replace('>', '>')
|
||||
.replace('{', '{')
|
||||
.replace('}', '}')
|
||||
)
|
||||
return "".join(parts)
|
||||
|
||||
def get_base_type(ts_type: str) -> str:
|
||||
"""
|
||||
Given a TypeScript-like type (e.g. "Drawing[][]"), return the
|
||||
'base' portion by stripping trailing "[]". For "Drawing[][]",
|
||||
returns "Drawing". For "Array.<Drawing>", you'd convert it first
|
||||
to "Drawing[]" then return "Drawing".
|
||||
"""
|
||||
while ts_type.endswith('[]'):
|
||||
ts_type = ts_type[:-2]
|
||||
return ts_type
|
||||
|
||||
def generate_data_types_markdown(types, enumerations, classes, root='../../'):
|
||||
"""
|
||||
1) Converts each type from JSDoc (e.g., Array.<T>) to T[].
|
||||
2) Processes union types by splitting them using '|'.
|
||||
3) Supports multidimensional arrays, e.g., (string|ApiRange|number)[].
|
||||
4) If the base type matches the name of an enumeration or class, generates a link.
|
||||
5) The final types are joined using " | ".
|
||||
"""
|
||||
# Convert each type from JSDoc format to TypeScript format (e.g., T[])
|
||||
converted = [convert_jsdoc_array_to_ts(t) for t in types]
|
||||
|
||||
# Set of primitive types
|
||||
primitive_types = {"string", "number", "boolean", "null", "undefined", "any", "object", "false", "true", "json", "function", "{}"}
|
||||
|
||||
def is_primitive(type):
|
||||
if (type.lower() in primitive_types or
|
||||
(type.startswith('"') and type.endswith('"')) or
|
||||
(type.startswith("'") and type.endswith("'")) or
|
||||
type.replace('.', '', 1).isdigit() or
|
||||
(type.startswith('-') and type[1:].replace('.', '', 1).isdigit())):
|
||||
return True
|
||||
return False
|
||||
|
||||
def link_if_known(ts_type):
|
||||
ts_type = ts_type.strip()
|
||||
# Count the number of array dimensions, e.g., "[][]" has 2 dimensions
|
||||
array_dims = 0
|
||||
while ts_type.endswith("[]"):
|
||||
array_dims += 1
|
||||
ts_type = ts_type[:-2].strip()
|
||||
|
||||
# Process generic types, e.g., Object.<string, editorType>
|
||||
if ".<" in ts_type and ts_type.endswith(">"):
|
||||
import re
|
||||
m = re.match(r'^(.*?)\.<(.*)>$', ts_type)
|
||||
if m:
|
||||
base_part = m.group(1).strip()
|
||||
generic_args_str = m.group(2).strip()
|
||||
# Process the base part of the type
|
||||
found = False
|
||||
for enum in enumerations:
|
||||
if enum['name'] == base_part:
|
||||
used_enumerations.add(base_part)
|
||||
base_result = f"[{base_part}]({root}Enumeration/{base_part}.md)"
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
if base_part in classes:
|
||||
base_result = f"[{base_part}]({root}{base_part}/{base_part}.md)"
|
||||
elif is_primitive(base_part):
|
||||
base_result = base_part
|
||||
elif cur_editor_name == "forms":
|
||||
base_result = f"[{base_part}]({root}../text-document-api/{base_part}/{base_part}.md)"
|
||||
else:
|
||||
print(f"Unknown type encountered: {base_part}")
|
||||
base_result = base_part
|
||||
# Split the generic parameters by commas and process each recursively
|
||||
generic_args = [link_if_known(x) for x in generic_args_str.split(",")]
|
||||
result = base_result + ".<" + ", ".join(generic_args) + ">"
|
||||
result += "[]" * array_dims
|
||||
return result
|
||||
|
||||
# Process union types: if the type is enclosed in parentheses
|
||||
if ts_type.startswith("(") and ts_type.endswith(")"):
|
||||
inner = ts_type[1:-1].strip()
|
||||
subtypes = [sub.strip() for sub in inner.split("|")]
|
||||
if len(subtypes) == 1:
|
||||
result = link_if_known(subtypes[0])
|
||||
else:
|
||||
processed = [link_if_known(subtype) for subtype in subtypes]
|
||||
result = "(" + " | ".join(processed) + ")"
|
||||
result += "[]" * array_dims
|
||||
return result
|
||||
|
||||
# If not a generic or union type – process the base type
|
||||
else:
|
||||
base = ts_type
|
||||
found = False
|
||||
for enum in enumerations:
|
||||
if enum['name'] == base:
|
||||
used_enumerations.add(base)
|
||||
result = f"[{base}]({root}Enumeration/{base}.md)"
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
if base in classes:
|
||||
result = f"[{base}]({root}{base}/{base}.md)"
|
||||
elif is_primitive(base):
|
||||
result = base
|
||||
elif cur_editor_name == "forms":
|
||||
result = f"[{base}]({root}../text-document-api/{base}/{base}.md)"
|
||||
else:
|
||||
print(f"Unknown type encountered: {base}")
|
||||
result = base
|
||||
result += "[]" * array_dims
|
||||
return result
|
||||
|
||||
# Apply link_if_known to each converted type
|
||||
linked = [link_if_known(ts_t) for ts_t in converted]
|
||||
|
||||
# Join results using " | "
|
||||
param_types_md = r' | '.join(linked)
|
||||
param_types_md = param_types_md.replace("|", r"\|")
|
||||
|
||||
# Escape remaining angle brackets for generics
|
||||
def replace_leftover_generics(match):
|
||||
element = match.group(1).strip()
|
||||
return f"<{element}>"
|
||||
|
||||
param_types_md = re.sub(r'<([^<>]+)>', replace_leftover_generics, param_types_md)
|
||||
|
||||
return param_types_md
|
||||
|
||||
def generate_class_markdown(class_name, methods, properties, enumerations, classes):
|
||||
content = f"# {class_name}\n\nRepresents the {class_name} class.\n\n"
|
||||
|
||||
content += generate_properties_markdown(properties, enumerations, classes)
|
||||
|
||||
content += "\n## Methods\n\n"
|
||||
content += "| Method | Returns | Description |\n"
|
||||
content += "| ------ | ------- | ----------- |\n"
|
||||
|
||||
for method in sorted(methods, key=lambda m: m['name']):
|
||||
method_name = method['name']
|
||||
|
||||
# Get the type of return values
|
||||
returns = method.get('returns', [])
|
||||
if returns:
|
||||
return_type_list = returns[0].get('type', {}).get('names', [])
|
||||
returns_markdown = generate_data_types_markdown(return_type_list, enumerations, classes, '../')
|
||||
else:
|
||||
returns_markdown = "None"
|
||||
|
||||
# Processing the method description
|
||||
description = remove_line_breaks(correct_description(method.get('description', 'No description provided.'), '../'))
|
||||
|
||||
# Form a link to the method document
|
||||
method_link = f"[{method_name}](./Methods/{method_name}.md)"
|
||||
|
||||
content += f"| {method_link} | {returns_markdown} | {description} |\n"
|
||||
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def generate_method_markdown(method, enumerations, classes):
|
||||
"""
|
||||
Generates Markdown for a method doclet, relying only on `method['examples']`
|
||||
(array of strings). Ignores any single `method['example']` field.
|
||||
"""
|
||||
|
||||
method_name = method['name']
|
||||
description = method.get('description', 'No description provided.')
|
||||
description = correct_description(description, '../../')
|
||||
params = method.get('params', [])
|
||||
returns = method.get('returns', [])
|
||||
memberof = method.get('memberof', '')
|
||||
|
||||
# Use the 'examples' array only
|
||||
examples = method.get('examples', [])
|
||||
|
||||
content = f"# {method_name}\n\n{description}\n\n"
|
||||
|
||||
# Syntax
|
||||
param_list = ', '.join([param['name'] for param in params if '.' not in param['name']]) if params else ''
|
||||
content += f"## Syntax\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n"
|
||||
if memberof:
|
||||
content += f"`expression` - A variable that represents a [{memberof}](../{memberof}.md) class.\n\n"
|
||||
|
||||
# Parameters
|
||||
content += "## Parameters\n\n"
|
||||
if params:
|
||||
content += "| **Name** | **Required/Optional** | **Data type** | **Default** | **Description** |\n"
|
||||
content += "| ------------- | ------------- | ------------- | ------------- | ------------- |\n"
|
||||
for param in params:
|
||||
param_name = param.get('name', 'Unnamed')
|
||||
param_types = param.get('type', {}).get('names', []) if param.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(param_types, enumerations, classes)
|
||||
param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.'), '../../'))
|
||||
param_required = "Required" if not param.get('optional') else "Optional"
|
||||
param_default = correct_default_value(param.get('defaultvalue', ''), enumerations, classes)
|
||||
|
||||
content += f"| {param_name} | {param_required} | {param_types_md} | {param_default} | {param_desc} |\n"
|
||||
else:
|
||||
content += "This method doesn't have any parameters.\n"
|
||||
|
||||
# Returns
|
||||
content += "\n## Returns\n\n"
|
||||
if returns:
|
||||
return_type_list = returns[0].get('type', {}).get('names', [])
|
||||
return_type_md = generate_data_types_markdown(return_type_list, enumerations, classes)
|
||||
content += return_type_md
|
||||
else:
|
||||
content += "This method doesn't return any data."
|
||||
|
||||
# Process examples array
|
||||
if examples:
|
||||
if len(examples) > 1:
|
||||
content += "\n\n## Examples\n\n"
|
||||
else:
|
||||
content += "\n\n## Example\n\n"
|
||||
|
||||
for i, ex_line in enumerate(examples, start=1):
|
||||
# Remove JS comments
|
||||
cleaned_example = remove_js_comments(ex_line).strip()
|
||||
|
||||
# Attempt splitting if the user used ```js
|
||||
if '```js' in cleaned_example:
|
||||
comment, code = cleaned_example.split('```js', 1)
|
||||
comment = comment.strip()
|
||||
code = code.strip()
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
|
||||
content += f"```javascript\n{code}\n```\n"
|
||||
else:
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
# No special fences, just show as code
|
||||
content += f"```javascript\n{cleaned_example}\n```\n"
|
||||
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def generate_properties_markdown(properties, enumerations, classes, root='../'):
|
||||
if properties is None:
|
||||
return ''
|
||||
|
||||
content = "## Properties\n\n"
|
||||
content += "| Name | Type | Description |\n"
|
||||
content += "| ---- | ---- | ----------- |\n"
|
||||
|
||||
for prop in sorted(properties, key=lambda m: m['name']):
|
||||
prop_name = prop['name']
|
||||
prop_description = prop.get('description', 'No description provided.')
|
||||
prop_description = remove_line_breaks(correct_description(prop_description))
|
||||
prop_types = prop['type']['names'] if prop.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(prop_types, enumerations, classes, root)
|
||||
content += f"| {prop_name} | {param_types_md} | {prop_description} |\n"
|
||||
|
||||
# Escape outside code blocks
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def generate_enumeration_markdown(enumeration, enumerations, classes):
|
||||
"""
|
||||
Generates Markdown documentation for a 'typedef' doclet.
|
||||
This version only works with `enumeration['examples']` (an array of strings),
|
||||
ignoring any single `enumeration['examples']` field.
|
||||
"""
|
||||
enum_name = enumeration['name']
|
||||
|
||||
if enum_name not in used_enumerations:
|
||||
return None
|
||||
|
||||
description = enumeration.get('description', 'No description provided.')
|
||||
description = correct_description(description, '../')
|
||||
|
||||
# Only use the 'examples' array
|
||||
examples = enumeration.get('examples', [])
|
||||
|
||||
content = f"# {enum_name}\n\n{description}\n\n"
|
||||
|
||||
parsed_type = enumeration['type'].get('parsedType')
|
||||
if not parsed_type:
|
||||
# If parsedType is missing, just list 'type.names' if available
|
||||
type_names = enumeration['type'].get('names', [])
|
||||
if type_names:
|
||||
content += "## Type\n\n"
|
||||
t_md = generate_data_types_markdown(type_names, enumerations, classes)
|
||||
content += t_md + "\n\n"
|
||||
else:
|
||||
ptype = parsed_type['type']
|
||||
|
||||
# 1) Handle TypeUnion
|
||||
if ptype == 'TypeUnion':
|
||||
content += "## Type\n\nEnumeration\n\n"
|
||||
content += "## Values\n\n"
|
||||
for raw_t in enumeration['type']['names']:
|
||||
# Attempt linking
|
||||
if any(enum['name'] == raw_t for enum in enumerations):
|
||||
used_enumerations.add(raw_t)
|
||||
content += f"- [{raw_t}](../Enumeration/{raw_t}.md)\n"
|
||||
elif raw_t in classes:
|
||||
content += f"- [{raw_t}](../{raw_t}/{raw_t}.md)\n"
|
||||
else:
|
||||
content += f"- {raw_t}\n"
|
||||
|
||||
# 2) Handle TypeApplication (e.g. Object.<string, string>)
|
||||
elif ptype == 'TypeApplication':
|
||||
content += "## Type\n\nObject\n\n"
|
||||
type_names = enumeration['type'].get('names', [])
|
||||
if type_names:
|
||||
t_md = generate_data_types_markdown(type_names, enumerations, classes)
|
||||
content += f"**Type:** {t_md}\n\n"
|
||||
|
||||
# 3) If properties are present, treat it like an object
|
||||
if enumeration.get('properties') is not None:
|
||||
content += generate_properties_markdown(enumeration['properties'], enumerations, classes)
|
||||
|
||||
# 4) If it's neither TypeUnion nor TypeApplication, just output the type names
|
||||
if ptype not in ('TypeUnion', 'TypeApplication'):
|
||||
type_names = enumeration['type'].get('names', [])
|
||||
if type_names:
|
||||
content += "## Type\n\n"
|
||||
t_md = generate_data_types_markdown(type_names, enumerations, classes)
|
||||
content += t_md + "\n\n"
|
||||
|
||||
# Process examples array
|
||||
if examples:
|
||||
if len(examples) > 1:
|
||||
content += "\n\n## Examples\n\n"
|
||||
else:
|
||||
content += "\n\n## Example\n\n"
|
||||
|
||||
for i, ex_line in enumerate(examples, start=1):
|
||||
# Remove JS comments
|
||||
cleaned_example = remove_js_comments(ex_line).strip()
|
||||
|
||||
# Attempt splitting if the user used ```js
|
||||
if '```js' in cleaned_example:
|
||||
comment, code = cleaned_example.split('```js', 1)
|
||||
comment = comment.strip()
|
||||
code = code.strip()
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
|
||||
content += f"```javascript\n{code}\n```\n"
|
||||
else:
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
# No special fences, just show as code
|
||||
content += f"```javascript\n{cleaned_example}\n```\n"
|
||||
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def process_doclets(data, output_dir, editor_name):
|
||||
global cur_editor_name
|
||||
cur_editor_name = editor_name
|
||||
|
||||
classes = {}
|
||||
classes_props = {}
|
||||
enumerations = []
|
||||
editor_dir = os.path.join(output_dir, editors[editor_name])
|
||||
|
||||
for doclet in data:
|
||||
if doclet['kind'] == 'class':
|
||||
class_name = doclet['name']
|
||||
if class_name:
|
||||
if class_name not in classes:
|
||||
classes[class_name] = []
|
||||
classes_props[class_name] = doclet.get('properties', None)
|
||||
elif doclet['kind'] == 'function':
|
||||
class_name = doclet.get('memberof')
|
||||
if class_name:
|
||||
if class_name not in classes:
|
||||
classes[class_name] = []
|
||||
classes[class_name].append(doclet)
|
||||
elif doclet['kind'] == 'typedef':
|
||||
enumerations.append(doclet)
|
||||
|
||||
# Process classes
|
||||
for class_name, methods in classes.items():
|
||||
if (len(methods) == 0):
|
||||
continue
|
||||
|
||||
class_dir = os.path.join(editor_dir, class_name)
|
||||
methods_dir = os.path.join(class_dir, 'Methods')
|
||||
os.makedirs(methods_dir, exist_ok=True)
|
||||
|
||||
# Write class file
|
||||
class_content = generate_class_markdown(
|
||||
class_name,
|
||||
methods,
|
||||
classes_props[class_name],
|
||||
enumerations,
|
||||
classes
|
||||
)
|
||||
write_markdown_file(os.path.join(class_dir, f"{class_name}.md"), class_content)
|
||||
|
||||
# Write method files
|
||||
for method in methods:
|
||||
method_file_path = os.path.join(methods_dir, f"{method['name']}.md")
|
||||
method_content = generate_method_markdown(method, enumerations, classes)
|
||||
write_markdown_file(method_file_path, method_content)
|
||||
|
||||
if not method.get('examples', ''):
|
||||
missing_examples.append(os.path.relpath(method_file_path, output_dir))
|
||||
|
||||
# Process enumerations
|
||||
enum_dir = os.path.join(editor_dir, 'Enumeration')
|
||||
os.makedirs(enum_dir, exist_ok=True)
|
||||
|
||||
# idle run
|
||||
prev_used_count = -1
|
||||
while len(used_enumerations) != prev_used_count:
|
||||
prev_used_count = len(used_enumerations)
|
||||
for enum in [e for e in enumerations if e['name'] in used_enumerations]:
|
||||
enum_content = generate_enumeration_markdown(enum, enumerations, classes)
|
||||
|
||||
for enum in enumerations:
|
||||
enum_file_path = os.path.join(enum_dir, f"{enum['name']}.md")
|
||||
enum_content = generate_enumeration_markdown(enum, enumerations, classes)
|
||||
if enum_content is None:
|
||||
continue
|
||||
|
||||
write_markdown_file(enum_file_path, enum_content)
|
||||
if not enum.get('examples', ''):
|
||||
missing_examples.append(os.path.relpath(enum_file_path, output_dir))
|
||||
|
||||
def generate(output_dir):
|
||||
print('Generating Markdown documentation...')
|
||||
|
||||
if output_dir[-1] == '/':
|
||||
output_dir = output_dir[:-1]
|
||||
|
||||
generate_docs_plugins_json.generate(output_dir + '/tmp_json', md=True)
|
||||
for editor_name, folder_name in editors.items():
|
||||
input_file = os.path.join(output_dir + '/tmp_json', editor_name + ".json")
|
||||
|
||||
shutil.rmtree(output_dir + f'/{folder_name}', ignore_errors=True)
|
||||
os.makedirs(output_dir + f'/{folder_name}')
|
||||
|
||||
data = load_json(input_file)
|
||||
used_enumerations.clear()
|
||||
process_doclets(data, output_dir, editor_name)
|
||||
|
||||
shutil.rmtree(output_dir + '/tmp_json')
|
||||
print('Done')
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Generate documentation")
|
||||
parser.add_argument(
|
||||
"destination",
|
||||
type=str,
|
||||
help="Destination directory for the generated documentation",
|
||||
nargs='?', # Indicates the argument is optional
|
||||
default="../../../../office-js-api/plugins/" # Default value
|
||||
)
|
||||
args = parser.parse_args()
|
||||
generate(args.destination)
|
||||
print("START_MISSING_EXAMPLES")
|
||||
print(",".join(missing_examples))
|
||||
print("END_MISSING_EXAMPLES")
|
||||
621
scripts/sdkjs_common/jsdoc/generate_docs_plugins_md_site.py
Normal file
621
scripts/sdkjs_common/jsdoc/generate_docs_plugins_md_site.py
Normal file
@ -0,0 +1,621 @@
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import argparse
|
||||
import generate_docs_plugins_json
|
||||
|
||||
# Configuration files
|
||||
editors = {
|
||||
"word": "text-document-api",
|
||||
"cell": "spreadsheet-api",
|
||||
"slide": "presentation-api",
|
||||
"forms": "form-api"
|
||||
}
|
||||
|
||||
missing_examples = []
|
||||
used_enumerations = set()
|
||||
|
||||
cur_editor_name = None
|
||||
|
||||
def load_json(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
def write_markdown_file(file_path, content):
|
||||
with open(file_path, 'w', encoding='utf-8') as md_file:
|
||||
md_file.write(content)
|
||||
|
||||
def remove_js_comments(text):
|
||||
text = re.sub(r'^\s*//.*$', '', text, flags=re.MULTILINE) # single-line
|
||||
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) # multi-line
|
||||
return text.strip()
|
||||
|
||||
def process_link_tags(text, root=''):
|
||||
"""
|
||||
Finds patterns like {@link ...} and replaces them with Markdown links.
|
||||
If the prefix 'global#' is found, a link to a typedef is generated,
|
||||
otherwise, a link to a class method is created.
|
||||
For a method, if an alias is not specified, the name is left in the format 'Class#Method'.
|
||||
"""
|
||||
reserved_links = {
|
||||
'/docbuilder/global#ShapeType': f"{'../../../../../../' if root == '' else '../../../../../' if root == '../' else root}docs/office-api/usage-api/text-document-api/Enumeration/ShapeType.md",
|
||||
'/plugin/config': 'https://api.onlyoffice.com/docs/plugin-and-macros/structure/manifest/',
|
||||
'/docbuilder/basic': 'https://api.onlyoffice.com/docs/office-api/usage-api/text-document-api/'
|
||||
}
|
||||
|
||||
def replace_link(match):
|
||||
content = match.group(1).strip() # Example: "/docbuilder/global#ShapeType shape type" or "global#ErrorValue ErrorValue"
|
||||
parts = content.split()
|
||||
ref = parts[0]
|
||||
label = parts[1] if len(parts) > 1 else None
|
||||
|
||||
if ref.startswith('/'):
|
||||
# Handle reserved links using mapping
|
||||
if ref in reserved_links:
|
||||
url = reserved_links[ref]
|
||||
display_text = label if label else ref
|
||||
return f"[{display_text}]({url})"
|
||||
else:
|
||||
# If the link is not in the mapping, return the original construction
|
||||
return match.group(0)
|
||||
elif ref.startswith("global#"):
|
||||
# Handle links to typedef (similar logic as before)
|
||||
typedef_name = ref.split("#")[1]
|
||||
used_enumerations.add(typedef_name)
|
||||
display_text = label if label else typedef_name
|
||||
return f"[{display_text}]({root}Enumeration/{typedef_name}.md)"
|
||||
else:
|
||||
# Handle links to class methods like ClassName#MethodName
|
||||
try:
|
||||
class_name, method_name = ref.split("#")
|
||||
except ValueError:
|
||||
return match.group(0)
|
||||
display_text = label if label else ref # Keep the full notation, e.g., "Api#CreateSlide"
|
||||
return f"[{display_text}]({root}{class_name}/Methods/{method_name}.md)"
|
||||
|
||||
return re.sub(r'{@link\s+([^}]+)}', replace_link, text)
|
||||
|
||||
def correct_description(string, root=''):
|
||||
"""
|
||||
Cleans or transforms specific tags in the doclet description:
|
||||
- <b> => ** (bold text)
|
||||
- <note>...</note> => 💡 ...
|
||||
- {@link ...} is replaced with a Markdown link
|
||||
- If the description is missing, returns a default value.
|
||||
- All '\r' characters are replaced with '\n'.
|
||||
"""
|
||||
if string is None:
|
||||
return 'No description provided.'
|
||||
|
||||
# Line breaks
|
||||
string = string.replace('\r', '\\\n')
|
||||
|
||||
# Replace <b> tags with Markdown bold formatting
|
||||
string = re.sub(r'<b>', '-**', string)
|
||||
string = re.sub(r'</b>', '**', string)
|
||||
|
||||
# Replace <note> tags with an icon and text
|
||||
string = re.sub(r'<note>(.*?)</note>', r'💡 \1', string, flags=re.DOTALL)
|
||||
|
||||
# Process {@link ...} constructions
|
||||
string = process_link_tags(string, root)
|
||||
|
||||
return string
|
||||
|
||||
def correct_default_value(value, enumerations, classes):
|
||||
if value is None or value == '':
|
||||
return ''
|
||||
|
||||
if value == True:
|
||||
value = "true"
|
||||
elif value == False:
|
||||
value = "false"
|
||||
else:
|
||||
value = str(value)
|
||||
|
||||
return generate_data_types_markdown([value], enumerations, classes)
|
||||
|
||||
def remove_line_breaks(string):
|
||||
return re.sub(r'[\r\n]+', ' ', string)
|
||||
|
||||
# Convert Array.<T> => T[] (including nested arrays).
|
||||
def convert_jsdoc_array_to_ts(type_str: str) -> str:
|
||||
"""
|
||||
Recursively replaces 'Array.<T>' with 'T[]',
|
||||
handling nested arrays like 'Array.<Array.<string>>' => 'string[][]'.
|
||||
"""
|
||||
pattern = re.compile(r'Array\.<([^>]+)>')
|
||||
|
||||
while True:
|
||||
match = pattern.search(type_str)
|
||||
if not match:
|
||||
break
|
||||
|
||||
inner_type = match.group(1).strip()
|
||||
# Recursively convert inner parts
|
||||
inner_type = convert_jsdoc_array_to_ts(inner_type)
|
||||
|
||||
# Replace the outer Array.<...> with ...[]
|
||||
type_str = (
|
||||
type_str[:match.start()]
|
||||
+ f"{inner_type}[]"
|
||||
+ type_str[match.end():]
|
||||
)
|
||||
|
||||
return type_str
|
||||
|
||||
def escape_text_outside_code_blocks(markdown: str) -> str:
|
||||
"""
|
||||
Splits content by fenced code blocks, escapes MDX-unsafe characters
|
||||
(<, >, {, }) only in the text outside those code blocks.
|
||||
"""
|
||||
# A regex to capture fenced code blocks with ```
|
||||
parts = re.split(r'(```.*?```)', markdown, flags=re.DOTALL)
|
||||
|
||||
# Even indices (0, 2, 4, ...) are outside code blocks,
|
||||
# odd indices (1, 3, 5, ...) are actual code blocks.
|
||||
for i in range(0, len(parts), 2):
|
||||
# Only escape in parts outside code blocks
|
||||
parts[i] = (parts[i]
|
||||
.replace('<', '<')
|
||||
.replace('>', '>')
|
||||
.replace('{', '{')
|
||||
.replace('}', '}')
|
||||
)
|
||||
return "".join(parts)
|
||||
|
||||
def get_base_type(ts_type: str) -> str:
|
||||
"""
|
||||
Given a TypeScript-like type (e.g. "Drawing[][]"), return the
|
||||
'base' portion by stripping trailing "[]". For "Drawing[][]",
|
||||
returns "Drawing". For "Array.<Drawing>", you'd convert it first
|
||||
to "Drawing[]" then return "Drawing".
|
||||
"""
|
||||
while ts_type.endswith('[]'):
|
||||
ts_type = ts_type[:-2]
|
||||
return ts_type
|
||||
|
||||
def generate_data_types_markdown(types, enumerations, classes, root='../../'):
|
||||
"""
|
||||
1) Converts each type from JSDoc (e.g., Array.<T>) to T[].
|
||||
2) Processes union types by splitting them using '|'.
|
||||
3) Supports multidimensional arrays, e.g., (string|ApiRange|number)[].
|
||||
4) If the base type matches the name of an enumeration or class, generates a link.
|
||||
5) The final types are joined using " | ".
|
||||
"""
|
||||
# Convert each type from JSDoc format to TypeScript format (e.g., T[])
|
||||
converted = [convert_jsdoc_array_to_ts(t) for t in types]
|
||||
|
||||
# Set of primitive types
|
||||
primitive_types = {"string", "number", "boolean", "null", "undefined", "any", "object", "false", "true", "json", "function", "{}"}
|
||||
|
||||
def is_primitive(type):
|
||||
if (type.lower() in primitive_types or
|
||||
(type.startswith('"') and type.endswith('"')) or
|
||||
(type.startswith("'") and type.endswith("'")) or
|
||||
type.replace('.', '', 1).isdigit() or
|
||||
(type.startswith('-') and type[1:].replace('.', '', 1).isdigit())):
|
||||
return True
|
||||
return False
|
||||
|
||||
def link_if_known(ts_type):
|
||||
ts_type = ts_type.strip()
|
||||
# Count the number of array dimensions, e.g., "[][]" has 2 dimensions
|
||||
array_dims = 0
|
||||
while ts_type.endswith("[]"):
|
||||
array_dims += 1
|
||||
ts_type = ts_type[:-2].strip()
|
||||
|
||||
# Process generic types, e.g., Object.<string, editorType>
|
||||
if ".<" in ts_type and ts_type.endswith(">"):
|
||||
import re
|
||||
m = re.match(r'^(.*?)\.<(.*)>$', ts_type)
|
||||
if m:
|
||||
base_part = m.group(1).strip()
|
||||
generic_args_str = m.group(2).strip()
|
||||
# Process the base part of the type
|
||||
found = False
|
||||
for enum in enumerations:
|
||||
if enum['name'] == base_part:
|
||||
used_enumerations.add(base_part)
|
||||
base_result = f"[{base_part}]({root}Enumeration/{base_part}.md)"
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
if base_part in classes:
|
||||
base_result = f"[{base_part}]({root}{base_part}/{base_part}.md)"
|
||||
elif is_primitive(base_part):
|
||||
base_result = base_part
|
||||
elif cur_editor_name == "forms":
|
||||
base_result = f"[{base_part}]({root}../text-document-api/{base_part}/{base_part}.md)"
|
||||
else:
|
||||
print(f"Unknown type encountered: {base_part}")
|
||||
base_result = base_part
|
||||
# Split the generic parameters by commas and process each recursively
|
||||
generic_args = [link_if_known(x) for x in generic_args_str.split(",")]
|
||||
result = base_result + ".<" + ", ".join(generic_args) + ">"
|
||||
result += "[]" * array_dims
|
||||
return result
|
||||
|
||||
# Process union types: if the type is enclosed in parentheses
|
||||
if ts_type.startswith("(") and ts_type.endswith(")"):
|
||||
inner = ts_type[1:-1].strip()
|
||||
subtypes = [sub.strip() for sub in inner.split("|")]
|
||||
if len(subtypes) == 1:
|
||||
result = link_if_known(subtypes[0])
|
||||
else:
|
||||
processed = [link_if_known(subtype) for subtype in subtypes]
|
||||
result = "(" + " | ".join(processed) + ")"
|
||||
result += "[]" * array_dims
|
||||
return result
|
||||
|
||||
# If not a generic or union type – process the base type
|
||||
else:
|
||||
base = ts_type
|
||||
found = False
|
||||
for enum in enumerations:
|
||||
if enum['name'] == base:
|
||||
used_enumerations.add(base)
|
||||
result = f"[{base}]({root}Enumeration/{base}.md)"
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
if base in classes:
|
||||
result = f"[{base}]({root}{base}/{base}.md)"
|
||||
elif is_primitive(base):
|
||||
result = base
|
||||
elif cur_editor_name == "forms":
|
||||
result = f"[{base}]({root}../text-document-api/{base}/{base}.md)"
|
||||
else:
|
||||
print(f"Unknown type encountered: {base}")
|
||||
result = base
|
||||
result += "[]" * array_dims
|
||||
return result
|
||||
|
||||
# Apply link_if_known to each converted type
|
||||
linked = [link_if_known(ts_t) for ts_t in converted]
|
||||
|
||||
# Join results using " | "
|
||||
param_types_md = r' | '.join(linked)
|
||||
param_types_md = param_types_md.replace("|", r"\|")
|
||||
|
||||
# Escape remaining angle brackets for generics
|
||||
def replace_leftover_generics(match):
|
||||
element = match.group(1).strip()
|
||||
return f"<{element}>"
|
||||
|
||||
param_types_md = re.sub(r'<([^<>]+)>', replace_leftover_generics, param_types_md)
|
||||
|
||||
return param_types_md
|
||||
|
||||
def generate_class_markdown(class_name, methods, properties, enumerations, classes):
|
||||
content = f"# {class_name}\n\nRepresents the {class_name} class.\n\n"
|
||||
|
||||
content += generate_properties_markdown(properties, enumerations, classes)
|
||||
|
||||
content += "\n## Methods\n\n"
|
||||
content += "| Method | Returns | Description |\n"
|
||||
content += "| ------ | ------- | ----------- |\n"
|
||||
|
||||
for method in sorted(methods, key=lambda m: m['name']):
|
||||
method_name = method['name']
|
||||
|
||||
# Get the type of return values
|
||||
returns = method.get('returns', [])
|
||||
if returns:
|
||||
return_type_list = returns[0].get('type', {}).get('names', [])
|
||||
returns_markdown = generate_data_types_markdown(return_type_list, enumerations, classes, '../')
|
||||
else:
|
||||
returns_markdown = "None"
|
||||
|
||||
# Processing the method description
|
||||
description = remove_line_breaks(correct_description(method.get('description', 'No description provided.'), '../'))
|
||||
|
||||
# Form a link to the method document
|
||||
method_link = f"[{method_name}](./Methods/{method_name}.md)"
|
||||
|
||||
content += f"| {method_link} | {returns_markdown} | {description} |\n"
|
||||
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def generate_method_markdown(method, enumerations, classes):
|
||||
"""
|
||||
Generates Markdown for a method doclet, relying only on `method['examples']`
|
||||
(array of strings). Ignores any single `method['example']` field.
|
||||
"""
|
||||
|
||||
method_name = method['name']
|
||||
description = method.get('description', 'No description provided.')
|
||||
description = correct_description(description, '../../')
|
||||
params = method.get('params', [])
|
||||
returns = method.get('returns', [])
|
||||
memberof = method.get('memberof', '')
|
||||
|
||||
# Use the 'examples' array only
|
||||
examples = method.get('examples', [])
|
||||
|
||||
content = f"# {method_name}\n\n{description}\n\n"
|
||||
|
||||
# Syntax
|
||||
param_list = ', '.join([param['name'] for param in params if '.' not in param['name']]) if params else ''
|
||||
content += f"## Syntax\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n"
|
||||
if memberof:
|
||||
content += f"`expression` - A variable that represents a [{memberof}](../{memberof}.md) class.\n\n"
|
||||
|
||||
# Parameters
|
||||
content += "## Parameters\n\n"
|
||||
if params:
|
||||
content += "| **Name** | **Required/Optional** | **Data type** | **Default** | **Description** |\n"
|
||||
content += "| ------------- | ------------- | ------------- | ------------- | ------------- |\n"
|
||||
for param in params:
|
||||
param_name = param.get('name', 'Unnamed')
|
||||
param_types = param.get('type', {}).get('names', []) if param.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(param_types, enumerations, classes)
|
||||
param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.'), '../../'))
|
||||
param_required = "Required" if not param.get('optional') else "Optional"
|
||||
param_default = correct_default_value(param.get('defaultvalue', ''), enumerations, classes)
|
||||
|
||||
content += f"| {param_name} | {param_required} | {param_types_md} | {param_default} | {param_desc} |\n"
|
||||
else:
|
||||
content += "This method doesn't have any parameters.\n"
|
||||
|
||||
# Returns
|
||||
content += "\n## Returns\n\n"
|
||||
if returns:
|
||||
return_type_list = returns[0].get('type', {}).get('names', [])
|
||||
return_type_md = generate_data_types_markdown(return_type_list, enumerations, classes)
|
||||
content += return_type_md
|
||||
else:
|
||||
content += "This method doesn't return any data."
|
||||
|
||||
# Process examples array
|
||||
if examples:
|
||||
if len(examples) > 1:
|
||||
content += "\n\n## Examples\n\n"
|
||||
else:
|
||||
content += "\n\n## Example\n\n"
|
||||
|
||||
for i, ex_line in enumerate(examples, start=1):
|
||||
# Remove JS comments
|
||||
cleaned_example = remove_js_comments(ex_line).strip()
|
||||
|
||||
# Attempt splitting if the user used ```js
|
||||
if '```js' in cleaned_example:
|
||||
comment, code = cleaned_example.split('```js', 1)
|
||||
comment = comment.strip()
|
||||
code = code.strip()
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
|
||||
content += f"```javascript\n{code}\n```\n"
|
||||
else:
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
# No special fences, just show as code
|
||||
content += f"```javascript\n{cleaned_example}\n```\n"
|
||||
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def generate_properties_markdown(properties, enumerations, classes, root='../'):
|
||||
if properties is None:
|
||||
return ''
|
||||
|
||||
content = "## Properties\n\n"
|
||||
content += "| Name | Type | Description |\n"
|
||||
content += "| ---- | ---- | ----------- |\n"
|
||||
|
||||
for prop in sorted(properties, key=lambda m: m['name']):
|
||||
prop_name = prop['name']
|
||||
prop_description = prop.get('description', 'No description provided.')
|
||||
prop_description = remove_line_breaks(correct_description(prop_description))
|
||||
prop_types = prop['type']['names'] if prop.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(prop_types, enumerations, classes, root)
|
||||
content += f"| {prop_name} | {param_types_md} | {prop_description} |\n"
|
||||
|
||||
# Escape outside code blocks
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def generate_enumeration_markdown(enumeration, enumerations, classes):
|
||||
"""
|
||||
Generates Markdown documentation for a 'typedef' doclet.
|
||||
This version only works with `enumeration['examples']` (an array of strings),
|
||||
ignoring any single `enumeration['examples']` field.
|
||||
"""
|
||||
enum_name = enumeration['name']
|
||||
|
||||
if enum_name not in used_enumerations:
|
||||
return None
|
||||
|
||||
description = enumeration.get('description', 'No description provided.')
|
||||
description = correct_description(description, '../')
|
||||
|
||||
# Only use the 'examples' array
|
||||
examples = enumeration.get('examples', [])
|
||||
|
||||
content = f"# {enum_name}\n\n{description}\n\n"
|
||||
|
||||
parsed_type = enumeration['type'].get('parsedType')
|
||||
if not parsed_type:
|
||||
# If parsedType is missing, just list 'type.names' if available
|
||||
type_names = enumeration['type'].get('names', [])
|
||||
if type_names:
|
||||
content += "## Type\n\n"
|
||||
t_md = generate_data_types_markdown(type_names, enumerations, classes)
|
||||
content += t_md + "\n\n"
|
||||
else:
|
||||
ptype = parsed_type['type']
|
||||
|
||||
# 1) Handle TypeUnion
|
||||
if ptype == 'TypeUnion':
|
||||
content += "## Type\n\nEnumeration\n\n"
|
||||
content += "## Values\n\n"
|
||||
for raw_t in enumeration['type']['names']:
|
||||
# Attempt linking
|
||||
if any(enum['name'] == raw_t for enum in enumerations):
|
||||
used_enumerations.add(raw_t)
|
||||
content += f"- [{raw_t}](../Enumeration/{raw_t}.md)\n"
|
||||
elif raw_t in classes:
|
||||
content += f"- [{raw_t}](../{raw_t}/{raw_t}.md)\n"
|
||||
else:
|
||||
content += f"- {raw_t}\n"
|
||||
|
||||
# 2) Handle TypeApplication (e.g. Object.<string, string>)
|
||||
elif ptype == 'TypeApplication':
|
||||
content += "## Type\n\nObject\n\n"
|
||||
type_names = enumeration['type'].get('names', [])
|
||||
if type_names:
|
||||
t_md = generate_data_types_markdown(type_names, enumerations, classes)
|
||||
content += f"**Type:** {t_md}\n\n"
|
||||
|
||||
# 3) If properties are present, treat it like an object
|
||||
if enumeration.get('properties') is not None:
|
||||
content += generate_properties_markdown(enumeration['properties'], enumerations, classes)
|
||||
|
||||
# 4) If it's neither TypeUnion nor TypeApplication, just output the type names
|
||||
if ptype not in ('TypeUnion', 'TypeApplication'):
|
||||
type_names = enumeration['type'].get('names', [])
|
||||
if type_names:
|
||||
content += "## Type\n\n"
|
||||
t_md = generate_data_types_markdown(type_names, enumerations, classes)
|
||||
content += t_md + "\n\n"
|
||||
|
||||
# Process examples array
|
||||
if examples:
|
||||
if len(examples) > 1:
|
||||
content += "\n\n## Examples\n\n"
|
||||
else:
|
||||
content += "\n\n## Example\n\n"
|
||||
|
||||
for i, ex_line in enumerate(examples, start=1):
|
||||
# Remove JS comments
|
||||
cleaned_example = remove_js_comments(ex_line).strip()
|
||||
|
||||
# Attempt splitting if the user used ```js
|
||||
if '```js' in cleaned_example:
|
||||
comment, code = cleaned_example.split('```js', 1)
|
||||
comment = comment.strip()
|
||||
code = code.strip()
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
|
||||
content += f"```javascript\n{code}\n```\n"
|
||||
else:
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
# No special fences, just show as code
|
||||
content += f"```javascript\n{cleaned_example}\n```\n"
|
||||
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
def process_doclets(data, output_dir, editor_name):
|
||||
global cur_editor_name
|
||||
cur_editor_name = editor_name
|
||||
|
||||
classes = {}
|
||||
classes_props = {}
|
||||
enumerations = []
|
||||
editor_dir = os.path.join(output_dir, editors[editor_name])
|
||||
|
||||
for doclet in data:
|
||||
if doclet['kind'] == 'class':
|
||||
class_name = doclet['name']
|
||||
if class_name:
|
||||
if class_name not in classes:
|
||||
classes[class_name] = []
|
||||
classes_props[class_name] = doclet.get('properties', None)
|
||||
elif doclet['kind'] == 'function':
|
||||
class_name = doclet.get('memberof')
|
||||
if class_name:
|
||||
if class_name not in classes:
|
||||
classes[class_name] = []
|
||||
classes[class_name].append(doclet)
|
||||
elif doclet['kind'] == 'typedef':
|
||||
enumerations.append(doclet)
|
||||
|
||||
# Process classes
|
||||
for class_name, methods in classes.items():
|
||||
if (len(methods) == 0):
|
||||
continue
|
||||
|
||||
class_dir = os.path.join(editor_dir, class_name)
|
||||
methods_dir = os.path.join(class_dir, 'Methods')
|
||||
os.makedirs(methods_dir, exist_ok=True)
|
||||
|
||||
# Write class file
|
||||
class_content = generate_class_markdown(
|
||||
class_name,
|
||||
methods,
|
||||
classes_props[class_name],
|
||||
enumerations,
|
||||
classes
|
||||
)
|
||||
write_markdown_file(os.path.join(class_dir, f"{class_name}.md"), class_content)
|
||||
|
||||
# Write method files
|
||||
for method in methods:
|
||||
method_file_path = os.path.join(methods_dir, f"{method['name']}.md")
|
||||
method_content = generate_method_markdown(method, enumerations, classes)
|
||||
write_markdown_file(method_file_path, method_content)
|
||||
|
||||
if not method.get('examples', ''):
|
||||
missing_examples.append(os.path.relpath(method_file_path, output_dir))
|
||||
|
||||
# Process enumerations
|
||||
enum_dir = os.path.join(editor_dir, 'Enumeration')
|
||||
os.makedirs(enum_dir, exist_ok=True)
|
||||
|
||||
# idle run
|
||||
prev_used_count = -1
|
||||
while len(used_enumerations) != prev_used_count:
|
||||
prev_used_count = len(used_enumerations)
|
||||
for enum in [e for e in enumerations if e['name'] in used_enumerations]:
|
||||
enum_content = generate_enumeration_markdown(enum, enumerations, classes)
|
||||
|
||||
for enum in enumerations:
|
||||
enum_file_path = os.path.join(enum_dir, f"{enum['name']}.md")
|
||||
enum_content = generate_enumeration_markdown(enum, enumerations, classes)
|
||||
if enum_content is None:
|
||||
continue
|
||||
|
||||
write_markdown_file(enum_file_path, enum_content)
|
||||
if not enum.get('examples', ''):
|
||||
missing_examples.append(os.path.relpath(enum_file_path, output_dir))
|
||||
|
||||
def generate(output_dir):
|
||||
print('Generating Markdown documentation...')
|
||||
|
||||
if output_dir[-1] == '/':
|
||||
output_dir = output_dir[:-1]
|
||||
|
||||
generate_docs_plugins_json.generate(output_dir + '/tmp_json', md=True)
|
||||
for editor_name, folder_name in editors.items():
|
||||
input_file = os.path.join(output_dir + '/tmp_json', editor_name + ".json")
|
||||
|
||||
editor_folder_path = os.path.join(output_dir, folder_name)
|
||||
for folder_name in os.listdir(editor_folder_path):
|
||||
folder_path_to_del = os.path.join(editor_folder_path, folder_name)
|
||||
if os.path.isdir(folder_path_to_del):
|
||||
shutil.rmtree(folder_path_to_del, ignore_errors=True)
|
||||
|
||||
data = load_json(input_file)
|
||||
used_enumerations.clear()
|
||||
process_doclets(data, output_dir, editor_name)
|
||||
|
||||
shutil.rmtree(output_dir + '/tmp_json')
|
||||
print('Done')
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Generate documentation")
|
||||
parser.add_argument(
|
||||
"destination",
|
||||
type=str,
|
||||
help="Destination directory for the generated documentation",
|
||||
nargs='?', # Indicates the argument is optional
|
||||
default="../../../../api.onlyoffice.com/site/docs/plugin-and-macros/interacting-with-editors/methods/" # Default value
|
||||
)
|
||||
args = parser.parse_args()
|
||||
generate(args.destination)
|
||||
print("START_MISSING_EXAMPLES")
|
||||
print(",".join(missing_examples))
|
||||
print("END_MISSING_EXAMPLES")
|
||||
248
scripts/sdkjs_common/jsdoc/generate_jsonl_dataset.py
Normal file
248
scripts/sdkjs_common/jsdoc/generate_jsonl_dataset.py
Normal file
@ -0,0 +1,248 @@
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import argparse
|
||||
import generate_docs_json
|
||||
from datetime import datetime
|
||||
|
||||
# Configuration files
|
||||
editors = [
|
||||
"word",
|
||||
"cell",
|
||||
"slide",
|
||||
"forms"
|
||||
]
|
||||
|
||||
root = '../../../..'
|
||||
missing_examples = []
|
||||
|
||||
def load_json(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
def read_file_content(file_path):
|
||||
try:
|
||||
with open(file_path, encoding='utf-8') as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
missing_examples.append(file_path)
|
||||
# print(f"Failed to open file {file_path}: {e}")
|
||||
return ""
|
||||
|
||||
def extract_js_comments_as_text(text):
|
||||
# Extract single-line comments (after //)
|
||||
single_line_comments = re.findall(r'^\s*//(.*)$', text, flags=re.MULTILINE)
|
||||
# Extract multi-line comments (between /* and */)
|
||||
multi_line_comments = re.findall(r'/\*(.*?)\*/', text, flags=re.DOTALL)
|
||||
# Combine all found comments into a single list
|
||||
all_comments = single_line_comments + multi_line_comments
|
||||
# Join comments into a single text, separated by a space
|
||||
return " ".join(comment.strip() for comment in all_comments if comment.strip())
|
||||
|
||||
def extract_examples_blocks(content: str):
|
||||
blocks = []
|
||||
current_block = {"comments": [], "code": []}
|
||||
in_comment_section = True # Collect comments until code appears
|
||||
current_comment_group = [] # Accumulate lines of the current comment
|
||||
|
||||
for line in content.splitlines():
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
# Empty line
|
||||
if in_comment_section and current_comment_group:
|
||||
# Finish the current comment group
|
||||
comment_text = " ".join(current_comment_group)
|
||||
current_block["comments"].append(comment_text)
|
||||
current_comment_group = []
|
||||
elif not in_comment_section:
|
||||
# Empty line in the code – keep it as is
|
||||
current_block["code"].append(line)
|
||||
continue
|
||||
|
||||
if stripped.startswith("//"):
|
||||
if in_comment_section:
|
||||
# Remove comment marker and extra spaces
|
||||
current_comment_group.append(extract_js_comments_as_text(stripped))
|
||||
else:
|
||||
# Comment after code starts – finish the current block and start a new one
|
||||
blocks.append({
|
||||
"comments": current_block["comments"],
|
||||
"code": "\n".join(current_block["code"]).rstrip()
|
||||
})
|
||||
current_block = {"comments": [], "code": []}
|
||||
in_comment_section = True
|
||||
# Start a new comment group with the current line
|
||||
current_comment_group = [stripped[2:].strip()]
|
||||
else:
|
||||
# Code line
|
||||
if in_comment_section:
|
||||
if current_comment_group:
|
||||
comment_text = " ".join(current_comment_group)
|
||||
current_block["comments"].append(comment_text)
|
||||
current_comment_group = []
|
||||
in_comment_section = False
|
||||
current_block["code"].append(line)
|
||||
|
||||
# Finalize any remaining comment group
|
||||
if in_comment_section and current_comment_group:
|
||||
comment_text = " ".join(current_comment_group)
|
||||
current_block["comments"].append(comment_text)
|
||||
# Save the last block if it's not empty
|
||||
if current_block["comments"] or current_block["code"]:
|
||||
blocks.append({
|
||||
"comments": current_block["comments"],
|
||||
"code": "\n".join(current_block["code"]).rstrip()
|
||||
})
|
||||
|
||||
return blocks
|
||||
|
||||
def extract_examples_blocks_temp(content: str):
|
||||
lines = content.splitlines()
|
||||
comment_blocks = []
|
||||
current_group = []
|
||||
first_code_index = None
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
if current_group:
|
||||
comment_blocks.append(" ".join(current_group))
|
||||
current_group = []
|
||||
continue
|
||||
if stripped.startswith("//"):
|
||||
current_group.append(stripped[2:].strip())
|
||||
else:
|
||||
if current_group:
|
||||
comment_blocks.append(" ".join(current_group))
|
||||
current_group = []
|
||||
first_code_index = i
|
||||
break
|
||||
|
||||
code_part = ""
|
||||
if first_code_index is not None:
|
||||
code_part = "\n".join(lines[first_code_index:]).rstrip()
|
||||
|
||||
return [{"comments": comment_blocks, "code": code_part}]
|
||||
|
||||
def create_entry(system_message, user_message, assistant_message, model):
|
||||
entry = {
|
||||
"created_at": datetime.now().isoformat(" "),
|
||||
"messages": [
|
||||
{"role": "system", "content": system_message},
|
||||
{"role": "user", "content": user_message},
|
||||
{"role": "assistant", "content": assistant_message}
|
||||
],
|
||||
"recommended": False,
|
||||
"upvoted": True
|
||||
}
|
||||
|
||||
if model is not "":
|
||||
entry["model"] = model
|
||||
|
||||
return entry
|
||||
|
||||
def process_doclets(doclets, output_entries, editor_name, model):
|
||||
for doclet in doclets:
|
||||
kind = doclet.get("kind", "").lower()
|
||||
see = doclet.get("see", [])
|
||||
|
||||
# The "see" field must always be present
|
||||
if not see:
|
||||
continue
|
||||
|
||||
# Processing based on the "kind" value
|
||||
if kind == "function":
|
||||
method_name = doclet.get("name", "")
|
||||
memberof = doclet.get("memberof", "")
|
||||
# Functions must have both "name" (method_name) and "memberof" fields filled
|
||||
if not (method_name and memberof):
|
||||
continue
|
||||
system_message = (
|
||||
f"You act as an API expert for Onlyoffice {editor_name.title()} editor. "
|
||||
f"This task is an example for the function {method_name} in the class {memberof}."
|
||||
)
|
||||
default_user_message = f"How do I use the method {method_name} of {memberof} class?"
|
||||
|
||||
elif kind == "class":
|
||||
class_name = doclet.get("name", "")
|
||||
system_message = (
|
||||
f"You act as an API expert for Onlyoffice {editor_name.title()} editor. "
|
||||
f"This task is an example for the class {class_name}."
|
||||
)
|
||||
default_user_message = f"How do I instantiate or work with the class {class_name}?"
|
||||
|
||||
elif kind == "typedef":
|
||||
typedef_name = doclet.get("name", "")
|
||||
system_message = (
|
||||
f"You act as an API expert for Onlyoffice {editor_name.title()} editor. "
|
||||
f"This task is an example for the typedef {typedef_name}"
|
||||
)
|
||||
default_user_message = f"How do I use the typedef {typedef_name}?"
|
||||
|
||||
else:
|
||||
continue
|
||||
|
||||
# Read the content of the first file listed in the "see" field
|
||||
content = read_file_content(f'{root}/{see[0]}')
|
||||
if content == "":
|
||||
continue
|
||||
|
||||
# now use only first block cause there is bad comments in examples
|
||||
blocks = extract_examples_blocks_temp(content)
|
||||
|
||||
for block in blocks:
|
||||
assistant_message = block['code']
|
||||
|
||||
# default entry
|
||||
output_entries.append(create_entry(system_message, default_user_message, assistant_message, model))
|
||||
|
||||
# If the file content contains comments, create a separate entry for each one
|
||||
for comment in block['comments']:
|
||||
output_entries.append(create_entry(system_message, comment, assistant_message, model))
|
||||
|
||||
def generate(output_dir, model):
|
||||
print('Generating documentation JSONL dataset...')
|
||||
|
||||
shutil.rmtree(output_dir, ignore_errors=True)
|
||||
os.makedirs(output_dir)
|
||||
|
||||
generate_docs_json.generate(f'{output_dir}/tmp_json')
|
||||
|
||||
output_entries = []
|
||||
output_filename = "dataset.jsonl"
|
||||
|
||||
for editor_name in editors:
|
||||
input_file = os.path.join(f'{output_dir}/tmp_json', editor_name + ".json")
|
||||
doclets = load_json(input_file)
|
||||
process_doclets(doclets, output_entries, editor_name, model)
|
||||
|
||||
with open(f'{output_dir}/{output_filename}', "w", encoding="utf-8") as out_file:
|
||||
for entry in output_entries:
|
||||
out_file.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
||||
|
||||
shutil.rmtree(f'{output_dir}/tmp_json')
|
||||
print('Done')
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Generate documentation JSONL dataset")
|
||||
parser.add_argument(
|
||||
"destination",
|
||||
type=str,
|
||||
help="Destination directory for the generated documentation",
|
||||
nargs='?', # Indicates the argument is optional
|
||||
default="../../../../office-js-api/dataset" # Default value
|
||||
)
|
||||
parser.add_argument(
|
||||
"model",
|
||||
type=str,
|
||||
help="Type of model",
|
||||
nargs='?', # Indicates the argument is optional
|
||||
default="" # Default value
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
generate(args.destination, args.model)
|
||||
print("START_MISSING_EXAMPLES")
|
||||
print(",".join(missing_examples))
|
||||
print("END_MISSING_EXAMPLES")
|
||||
39
scripts/sdkjs_common/jsdoc/get_latest_branch.py
Normal file
39
scripts/sdkjs_common/jsdoc/get_latest_branch.py
Normal file
@ -0,0 +1,39 @@
|
||||
import subprocess
|
||||
|
||||
def fetch_branches():
|
||||
#Fetch all branches without tags from the remote.
|
||||
subprocess.run(['git', 'fetch', '--no-tags', 'origin', '+refs/heads/*:refs/remotes/origin/*'], check=True)
|
||||
|
||||
def get_branches():
|
||||
#Get list of branches in the repository."""
|
||||
result = subprocess.run(['git', 'branch', '-r'], capture_output=True, text=True)
|
||||
return [line.strip() for line in result.stdout.splitlines()]
|
||||
|
||||
def parse_version(version_str):
|
||||
#Parse version string and return a tuple of integers (major, minor, patch).
|
||||
try:
|
||||
return tuple(map(int, version_str.lstrip('v').split('.')))
|
||||
except ValueError:
|
||||
return (0, 0, 0) # Default for non-parsable versions
|
||||
|
||||
def get_max_version_branch(branches):
|
||||
#Find the branch with the highest version.
|
||||
max_branch = None
|
||||
max_version = (0, 0, 0)
|
||||
|
||||
for branch in branches:
|
||||
parts = branch.split('/')
|
||||
if len(parts) >= 2 and (parts[1] == 'hotfix' or parts[1] == 'release'):
|
||||
version = parse_version(parts[2])
|
||||
if version > max_version:
|
||||
max_version = version
|
||||
max_branch = parts
|
||||
|
||||
return max_branch
|
||||
|
||||
if __name__ == "__main__":
|
||||
fetch_branches() # Fetch branches without tags
|
||||
branches = get_branches()
|
||||
max_version_branch = get_max_version_branch(branches)
|
||||
if max_version_branch:
|
||||
print('/'.join(max_version_branch[1:])) # Print only the branch name without origin
|
||||
@ -14,6 +14,15 @@ def is_exist_in_array(projects, proj):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_full_projects_list(json_data, list):
|
||||
result = []
|
||||
for rec in list:
|
||||
if rec in json_data:
|
||||
result += get_full_projects_list(json_data, json_data[rec])
|
||||
else:
|
||||
result.append(rec)
|
||||
return result
|
||||
|
||||
def adjust_project_params(params):
|
||||
ret_params = params
|
||||
|
||||
@ -86,13 +95,9 @@ def get_projects(pro_json_path, platform):
|
||||
|
||||
# check aliases to modules
|
||||
records_src = data[module]
|
||||
records = []
|
||||
records = get_full_projects_list(data, records_src)
|
||||
|
||||
for rec in records_src:
|
||||
if rec in data:
|
||||
records += data[rec]
|
||||
else:
|
||||
records.append(rec)
|
||||
print(records)
|
||||
|
||||
for rec in records:
|
||||
params = []
|
||||
|
||||
27
sln.json
27
sln.json
@ -1,6 +1,10 @@
|
||||
{
|
||||
"root" : "../",
|
||||
|
||||
"spell" : [
|
||||
"[win,linux,mac]core/Common/3dParty/hunspell/qt/hunspell.pro"
|
||||
],
|
||||
|
||||
"core" : [
|
||||
|
||||
"core/Common/3dParty/cryptopp/project/cryptopp.pro",
|
||||
@ -18,12 +22,13 @@
|
||||
"core/HtmlFile2/HtmlFile2.pro",
|
||||
"core/Fb2File/Fb2File.pro",
|
||||
"core/EpubFile/CEpubFile.pro",
|
||||
|
||||
"core/HtmlRenderer/htmlrenderer.pro",
|
||||
"core/HwpFile/HWPFile.pro",
|
||||
|
||||
"core/Apple/IWork.pro",
|
||||
|
||||
"core/DocxRenderer/DocxRenderer.pro",
|
||||
|
||||
"core/DesktopEditor/doctrenderer/doctrenderer.pro",
|
||||
"core/DesktopEditor/doctrenderer/docbuilder.python/src/docbuilder_func_lib.pro",
|
||||
|
||||
"[!no_x2t]core/OOXML/Projects/Linux/DocxFormatLib/DocxFormatLib.pro",
|
||||
"[!no_x2t]core/OOXML/Projects/Linux/PPTXFormatLib/PPTXFormatLib.pro",
|
||||
@ -51,15 +56,19 @@
|
||||
|
||||
"[win,linux,mac,!linux_arm64]core/OfficeCryptReader/ooxml_crypt/ooxml_crypt.pro",
|
||||
|
||||
"spell",
|
||||
|
||||
"[win,linux,mac,!no_tests]core/DesktopEditor/vboxtester/vboxtester.pro",
|
||||
"[win,linux,mac,!no_tests]core/Test/Applications/StandardTester/standardtester.pro",
|
||||
"[win,linux,mac,!no_tests]core/Test/Applications/x2tTester/x2ttester.pro",
|
||||
"[win,linux,mac,!no_tests]core/Test/Applications/MetafileTester/MetafileTester.pro"
|
||||
"[win,linux,mac,!no_tests]core/Test/Applications/MetafileTester/MetafileTester.pro",
|
||||
"[win,linux,mac,!no_tests]core/Common/3dParty/hunspell/test/test.pro"
|
||||
|
||||
],
|
||||
|
||||
"builder" : [
|
||||
"core"
|
||||
"core",
|
||||
"core/DesktopEditor/doctrenderer/docbuilder.python/src/docbuilder_func_lib.pro"
|
||||
],
|
||||
|
||||
"server" : [
|
||||
@ -70,13 +79,8 @@
|
||||
"[win,linux]desktop-sdk/ChromiumBasedEditors/videoplayerlib/videoplayerlib.pro"
|
||||
],
|
||||
|
||||
"spell" : [
|
||||
"[win,linux,mac]core/Common/3dParty/hunspell/qt/hunspell.pro"
|
||||
],
|
||||
|
||||
"desktop" : [
|
||||
"core",
|
||||
"spell",
|
||||
"multimedia",
|
||||
|
||||
"core/DesktopEditor/xmlsec/src/ooxmlsignature.pro",
|
||||
@ -88,7 +92,8 @@
|
||||
"[win,linux]desktop-apps/win-linux/ASCDocumentEditor.pro",
|
||||
|
||||
"[win]desktop-apps/win-linux/extras/projicons/ProjIcons.pro",
|
||||
"[win,!win_xp]desktop-apps/win-linux/extras/update-daemon/UpdateDaemon.pro"
|
||||
"[win,!win_xp]desktop-apps/win-linux/extras/update-daemon/UpdateDaemon.pro",
|
||||
"[win_xp]desktop-apps/win-linux/extras/online-installer/OnlineInstaller.pro"
|
||||
],
|
||||
|
||||
"mobile" : [
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"browser" : "chrome",
|
||||
"browserUrl" : "C:/Program Files/Google/Chrome/Application/chrome.exe"
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"browser" : "firefox",
|
||||
"browserUrl" : "C:/Program Files/Mozilla Firefox/firefox.exe"
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
sys.path.append('../../scripts')
|
||||
import base
|
||||
import os
|
||||
|
||||
os.environ["PUPPETEER_SKIP_CHROMIUM_DOWNLOAD"] = "true"
|
||||
base.cmd("npm", ["i", "puppeteer"])
|
||||
@ -1,64 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
sys.path.append('../../scripts')
|
||||
import base
|
||||
import os
|
||||
import glob
|
||||
import json
|
||||
|
||||
def get_tests_in_dir(directory):
|
||||
files = []
|
||||
for file in glob.glob(directory + "/*.js"):
|
||||
if base.is_file(file):
|
||||
files.append(file)
|
||||
elif is_dir(file):
|
||||
files += get_tests_in_dir(file)
|
||||
return files
|
||||
|
||||
params = sys.argv[1:]
|
||||
if (0 == len(params)):
|
||||
print("use: run.py path_to_config [path_to_test]")
|
||||
exit(0)
|
||||
|
||||
config_path = params[0]
|
||||
test_file = "./tests"
|
||||
|
||||
if (1 < len(params)):
|
||||
test_file = params[1]
|
||||
|
||||
tests_array = [test_file]
|
||||
if base.is_dir(test_file):
|
||||
tests_array = get_tests_in_dir(test_file)
|
||||
|
||||
config_content = "{}"
|
||||
with open(config_path, "r") as config_path_loader:
|
||||
config_content = config_path_loader.read()
|
||||
|
||||
print(config_content)
|
||||
|
||||
config = json.loads(config_content)
|
||||
os.environ["PUPPETEER_SKIP_CHROMIUM_DOWNLOAD"] = "true"
|
||||
if "browser" in config:
|
||||
print("browser: " + config["browser"])
|
||||
os.environ["PUPPETEER_PRODUCT"] = config["browser"]
|
||||
|
||||
if "browserUrl" in config:
|
||||
print("browserUrl: " + config["browserUrl"])
|
||||
os.environ["PUPPETEER_EXECUTABLE_PATH"] = config["browserUrl"]
|
||||
|
||||
if not base.is_dir("./work_directory"):
|
||||
base.create_dir("./work_directory")
|
||||
base.create_dir("./work_directory/cache")
|
||||
base.create_dir("./work_directory/downloads")
|
||||
|
||||
for test in tests_array:
|
||||
print("run test: " + test)
|
||||
run_file = test + ".runned.js"
|
||||
base.copy_file("./tester.js", run_file)
|
||||
test_content = base.readFile(test)
|
||||
test_content = test_content.replace("await Tester.", "Tester.")
|
||||
test_content = test_content.replace("Tester.", "await Tester.")
|
||||
base.replaceInFile(run_file, "\"%%CODE%%\"", test_content)
|
||||
base.cmd("node", [run_file])
|
||||
base.delete_file(run_file)
|
||||
@ -1,171 +0,0 @@
|
||||
const puppeteer = require('puppeteer')
|
||||
const pathfs = require('path')
|
||||
const fs = require('fs');
|
||||
|
||||
function TesterImpl()
|
||||
{
|
||||
this.browser = null;
|
||||
this.page = null;
|
||||
this.width = 1500;
|
||||
this.height = 800;
|
||||
this.pixelRatio = 1;
|
||||
|
||||
this.cacheDir = pathfs.resolve("./work_directory/cache");
|
||||
this.downloadsDir = pathfs.resolve("./work_directory/downloads");
|
||||
this.downloadCounter = 0;
|
||||
|
||||
this.load = async function(url)
|
||||
{
|
||||
const head = { x: 100, y: 200 };
|
||||
this.browser = await puppeteer.launch({
|
||||
headless: false,
|
||||
product: process.env["PUPPETEER_PRODUCT"],
|
||||
args: [
|
||||
"--disable-infobars",
|
||||
`--window-size=${this.width+head.x},${this.height+head.y}`,
|
||||
"--disk-cache-dir=" + this.cacheDir
|
||||
],
|
||||
defaultViewport : {width: this.width, height: this.height, deviceScaleFactor : this.pixelRatio }
|
||||
});
|
||||
|
||||
this.page = await this.browser.newPage();
|
||||
await this.page.setViewport({ width: this.width, height: this.height });
|
||||
let waitObject = (process.env["PUPPETEER_PRODUCT"] === "firefox") ? { waitUntil: "networkidle0", timeout: 15000 } : {};
|
||||
await this.page.goto(url + "&autotest=enabled", waitObject);
|
||||
console.log("[tester] pageLoaded");
|
||||
return this.page;
|
||||
};
|
||||
|
||||
this.close = async function(nosleep)
|
||||
{
|
||||
if (true !== nosleep)
|
||||
await this.waitAutosave();
|
||||
await this.browser.close();
|
||||
};
|
||||
|
||||
this.sleep = async function(ms)
|
||||
{
|
||||
return await new Promise(resolve => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
this.waitEditor = async function()
|
||||
{
|
||||
// TODO: wait first onEndRecalculate
|
||||
await this.sleep(5000);
|
||||
console.log("[tester] editorReady");
|
||||
};
|
||||
|
||||
this.waitAutosave = async function()
|
||||
{
|
||||
await this.sleep(5000);
|
||||
};
|
||||
|
||||
this.evaluateInMainFrame = async function(code)
|
||||
{
|
||||
return await this.page.evaluate(code);
|
||||
};
|
||||
this.evaluateInEditorFrame = async function(code)
|
||||
{
|
||||
const frame = await this.page.frames().find(frame => frame.name() === 'frameEditor');
|
||||
if (!frame)
|
||||
return;
|
||||
return await frame.evaluate(code);
|
||||
};
|
||||
|
||||
this.click = async function(id)
|
||||
{
|
||||
let res = await this.evaluateInEditorFrame("document.getElementById(\"" + id + "\").click(); \"[tester] clicked: " + id + "\"");
|
||||
//console.log(res);
|
||||
await this.sleep(200);
|
||||
return res;
|
||||
};
|
||||
|
||||
this.mouseClick = async function(x, y, options)
|
||||
{
|
||||
let res = await this.page.mouse.click(x, y, options);
|
||||
await this.sleep(200);
|
||||
return res;
|
||||
};
|
||||
|
||||
this.eval = async function(code)
|
||||
{
|
||||
let res = await this.evaluateInEditorFrame(code);
|
||||
await this.sleep(200);
|
||||
return res;
|
||||
};
|
||||
|
||||
this.keyDown = async function(key)
|
||||
{
|
||||
// https://pptr.dev/api/puppeteer.keyinput
|
||||
let res = await this.page.keyboard.down(key);
|
||||
await this.sleep(200);
|
||||
return res;
|
||||
};
|
||||
|
||||
this.keyUp = async function(key)
|
||||
{
|
||||
// https://pptr.dev/api/puppeteer.keyinput
|
||||
let res = await this.page.keyboard.up(key);
|
||||
await this.sleep(200);
|
||||
return res;
|
||||
};
|
||||
|
||||
this.keyClick = async function(key)
|
||||
{
|
||||
// https://pptr.dev/api/puppeteer.keyinput
|
||||
let res = await this.page.keyboard.down(key);
|
||||
res = await this.page.keyboard.up(key);
|
||||
await this.sleep(200);
|
||||
return res;
|
||||
};
|
||||
|
||||
this.keyPress = async function(key)
|
||||
{
|
||||
// https://pptr.dev/api/puppeteer.keyinput
|
||||
let res = await this.page.keyboard.press(key);
|
||||
await this.sleep(200);
|
||||
return res;
|
||||
};
|
||||
|
||||
this.input = async function(text)
|
||||
{
|
||||
let res = await this.page.keyboard.type(text);
|
||||
await this.sleep(200);
|
||||
return res;
|
||||
};
|
||||
|
||||
this.downloadFile = async function(format, path)
|
||||
{
|
||||
const tmpDir = pathfs.resolve(this.downloadsDir, "./tmp" + this.downloadCounter++);
|
||||
fs.mkdirSync(tmpDir);
|
||||
|
||||
// emulate download
|
||||
const client = await this.page.target().createCDPSession();
|
||||
await client.send("Page.setDownloadBehavior", {
|
||||
behavior: "allow",
|
||||
downloadPath: tmpDir
|
||||
});
|
||||
|
||||
await this.evaluateInEditorFrame("document.querySelectorAll('[data-layout-name=\"toolbar-file\"]')[0].click();");
|
||||
await this.sleep(200);
|
||||
await this.evaluateInEditorFrame("document.getElementsByClassName(\"svg-format-" + format + "\")[0].click();");
|
||||
await this.sleep(200);
|
||||
await this.evaluateInEditorFrame("document.getElementById(\"fm-btn-return\").click();");
|
||||
|
||||
await this.sleep(2000);
|
||||
|
||||
const files = fs.readdirSync(tmpDir);
|
||||
fs.copyFileSync(pathfs.resolve(tmpDir, "./" + files[0]), pathfs.resolve(path));
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
};
|
||||
}
|
||||
|
||||
const Tester = new TesterImpl;
|
||||
|
||||
try {
|
||||
(async () => {
|
||||
"%%CODE%%"
|
||||
})();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
Tester.load("path_to_file");
|
||||
Tester.waitEditor();
|
||||
|
||||
// down Enter
|
||||
Tester.keyClick("Enter");
|
||||
|
||||
// type text
|
||||
Tester.input("Hello World!");
|
||||
|
||||
Tester.keyPress("ArrowLeft");
|
||||
Tester.keyDown("Shift");
|
||||
for (let i = 0; i < 5; i++)
|
||||
Tester.keyPress("ArrowLeft");
|
||||
Tester.keyUp("Shift");
|
||||
|
||||
// bold
|
||||
Tester.click("id-toolbar-btn-bold");
|
||||
// italic
|
||||
Tester.mouseClick(115, 105);
|
||||
|
||||
// if needed
|
||||
Tester.waitAutosave();
|
||||
|
||||
Tester.downloadFile("docx", "./work_directory/new.docx")
|
||||
Tester.downloadFile("odt", "./work_directory/new.odt")
|
||||
|
||||
Tester.close(true);
|
||||
114
tools/common/desktop_templates.py
Normal file
114
tools/common/desktop_templates.py
Normal file
@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
sys.path.append('../../scripts')
|
||||
import base
|
||||
import os
|
||||
import glob
|
||||
import base64
|
||||
|
||||
sys.stdin.reconfigure(encoding='utf-8')
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
base.configure_common_apps()
|
||||
|
||||
def change_property(data_src, name, value):
|
||||
data = data_src
|
||||
creator_open = "<dc:" + name + ">"
|
||||
creator_close = "</dc:" + name + ">"
|
||||
open_tag_pos = data.find(creator_open)
|
||||
if open_tag_pos == -1:
|
||||
creator_close_to_find = "<dc:" + name + "/>"
|
||||
else:
|
||||
creator_close_to_find = "</dc:" + name + ">"
|
||||
close_tag_pos = data.find(creator_close_to_find)
|
||||
last_tag_pos = data.find("</cp:coreProperties>")
|
||||
|
||||
if open_tag_pos != -1 and close_tag_pos != - 1:
|
||||
data = data[:open_tag_pos + len(creator_open)] + value + data[close_tag_pos:]
|
||||
elif close_tag_pos != - 1:
|
||||
data = data[:close_tag_pos] + creator_open + value + creator_close + data[close_tag_pos + len(creator_close_to_find):]
|
||||
else:
|
||||
data = data[:last_tag_pos] + creator_open + value + creator_close + data[last_tag_pos:]
|
||||
return data
|
||||
|
||||
def change_author_name(file_input):
|
||||
temp_dir = os.getcwd().replace("\\", "/") + "/temp"
|
||||
base.create_dir(temp_dir)
|
||||
|
||||
app = "7za" if ("mac" == base.host_platform()) else "7z"
|
||||
base.cmd_exe(app, ["x", "-y", file_input, "-o" + temp_dir, "docProps/core.xml", "-r"])
|
||||
|
||||
with open(temp_dir + "/docProps/core.xml", 'r', encoding='utf-8') as file:
|
||||
data = file.read()
|
||||
|
||||
data = change_property(data, "creator", "")
|
||||
data = change_property(data, "lastModifiedBy", "")
|
||||
|
||||
with open(temp_dir + "/docProps/core.xml", 'w', encoding='utf-8') as file:
|
||||
file.write(data)
|
||||
|
||||
base.cmd_exe(app, ["a", "-r", file_input, temp_dir + "/docProps"])
|
||||
base.delete_dir(temp_dir)
|
||||
|
||||
def get_files(dir):
|
||||
arr_files = []
|
||||
for file in glob.glob(dir + "/*"):
|
||||
if base.is_file(file):
|
||||
arr_files.append(file)
|
||||
elif base.is_dir(file):
|
||||
arr_files += get_files(file)
|
||||
return arr_files
|
||||
|
||||
def get_local_path(base, src_dir):
|
||||
test1 = base.replace("\\", "/")
|
||||
test2 = src_dir.replace("\\", "/")
|
||||
return test2[len(test1)+1:]
|
||||
|
||||
params = sys.argv[1:]
|
||||
|
||||
if (3 > len(params)):
|
||||
print("use: convert.py path_to_x2t_directory path_to_input_directory path_to_output_directory")
|
||||
exit(0)
|
||||
|
||||
base.configure_common_apps()
|
||||
|
||||
x2t_directory = params[0]
|
||||
src_directory = params[1]
|
||||
dst_directory = params[2]
|
||||
|
||||
if base.is_dir(dst_directory):
|
||||
base.delete_dir(dst_directory)
|
||||
base.create_dir(dst_directory)
|
||||
|
||||
src_files = get_files(src_directory)
|
||||
|
||||
for file in src_files:
|
||||
directory = os.path.dirname(file)
|
||||
name = os.path.basename(file)
|
||||
directory_out_file = dst_directory + "/" + get_local_path(src_directory, directory)
|
||||
if not base.is_dir(directory_out_file):
|
||||
os.makedirs(directory_out_file, exist_ok=True)
|
||||
name_without_ext = os.path.splitext(name)[0]
|
||||
name_ext = os.path.splitext(name)[1][1:]
|
||||
|
||||
dst_ext = name_ext
|
||||
if ("docx" == name_ext) or ("dotx" == name_ext):
|
||||
dst_ext = "dotx"
|
||||
elif ("pptx" == name_ext) or ("potx" == name_ext):
|
||||
dst_ext = "potx"
|
||||
elif ("xlsx" == name_ext) or ("xltx" == name_ext):
|
||||
dst_ext = "xltx"
|
||||
|
||||
dst_name = name_without_ext
|
||||
if (len(dst_name) < 4) or (dst_name[0:4] != "[32]"):
|
||||
dst_name = "[32]" + base64.b32encode(name_without_ext.encode("utf-8")).decode("utf-8")
|
||||
|
||||
dst_file = directory_out_file + "/" + dst_name + "." + dst_ext
|
||||
|
||||
os.makedirs(directory_out_file, exist_ok=True)
|
||||
base.cmd_in_dir(x2t_directory, "x2t", [file, dst_file])
|
||||
|
||||
change_author_name(dst_file)
|
||||
|
||||
print(name_without_ext + " => " + dst_name)
|
||||
@ -55,14 +55,23 @@ def install_qt():
|
||||
base.cmd_in_dir("./qt-everywhere-opensource-src-5.9.9", "make", ["-j", "4"])
|
||||
base.cmd_in_dir("./qt-everywhere-opensource-src-5.9.9", "make", ["install"])
|
||||
return
|
||||
|
||||
def install_qt_prebuild():
|
||||
url_amd64 = "https://s3.eu-west-1.amazonaws.com/static-doc.teamlab.eu.com/qt/5.9.9/linux_amd64/qt_binary.7z"
|
||||
base.download(url_amd64, "./qt_amd64.7z")
|
||||
base.extract("./qt_amd64.7z", "./qt_build")
|
||||
base.create_dir("./qt_build/Qt-5.9.9")
|
||||
base.cmd("mv", ["./qt_build/qt_amd64", "./qt_build/Qt-5.9.9/gcc_64"])
|
||||
base.setup_local_qmake("./qt_build/Qt-5.9.9/gcc_64/bin")
|
||||
return
|
||||
|
||||
if not base.is_file("./node_js_setup_14.x"):
|
||||
print("install dependencies...")
|
||||
deps.install_deps()
|
||||
|
||||
if not base.is_dir("./qt_build"):
|
||||
if not base.is_dir("./qt_build"):
|
||||
print("install qt...")
|
||||
install_qt()
|
||||
install_qt_prebuild()
|
||||
|
||||
branch = get_branch_name("../..")
|
||||
|
||||
@ -103,6 +112,3 @@ build_tools_params = ["--branch", branch,
|
||||
|
||||
base.cmd_in_dir("../..", "./configure.py", build_tools_params)
|
||||
base.cmd_in_dir("../..", "./make.py")
|
||||
|
||||
|
||||
|
||||
|
||||
@ -39,9 +39,14 @@ def install_deps():
|
||||
"libxi-dev",
|
||||
"libxrender-dev",
|
||||
"libxss1",
|
||||
"libncurses5"]
|
||||
"libncurses5",
|
||||
"libncurses6",
|
||||
"curl",
|
||||
"libxkbcommon-dev",
|
||||
"libxkbcommon-x11-dev"]
|
||||
|
||||
base.cmd("sudo", ["apt-get", "install", "-y"] + packages)
|
||||
for package in packages:
|
||||
base.cmd("sudo", ["apt-get", "install", "-y", package], True)
|
||||
|
||||
# nodejs
|
||||
base.cmd("sudo", ["apt-get", "install", "-y", "nodejs"])
|
||||
@ -79,4 +84,3 @@ def install_deps():
|
||||
|
||||
if __name__ == "__main__":
|
||||
install_deps()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user