Compare commits

..

239 Commits

Author SHA1 Message Date
b87e305c06 Merge branch hotfix/v8.3.3 into release/v9.0.0 2025-04-21 08:57:49 +00:00
ab3165fdaf Add script for minimization js 2025-04-14 20:57:52 +03:00
1539c187e3 Merge pull request 'Up version to 8.3.3' (#72) from fix/version8.3.3 into hotfix/v8.3.3
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/72
2025-04-14 10:11:35 +00:00
78df8eb494 Up version to 8.3.3 2025-04-14 14:52:43 +05:00
2ab7616132 Fix typo 2025-04-07 12:25:42 +03:00
3be471b472 Merge pull request 'feature/clang' (#71) from feature/clang into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/71
2025-04-06 09:55:15 +00:00
a151375339 Remove spaces 2025-04-06 12:54:23 +03:00
ed2ab2d80b Refactoring 2025-04-06 12:53:03 +03:00
35f99ac3a0 Fix icu build 2025-04-04 10:20:24 +03:00
836a0401ed Testing clang build 2025-04-04 08:55:38 +03:00
b8024df7d3 Merge branch hotfix/v8.3.2 into release/v9.0.0 2025-04-01 08:32:43 +00:00
74c02f9d50 Merge branch hotfix/v8.3.2 into hotfix/v8.3.3 2025-04-01 08:32:42 +00:00
2b07d1aa4d Fix build 2025-04-01 10:50:23 +03:00
c6acd6cdcd Merge pull request '[jsdoc] Fix write inherited methods' (#69) from fix/js-doc into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/69
2025-03-27 11:42:25 +00:00
cee122afa5 [jsdoc] Fix write inherited methods 2025-03-27 09:53:10 +07:00
4c76406f8c Fix folder creation 2025-03-26 15:49:50 +03:00
6fd89057ec Fix typo 2025-03-26 15:41:48 +03:00
ae8b77628e Fix build 2025-03-26 15:31:21 +03:00
bf7df0b45a Merge pull request 'hotfix/v8.3.2' (#67) from hotfix/v8.3.2 into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/67
2025-03-21 11:43:22 +00:00
3589ea0f60 Fix bug with duplicate projects 2025-03-21 10:24:44 +03:00
ad23ee2803 Merge branch hotfix/v8.3.2 into master 2025-03-19 12:43:41 +00:00
bc59f739f5 Merge pull request 'Up version to 8.3.2' (#66) from fix/version8.3.2 into hotfix/v8.3.2
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/66
2025-03-18 11:09:00 +00:00
b8e42184f8 Up version to 8.3.2 2025-03-18 15:54:05 +05:00
50c312513c Merge pull request '[jsdoc] Fix dataset system message' (#65) from fix/js-doc into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/65
2025-03-18 10:01:27 +00:00
427ae97dd2 [jsdoc] Fix dataset system message 2025-03-18 16:59:58 +07:00
d80f1f1b0f [develop] Parse config to fix install_mysqlserver 2025-03-18 10:19:18 +03:00
9435cdc99b Merge pull request 'Added html 3dParty version control' (#64) from fix/html into hotfix/v8.3.2
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/64
2025-03-12 16:54:41 +00:00
cf90a5ce21 Added html 3dParty version control 2025-03-12 19:50:03 +03:00
3f4d0cefa8 Merge pull request 'hotfix/v8.3.2' (#63) from hotfix/v8.3.2 into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/63
2025-03-12 07:58:39 +00:00
8dcf0277ac Merge pull request '[jsdoc] Fix typo' (#62) from fix/js-doc into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/62
2025-03-10 07:49:10 +00:00
6664051127 [jsdoc] Fix typo 2025-03-10 14:47:55 +07:00
019f10ee86 Merge pull request '[jsdoc] Fix expression example' (#61) from fix/js-doc into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/61
2025-03-07 14:59:01 +00:00
a7ee5d5679 [jsdoc] Generation for site 2025-03-07 21:36:25 +07:00
7c31890fc0 [jsdoc] Fix expression example 2025-03-07 16:41:06 +07:00
ad9258710b Merge pull request '[jsdoc] Fix prev' (#60) from fix/js-doc into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/60
2025-03-07 09:21:06 +00:00
626dd37312 [jsdoc] Fix prev 2025-03-07 16:07:23 +07:00
b4d95ccbb9 Merge pull request '[jsdoc] Rename md docs folders' (#58) from fix/js-doc into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/58
2025-03-07 08:54:22 +00:00
0d0ae2b5e6 [jsdoc] Rename md docs folders 2025-03-07 15:49:57 +07:00
d7b3b7f120 Merge pull request '[jsdoc] Write only used enums' (#57) from fix/js-doc into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/57
2025-03-07 08:20:20 +00:00
7a864171b3 [jsdoc] Write only used enums 2025-03-07 15:04:48 +07:00
8cfb8f3d84 Merge pull request '[jsdoc] Fix path' (#56) from fix/js-doc into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/56
2025-03-06 13:58:01 +00:00
2f39454d31 [jsdoc] Fix path 2025-03-06 20:51:42 +07:00
de33755900 Remove unused files 2025-03-06 15:06:49 +03:00
d3d1080c89 Merge pull request '[jsdoc] Fix bug, sort props/methods' (#55) from fix/js-doc into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/55
2025-03-06 11:35:59 +00:00
539597f07c [jsdoc] Fix bug, sort props/methods, handle @link tag 2025-03-06 18:30:24 +07:00
6e87116634 Fix typo 2025-03-06 13:56:55 +03:00
029b16ca68 Fix deploy builder 2025-03-06 13:05:25 +03:00
40b11e192d Merge pull request '[jsdoc] Removed editor tag in plugins examples' (#54) from fix/js-doc into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/54
2025-03-06 06:45:33 +00:00
72cc19f346 [jsdoc] Removed editor tag in plugins examples 2025-03-06 13:43:56 +07:00
efb22f741f Merge pull request '[jsdoc] Fixes docs generation' (#53) from fix/js-doc into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/53
2025-03-05 12:07:32 +00:00
2458673d3c [jsdoc] Fixes docs generation 2025-03-05 19:01:28 +07:00
3881a6659e Merge pull request '[jsdoc] Class markdown, data types fixes' (#52) from fix/js-doc into release/v9.0.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/52
2025-03-04 12:59:06 +00:00
a567cc2222 [jsdoc] Class markdown, data types fixes 2025-03-04 19:43:48 +07:00
32b47cd21e Merge branch hotfix/v8.3.1 into master 2025-02-27 13:16:17 +00:00
bf75e1c062 Merge pull request 'Up version to 8.3.1' (#50) from fix/version8.3.1 into hotfix/v8.3.1
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/50
2025-02-27 13:15:05 +00:00
97fccfa34b Up version to 8.3.1 2025-02-27 12:07:55 +05:00
1fc9382ce9 Merge pull request 'hotfix/v8.3.1' (#49) from hotfix/v8.3.1 into develop
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/49
2025-02-20 13:31:20 +00:00
ea43e67fe8 Merge pull request '[jsdoc] Added editor name to examples' (#48) from fix/js-doc into hotfix/v8.3.1
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/48
2025-02-19 17:34:37 +00:00
dd28a41e17 [jsdoc] Added editor name to examples 2025-02-20 00:30:45 +07:00
b11a273d65 Merge pull request '[jsdoc] Remove model field if empty' (#46) from fix/js-doc into develop
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/46
2025-02-12 05:45:02 +00:00
d4ee25b004 [jsdoc] Remove model field if empty 2025-02-12 12:35:28 +07:00
a2b7719100 [v8] Disable git configuration on windows 2025-02-11 21:32:05 +03:00
1e6cde4d98 Merge pull request '[jsdoc] Added generation dataset script' (#45) from feauture/dataset-script into develop
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/45
2025-02-11 08:05:25 +00:00
34f627d146 [jsdoc] Added generation dataset script 2025-02-11 15:01:58 +07:00
54accd4394 Merge pull request '[jsdoc] Escape "|" in tables md' (#44) from fix/js-doc into hotfix/v8.3.1
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/44
2025-02-11 07:56:03 +00:00
63557fba56 [jsdoc] Escape "|" in tables md 2025-02-11 14:54:14 +07:00
7a4be158c2 Merge pull request 'Online installer: switch to MSVC2015' (#41) from feature/online-installer-msvc2015 into develop 2025-02-06 15:30:10 +00:00
810e12bd22 Merge release/8.3.0 into master 2025-02-06 07:41:46 +00:00
066f7ad8c1 Version Up 2025-02-06 10:31:28 +03:00
e52a654731 Merge branch release/v8.3.0 into master 2025-02-05 17:13:36 +00:00
370879f636 [jsdoc] Code block for expression 2025-01-31 17:18:30 +07:00
170a511654 [win] online installer: switch to MSVC2015 2025-01-30 13:24:51 +02:00
679afe1bc4 Fix generation on mac 2025-01-27 10:18:20 +03:00
8b5cfff24a [jsdoc] Remove output dirs before generation 2025-01-27 06:48:12 +00:00
27de97031e Merge pull request '[jsdoc] FIx paths in enums' (#39) from fix/js-doc into release/v8.3.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/39
2025-01-27 06:37:46 +00:00
8ee874da14 [jsdoc] FIx paths in enums 2025-01-27 13:23:58 +07:00
11c783f088 Merge pull request 'release/v8.3.0' (#38) from release/v8.3.0 into develop
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/38
2025-01-26 07:48:28 +00:00
a3cb31291f [desktop] debug 2025-01-23 13:32:21 +03:00
6a43b86912 [desktop] for bug 62528 2025-01-23 11:15:01 +03:00
21bb535ee0 [jsdoc] Added plugins md docs generation 2025-01-22 12:49:26 +00:00
9ea948b825 [jsdoc] Fix generation 2025-01-22 12:49:26 +00:00
fe2fad9378 [desktop] refactoring 2025-01-22 13:00:50 +03:00
d566ffd9fa Fix mac builder packages build 2025-01-21 12:22:46 +03:00
370b23f38f Fix md syntax 2025-01-20 21:03:27 +03:00
253ee696be Merge pull request '[desktop] remove folders for IE from web-apps' (#35) from feature/desktop-clean-ie-folder into release/v8.3.0 2025-01-20 16:18:08 +00:00
e08c6f79bc [desktop] remove folders for IE from web-apps 2025-01-20 19:16:03 +03:00
4240319fef [develop] Print error in check_mysqlServer 2025-01-17 11:23:20 +03:00
e1aaa2415b Merge pull request 'Delete unnecessary files on builder deploy' (#34) from feature/minimize-builder-deploy into release/v8.3.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/34
2025-01-15 11:51:28 +00:00
e71eb56630 Fix builder packages build 2025-01-15 11:25:06 +03:00
38496f2971 Delete unnecessary files on builder deploy 2025-01-14 20:04:54 +04:00
d1c7d8d9f6 HWPFile added to build_tools 2025-01-13 14:06:16 +03:00
36fdfd672f Merge pull request 'Fix bug 72349' (#30) from fix/bug72349 into release/v8.3.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/30
2025-01-10 10:03:10 +00:00
55c0f61189 Fix bug 72349 2025-01-10 13:50:59 +04:00
053e317850 Merge pull request 'release/v8.3.0' (#29) from release/v8.3.0 into develop
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/29
2025-01-02 19:54:05 +00:00
38296bf292 Merge pull request '[jsdoc][plugins] Fixed reading common methods' (#28) from fix/build-jsdoc-plugins into release/v8.3.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/28
2024-12-22 13:34:46 +00:00
f0ba4564cc [jsdoc][plugins] Fixed reading common methods 2024-12-22 19:53:43 +07:00
21ec70214d fix Bug 70053 - Fix warning with JSONArgsRecommended 2024-12-20 10:47:39 +00:00
6d1a8376ba Merge pull request '[jsdoc] Fixes for build plugins api' (#23) from fix/build-jsdoc-plugins into release/v8.3.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/23
2024-12-18 07:42:02 +00:00
0ca83fe152 [jsdoc] Fixes for build plugins api 2024-12-16 18:44:35 +07:00
2301c407a2 Remove HtmlRenderer library 2024-12-10 14:33:10 +03:00
d6096431bd Merge branch hotfix/v8.2.2 into develop 2024-12-09 11:57:11 +00:00
d7532d5b83 Merge branch hotfix/v8.2.2 into release/v8.3.0 2024-12-09 11:57:11 +00:00
c7d805f8df Up iwork version 2024-12-02 17:02:51 +03:00
d78ab30cdf Merge pull request 'Online-installer: add dev-channel support' (#21) from feature/online-installer-dev-channel into develop 2024-11-28 14:06:02 +00:00
c123f77195 [win] online-installer: add dev-channel support 2024-11-28 15:30:47 +02:00
a60bc78e23 Merge branch hotfix/v8.2.2 into master 2024-11-28 12:13:38 +00:00
78ee107e85 Fix build builder with branding onlyoffice 2024-11-28 14:57:00 +03:00
12c3310451 Up version 2024-11-27 20:01:36 +03:00
d525d8f603 Add change author for templates 2024-11-24 22:50:21 +03:00
337d1095dc Up iwork version 2024-11-22 17:51:36 +03:00
fab40cb6b3 Reformat code 2024-11-22 17:13:08 +03:00
f4cdc1aecd Merge pull request 'Update iwork build' (#20) from fix/iwork into develop
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/20
2024-11-22 13:45:29 +00:00
f702e3245a Update iwork build 2024-11-22 15:38:56 +03:00
d890ba4f43 Fix ds prerequisites build 2024-11-22 12:48:32 +03:00
d929ed411f Fix typo 2024-11-22 11:34:58 +03:00
55daa28d74 Merge pull request 'hotfix/v8.2.2' (#18) from hotfix/v8.2.2 into develop
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/18
2024-11-22 08:26:38 +00:00
2bab12aad1 Merge pull request 'feature/iwork' (#17) from feature/iwork into develop
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/17
2024-11-22 07:37:28 +00:00
80fb376132 Fix bug 71595 2024-11-21 17:29:22 +03:00
1d557f1065 Fix typo 2024-11-21 15:10:51 +03:00
30df3df8cf Setup qmake with portable data 2024-11-21 14:32:00 +03:00
02b4655a16 Refactoring 2024-11-20 16:07:21 +03:00
debf0158d4 iWork added to build_tools 2024-11-19 14:05:51 +03:00
0f730c1948 Write macos update changelog placeholder 2024-11-19 08:03:36 +00:00
fa7e324fe0 Add deps 2024-11-18 18:30:05 +03:00
e2313e6a3d fix Bug 71385 - Fix issue with DocsApi 2024-11-18 10:04:31 +00:00
2ce8c42323 Remove errors with setup system git 2024-11-18 11:57:30 +03:00
684e65adaa Skip errors from git config 2024-11-17 14:08:07 +03:00
a8fc3fb2f1 Add DocumentServer Prerequisites installer build 2024-11-15 18:10:04 +03:00
68bcdb2f88 Merge branch hotfix/v8.2.1 into master 2024-11-12 12:48:18 +00:00
af3627bccb Fix build 2024-11-11 16:50:32 +03:00
4cbe032363 Add online installer upload 2024-11-07 15:47:57 +03:00
5e4b3cf0d2 Fix build with modern compilers 2024-11-05 14:22:50 +03:00
593af1048b Remove windows changelog deploy 2024-11-02 12:44:00 +00:00
ae00ecb773 Fix web-apps closure maps deploy 2024-11-02 12:44:00 +00:00
da83e42172 Refactor packages deploy 2024-11-02 12:44:00 +00:00
2895d53f8e Fix mac old zip copy 2024-11-01 15:45:38 +03:00
10d1f22ec3 Merge pull request 'Deploy online-installer' (#10) from feature/deploy-online-installer into develop
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/10
2024-10-29 18:20:12 +00:00
4ed1e64a61 [win] online-installer: temporarily comment out upload stage 2024-10-29 18:44:18 +02:00
6402936285 [win] deploy online-installer 2024-10-29 14:59:03 +02:00
e01e5c145a Update scripts/sdkjs_common/jsdoc/get_latest_branch.py 2024-10-29 06:52:02 +00:00
56f6d82c8f Update scripts/sdkjs_common/jsdoc/get_latest_branch.py 2024-10-29 06:50:29 +00:00
3e79cf0c12 Add get latest branch 2024-10-29 05:57:06 +00:00
efc09657a8 Add get latest branch 2024-10-29 05:54:44 +00:00
64390c3e01 Fix build v8 on mac 2024-10-24 14:00:14 +03:00
513edb802d Merge branch release/v8.2.0 into develop 2024-10-21 11:46:47 +00:00
52c35b8e3c Merge branch release/v8.2.0 into master 2024-10-17 11:08:22 +00:00
cf1c25031c Update version 2024-10-17 12:45:40 +03:00
7b9f18867a Revert all remplates to desktop for macos 2024-10-10 10:51:28 +03:00
0985b4dbe8 Add script for deploy templates 2024-10-10 02:45:16 +03:00
772fb721ae Add data for hard update hunspell module 2024-10-09 10:48:59 +03:00
1ef1c795c1 Merge pull request '[desktop] debug macOS app launch' (#4) from fix/mac-launch-with-templates into release/v8.2.0 2024-10-08 22:15:31 +00:00
6d956566c5 [desktop] debug 2024-10-09 01:13:47 +03:00
edec5bb25f [desktop] debug macOS app launch 2024-10-09 01:08:19 +03:00
3534f65f0e Remove empty items in plugins list 2024-10-04 18:10:31 +03:00
6fbea9c8a4 Fix build v8 on macos with xcode 16+ 2024-10-03 21:54:17 +03:00
18bba5da3d Disable bitcode by default 2024-10-03 13:49:58 +03:00
952270e1ba Fix rpaths 2024-10-02 13:51:17 +00:00
0c180e6ee5 Fix deploy automation API 2024-09-27 12:13:45 +00:00
fdd9c329b1 Fix deploy automation API 2024-09-26 16:51:44 +00:00
5b80459b37 Merge pull request 'feature/templates-for-desktop' (#1) from feature/templates-for-desktop into release/v8.2.0
Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/build_tools/pulls/1
2024-09-18 09:52:59 +00:00
1b646a6e00 [desktop] correct path 2024-09-18 12:51:45 +03:00
cf970efbec [desktop] copy templates to package 2024-09-18 12:32:18 +03:00
4020cdac69 Merge branch hotfix/v8.1.3 into release/v8.2.0 2024-09-13 10:35:16 +00:00
2415c2ffe8 Merge branch hotfix/v8.1.3 into develop 2024-09-13 09:46:47 +00:00
d41502ea19 Merge branch hotfix/v8.1.3 into master 2024-09-13 09:46:46 +00:00
f5d0ef4005 Fix macos bug 2024-09-11 11:21:30 +03:00
c4a89ecf61 Fix update 2024-09-10 14:23:17 +03:00
71eb25e561 Remove unused files 2024-09-10 09:58:13 +03:00
486a6683fd Add dependencies for doctrenderer 2024-09-09 06:38:13 +03:00
2175d8d87c Update version 2024-09-05 10:38:13 +03:00
f463bff49e Fixes for working by ssh 2024-09-03 15:09:37 +03:00
a817e2b046 Update generate_docs_plugins_json.py
delete generating missing files with json
2024-09-01 00:34:25 +03:00
3539e36bde Update generate_docs_json.py
delete generate missing examples with json files
2024-09-01 00:32:50 +03:00
6930a9ffe1 Update generate_docs_md.py 2024-08-31 12:58:41 +03:00
e0a44502b1 Update generate_docs_md.py 2024-08-31 12:49:30 +03:00
19e1bd5586 Update generate_docs_md.py 2024-08-31 10:27:23 +03:00
ea65ba02f1 Update generate_docs_md.py 2024-08-31 10:23:46 +03:00
8406e48009 Update generate_docs_md.py 2024-08-31 10:14:28 +03:00
a8f1d11cbc Update generate_docs_md.py 2024-08-31 01:48:55 +03:00
f245a4a9c6 Update generate_docs_md.py 2024-08-31 01:40:00 +03:00
597529a16d Fix crossbuild 2024-08-30 10:49:09 +03:00
9b9dba05c2 Update generate_docs_plugins_json.py
delete branch from path
2024-08-30 09:36:10 +03:00
2d0bbc824f Update generate_docs_json.py
delete branch name from path
2024-08-30 09:33:34 +03:00
fa523c673f Update generate_docs_plugins_json.py
change default
2024-08-30 09:29:54 +03:00
da1a4ba393 Update README.md
change default
2024-08-30 09:29:44 +03:00
e9c9712e52 Update generate_docs_json.py
change default path
2024-08-30 09:25:34 +03:00
78561ca659 Update README.md 2024-08-30 09:25:23 +03:00
1ad87383e3 Update README.md
fix Requirements and Installation
2024-08-30 08:31:45 +03:00
c29ac1549f Update README.md
fix default path
2024-08-30 08:26:58 +03:00
f09eeb19e5 Fix generation on linux 2024-08-29 23:19:21 +03:00
4b7b2c78a2 Merge pull request #871 from ONLYOFFICE/fix/jsdoc
[jsdoc] Fixed style
2024-08-29 00:31:34 -07:00
414af6bdb0 [jsdoc] Fixed style 2024-08-29 14:27:47 +07:00
df7288b275 Merge pull request #868 from ONLYOFFICE/feature/extend-apijs-load
[web-apps] copy api.js as api.js.tpl for server package
2024-08-28 14:39:25 +03:00
ce80953086 Merge pull request #870 from ONLYOFFICE/fix/jsdoc
[jsdoc] Fixed creating data types.
2024-08-28 04:05:29 -07:00
d1344dab71 [jsdoc] Fixed creating data types. 2024-08-28 18:00:53 +07:00
4f2ba4ae76 Merge pull request #869 from ONLYOFFICE/fix/jsdocs
[jsdoc] Fixed output path
2024-08-28 03:32:34 -07:00
6bd525c3b4 [jsdoc] Fixed output path 2024-08-28 17:29:12 +07:00
341671a612 Fix typo 2024-08-28 13:10:29 +03:00
9161aa1556 Add generate snapshots 2024-08-28 13:06:12 +03:00
70e9fbabce [web-apps] copy api.js as api.js.tpl for server package 2024-08-27 21:45:15 +03:00
a2c00deba2 Add function for portable test utilities 2024-08-26 14:20:14 +03:00
9b4ef9d1d7 [web-apps] rename check translation script 2024-08-23 12:52:28 +03:00
3baee0c14e Move pro to builder module 2024-08-22 14:07:05 +03:00
0508bf43d1 Merge pull request #867 from ONLYOFFICE/feature/webapps-fix-translations
[web-apps] check translation for 'main' apps before build
2024-08-20 22:15:14 +03:00
bd279d1ad7 Merge pull request #861 from ONLYOFFICE/feature/docbuilder-java
Build and deploy docbuilder Java wrapper
2024-08-19 04:51:08 -07:00
4d55a66307 Merge pull request #865 from ONLYOFFICE/fix/jsdoc
[jsdoc] Replacing line breaks with spaces
2024-08-16 06:09:23 -07:00
9481e01581 [jsdoc] Replacing line breaks with spaces 2024-08-16 20:04:51 +07:00
fe91bf9620 Merge pull request #864 from ONLYOFFICE/fix/jsdocs
[jsdoc] Fixed paths generation
2024-08-16 05:30:20 -07:00
d812ba379b [jsdoc] Fixed paths generation 2024-08-16 19:28:56 +07:00
e1cc7f3c83 Fix libraries not loading on mac 2024-08-14 19:24:34 +04:00
f50d5d2cd1 Fix path problems on mac 2024-08-14 15:54:07 +04:00
b3987b0ad5 Move build of Java wrapper to build_sln.py 2024-08-13 20:42:54 +04:00
243946a189 Merge pull request #863 from ONLYOFFICE/release/v8.2.0
Merge branch release/v8.2.0 into develop
2024-08-13 07:46:12 -07:00
63fbbc5603 Add missed library to deploy 2024-08-13 17:26:14 +03:00
fcb857df69 [web-apps] check translation for 'main' apps before build 2024-08-13 11:19:18 +03:00
dabbc31c09 Handling complex dependencies in project file 2024-08-13 10:38:33 +03:00
997bfa3dd5 Fix typo 2024-08-13 09:14:23 +03:00
50eca8aab5 Fix build 2024-08-13 07:45:29 +03:00
6e4a2e4d5e Add dictionariestester to core deploy 2024-08-13 00:25:56 +03:00
40e9938885 Add test for dicts & spellmodule to core 2024-08-13 00:25:06 +03:00
5bc8ca2266 Build and deploy JAR 2024-08-12 18:42:18 +04:00
4cdbfbfb86 Deploy JNI helper dynamic library 2024-08-12 18:24:51 +04:00
01575d1f2e Fix core and builder archive deploy (#860)
* Refactoring script parameters

* Add builder 7z deploy

* Refactoring core 7z deploy

* Small fix
2024-08-12 16:53:56 +03:00
8f75c75b80 Merge pull request #859 from ONLYOFFICE/fix/builder-docs
[jsdoc][bu] Removed example filed from json docs
2024-08-07 04:24:42 -07:00
ebc084f9ea [jsdoc][bu] Removed example filed from json docs 2024-08-07 18:24:02 +07:00
626efaf5cf Merge pull request #858 from ONLYOFFICE/fix/jsdoc
[jsdoc][plugins] Added examples field to json.
2024-08-07 03:50:29 -07:00
096ce99588 [jsdoc][plugins] Added examples field to json. 2024-08-07 17:45:40 +07:00
9ce103b31b Add returncode in runcommand function 2024-08-06 17:54:39 +03:00
13cbd84b58 Change documentType for pdf 2024-08-06 14:24:12 +03:00
a8912dff41 Refactoring 2024-08-06 13:46:42 +03:00
8b773614ba Fix builder rpm package deploy (#857) 2024-08-06 11:45:54 +03:00
d04f04f382 Merge pull request #856 from ONLYOFFICE/fix/plugins-docs
Jsdocs api plugins generation script
2024-08-05 03:35:02 -07:00
9a44dae4f9 Jsdocs api plugins generation script 2024-08-05 17:28:06 +07:00
07665dd93e Merge pull request #855 from ONLYOFFICE/fix/jsdoc
Fixed jsdoc md generation
2024-08-02 07:35:32 -07:00
eeca17e78b Fixed jsdoc md generation 2024-08-02 21:30:29 +07:00
f91264bc94 Merge pull request #854 from ONLYOFFICE/fix/docs-generation
[jsdoc] Fixed api docs generation
2024-08-02 06:34:17 -07:00
10b7f63f9f Merge branch hotfix/v8.1.1 into develop 2024-07-26 08:02:44 +00:00
f2dff2d173 Merge branch hotfix/v8.1.1 into master 2024-07-26 08:02:40 +00:00
aa49605ac4 Merge branch hotfix/v8.1.1 into master 2024-07-15 11:38:25 +00:00
66 changed files with 3582 additions and 708 deletions

View File

@ -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"]

View File

@ -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")

View File

@ -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()

View File

@ -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()

View File

@ -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:
@ -418,6 +428,13 @@ def cmd_in_dir(directory, prog, args=[], is_no_errors=False):
os.chdir(cur_dir)
return ret
def cmd_in_dir_qemu(platform, directory, prog, args=[], is_no_errors=False):
if (platform == "linux_arm64"):
return cmd_in_dir(directory, "qemu-aarch64", ["-L", "/usr/aarch64-linux-gnu", prog] + args, is_no_errors)
if (platform == "linux_arm32"):
return cmd_in_dir(directory, "qemu-arm", ["-L", "/usr/arm-linux-gnueabi", prog] + args, is_no_errors)
return 0
def cmd_and_return_cwd(prog, args=[], is_no_errors=False):
cur_dir = os.getcwd()
ret = cmd(prog, args, is_no_errors)
@ -426,12 +443,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 +514,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 +577,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 +615,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 +783,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 +1233,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 +1253,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 +1270,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 +1287,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 +1323,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 +1379,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 +1445,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 +1807,50 @@ 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, platform):
if is_file(dir + "/libdoctrenderer.dylib") and (os.path.getsize(dir + "/libdoctrenderer.dylib") < 5*1024*1024):
return
if ((platform == "linux_arm64") and not is_os_arm()):
cmd_in_dir_qemu(platform, dir, "./x2t", ["-create-js-snapshots"], True)
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

View File

@ -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")

View File

@ -35,15 +35,41 @@ def make(solution=""):
qmake.make(platform, pro, "xcframework_platform_ios_simulator")
if config.check_option("module", "builder") and base.is_windows() and "onlyoffice" == config.branding():
# check branding libs
if (config.option("branding-name") == "onlyoffice"):
for platform in platforms:
if not platform in config.platforms:
continue
core_lib_unbranding_dir = os.getcwd() + "/../core/build/lib/" + platform + base.qt_dst_postfix()
if not base.is_dir(core_lib_unbranding_dir):
base.create_dir(core_lib_unbranding_dir)
core_lib_branding_dir = os.getcwd() + "/../core/build/onlyoffice/lib/" + platform + base.qt_dst_postfix()
base.copy_file(core_lib_branding_dir + "/doctrenderer.dll", core_lib_unbranding_dir + "/doctrenderer.dll")
base.copy_file(core_lib_branding_dir + "/doctrenderer.lib", core_lib_unbranding_dir + "/doctrenderer.lib")
# 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

View File

@ -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()

View File

@ -89,10 +89,15 @@ def make():
correct_install_includes_win(base_dir, "win_32")
if config.check_option("platform", "linux_64") and not base.is_dir("../build/linux_64"):
base.cmd("./bootstrap.sh", ["--with-libraries=filesystem,system,date_time,regex"])
addon_config = []
addon_compile = []
if "1" == config.option("use-clang"):
addon_config = ["--with-toolset=clang"]
addon_compile = ["cxxflags=-stdlib=libc++", "linkflags=-stdlib=libc++", "define=_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"]
base.cmd("./bootstrap.sh", ["--with-libraries=filesystem,system,date_time,regex"] + addon_config)
base.cmd("./b2", ["headers"])
base.cmd("./b2", ["--clean"])
base.cmd("./b2", ["--prefix=./../build/linux_64", "link=static", "cxxflags=-fPIC", "install"])
base.cmd("./b2", ["--prefix=./../build/linux_64", "link=static", "cxxflags=-fPIC"] + addon_compile + ["install"])
# TODO: support x86
if config.check_option("platform", "linux_arm64") and not base.is_dir("../build/linux_arm64"):

View File

@ -7,7 +7,27 @@ import base
import os
import subprocess
def clear_module():
directories = ["gumbo-parser", "katana-parser"]
for dir in directories:
if base.is_dir(dir):
base.delete_dir_with_access_error(dir)
def make():
old_cur_dir = os.getcwd()
print("[fetch]: html")
base_dir = base.get_script_dir() + "/../../core/Common/3dParty/html"
os.chdir(base_dir)
base.check_module_version("2", clear_module)
os.chdir(old_cur_dir)
base.cmd_in_dir(base_dir, "python", ["fetch.py"])
return
if __name__ == '__main__':
# manual compile
make()

View File

@ -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):

View File

@ -10,6 +10,8 @@ import glob
import icu_android
def fetch_icu(major, minor):
if (base.is_dir("./icu2")):
base.delete_dir_with_access_error("icu2")
base.cmd("git", ["clone", "--depth", "1", "--branch", "maint/maint-" + major, "https://github.com/unicode-org/icu.git", "./icu2"])
base.copy_dir("./icu2/icu4c", "./icu")
base.delete_dir_with_access_error("icu2")
@ -89,8 +91,13 @@ def make():
if not base.is_dir(base_dir + "/linux_64"):
base.create_dir(base_dir + "/icu/cross_build")
os.chdir("icu/cross_build")
base.cmd("./../source/runConfigureICU", ["Linux", "--prefix=" + base_dir + "/icu/cross_build_install"])
base.replaceInFile("./../source/icudefs.mk.in", "LDFLAGS = @LDFLAGS@ $(RPATHLDFLAGS)", "LDFLAGS = @LDFLAGS@ $(RPATHLDFLAGS) -static-libstdc++ -static-libgcc")
command_configure = "./../source/runConfigureICU"
command_compile_addon = "-static-libstdc++ -static-libgcc"
if "1" == config.option("use-clang"):
command_configure = "CXXFLAGS=-stdlib=libc++ " + command_configure
command_compile_addon = "-stdlib=libc++"
base.cmd(command_configure, ["Linux", "--prefix=" + base_dir + "/icu/cross_build_install"])
base.replaceInFile("./../source/icudefs.mk.in", "LDFLAGS = @LDFLAGS@ $(RPATHLDFLAGS)", "LDFLAGS = @LDFLAGS@ $(RPATHLDFLAGS) " + command_compile_addon)
base.cmd("make", ["-j4"])
base.cmd("make", ["install"], True)
base.create_dir(base_dir + "/linux_64")

View 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)

View File

@ -81,8 +81,16 @@ def make():
if (-1 != config.option("platform").find("linux")) and not base.is_dir("../build/linux_64"):
base.cmd("./config", ["enable-md2", "no-shared", "no-asm", "--prefix=" + old_cur_dir + "/build/linux_64", "--openssldir=" + old_cur_dir + "/build/linux_64"])
base.replaceInFile("./Makefile", "CFLAGS=-Wall -O3", "CFLAGS=-Wall -O3 -fvisibility=hidden")
base.replaceInFile("./Makefile", "CXXFLAGS=-Wall -O3", "CXXFLAGS=-Wall -O3 -fvisibility=hidden")
if "1" == config.option("use-clang"):
base.replaceInFile("./Makefile", "CC=$(CROSS_COMPILE)gcc", "CC=$(CROSS_COMPILE)clang")
base.replaceInFile("./Makefile", "CXX=$(CROSS_COMPILE)g++", "CXX=$(CROSS_COMPILE)clang++")
base.replaceInFile("./Makefile", "CFLAGS=-Wall -O3", "CFLAGS=-Wall -O3 -fvisibility=hidden")
base.replaceInFile("./Makefile", "CXXFLAGS=-Wall -O3", "CXXFLAGS=-Wall -O3 -fvisibility=hidden -stdlib=libc++")
base.replaceInFile("./Makefile", "LDFLAGS", "LDFLAGS=-stdlib=libc++")
else:
base.replaceInFile("./Makefile", "CFLAGS=-Wall -O3", "CFLAGS=-Wall -O3 -fvisibility=hidden")
base.replaceInFile("./Makefile", "CXXFLAGS=-Wall -O3", "CXXFLAGS=-Wall -O3 -fvisibility=hidden")
base.cmd("make")
base.cmd("make", ["install"])
base.cmd("make", ["clean"], True)

View File

@ -45,7 +45,7 @@ def is_use_clang():
gcc_version = base.get_gcc_version()
is_clang = "false"
if (gcc_version >= 6000):
if (gcc_version >= 6000 or "1" == config.option("use-clang")):
is_clang = "true"
print("gcc version: " + str(gcc_version) + ", use clang:" + is_clang)
@ -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

View File

@ -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
@ -46,12 +52,20 @@ def make_args(args, platform, is_64=True, is_debug=False):
else:
args_copy.append("is_debug=false")
linux_clang = False
if (platform == "linux"):
args_copy.append("is_clang=true")
args_copy.append("use_sysroot=false")
if "1" == config.option("use-clang"):
args_copy.append("use_sysroot=true")
linux_clang = True
else:
args_copy.append("use_sysroot=false")
if (platform == "windows"):
args_copy.append("is_clang=false")
if linux_clang != True:
args_copy.append("use_custom_libcxx=false")
return "--args=\"" + " ".join(args_copy) + "\""
def ninja_windows_make(args, is_64=True, is_debug=False):
@ -105,6 +119,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 +138,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 +154,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")
@ -147,7 +169,6 @@ def make():
"is_component_build=false",
"v8_monolithic=true",
"v8_use_external_startup_data=false",
"use_custom_libcxx=false",
"treat_warnings_as_errors=false"]
if config.check_option("platform", "linux_64"):

View File

@ -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,25 @@ 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", platform)
# 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"))
base.delete_dir(root_dir + "/sdkjs/slide/themes")
base.delete_dir(root_dir + "/sdkjs/cell/css")
base.delete_file(root_dir + "/sdkjs/pdf/src/engine/viewer.js")
base.delete_file(root_dir + "/sdkjs/common/spell/spell/spell.js.mem")
base.delete_dir(root_dir + "/sdkjs/common/Images")
return

View File

@ -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", platform)
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

View File

@ -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", platform)
if (0 == platform.find("win")):
base.delete_file(root_dir + "/cef_sandbox.lib")
base.delete_file(root_dir + "/libcef.lib")

View File

@ -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")

View File

@ -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", platform)
# 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"])

View File

@ -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("-----------------------------------------------------------")

View File

@ -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')

View File

@ -6,6 +6,7 @@ import base
import shutil
import optparse
import dependence
import config
arguments = sys.argv[1:]
@ -17,6 +18,10 @@ parser.add_option("--remove-path", action="append", type="string", dest="remove-
(options, args) = parser.parse_args(arguments)
configOptions = vars(options)
# parse configuration
config.parse()
config.parse_defaults()
for item in configOptions["uninstall"]:
dependence.uninstallProgram(item)
for item in configOptions["remove-path"]:

View File

@ -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",

32
scripts/min.py Normal file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env python
import sys
sys.path.append('../../build_tools/scripts')
import base
import os
args = sys.argv[1:]
if (1 > len(args)):
print("Please use min.py PATH_TO_SCRIPT.js")
exit(0)
script_path = args[0]
script_path = os.path.abspath(script_path)
script_dir = os.path.dirname(script_path)
script_name = os.path.splitext(os.path.basename(script_path))[0]
script_path_min = os.path.join(script_dir, script_name + ".min.js")
#compilation_level = "WHITESPACE_ONLY"
compilation_level = "SIMPLE_OPTIMIZATIONS"
base.cmd("java", ["-jar", "../../sdkjs/build/node_modules/google-closure-compiler-java/compiler.jar",
"--compilation_level", compilation_level,
"--js_output_file", script_path_min,
"--js", script_path])
dev_content = base.readFile(script_path)
license = dev_content[0:dev_content.find("*/")+2]
min_content = base.readFile(script_path_min)
base.delete_file(script_path_min)
base.writeFile(script_path_min, license + "\n\n" + min_content)

View File

@ -43,7 +43,7 @@ if utils.is_linux():
},
{
"make": "rpm",
"src": "rpm/builddir/RPMS/*/*.rpm",
"src": "rpm/build/RPMS/*/*.rpm",
"dst": "builder/linux/rhel/"
}
]

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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,12 @@ 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")
if "1" == config.option("use-clang"):
build_params.append("-spec")
build_params.append("linux-clang-libc++")
base.cmd(qmake_app, build_params)
base.correct_makefile_after_qmake(platform, makefile)
if ("1" == config.option("clean")):
@ -100,7 +106,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 +129,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)

View 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

View File

@ -82,6 +82,11 @@ exports.handlers = {
doclet.longname = cleanName(doclet.longname);
doclet.name = cleanName(doclet.name);
// skip inherited methods if ovveriden in child class
if (doclet.inherited && filteredDoclets.find((addedDoclet) => addedDoclet['name'] == doclet['name'] && addedDoclet['memberof'] == doclet['memberof'])) {
continue;
}
const filteredDoclet = {
comment: doclet.comment,
description: doclet.description,

View 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
}
}
}

View 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
}
}
}

View 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);
}
};

View 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
}
}
}

View 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
}
}
}

View 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
}
}
}

View File

@ -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)

View File

@ -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('<', '&lt;')
.replace('>', '&gt;')
.replace('{', '&#123;')
.replace('}', '&#125;')
)
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 = ' &#124;'.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 + ".&lt;" + ", ".join(generic_args) + "&gt;"
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"&lt;{element}&gt;"
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")

View 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('<', '&lt;')
.replace('>', '&gt;')
.replace('{', '&#123;')
.replace('}', '&#125;')
)
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 + ".&lt;" + ", ".join(generic_args) + "&gt;"
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"&lt;{element}&gt;"
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")

View 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)

View 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('<', '&lt;')
.replace('>', '&gt;')
.replace('{', '&#123;')
.replace('}', '&#125;')
)
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 + ".&lt;" + ", ".join(generic_args) + "&gt;"
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"&lt;{element}&gt;"
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")

View 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('<', '&lt;')
.replace('>', '&gt;')
.replace('{', '&#123;')
.replace('}', '&#125;')
)
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 + ".&lt;" + ", ".join(generic_args) + "&gt;"
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"&lt;{element}&gt;"
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")

View File

@ -0,0 +1,245 @@
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"
]
editors_names = {
"word": "Word",
"cell": "Spreadsheet",
"slide": "Presentation",
"forms": "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):
system_message = f'You are an expert in API for library from OnlyOffice. The library provides functions for editing {editors_names[editor_name]} documents. Your functional capabilities: 1) Explanation of Onlyoffice JavaScript API classes and their methods and parameters. 2) Assistance in writing Onlyoffice JavaScript API examples upon user request. 3) Reviewing user examples, assisting in finding and fixing their mistakes.'
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
default_user_message = f"How do I use the method {method_name} of {memberof} class?"
elif kind == "class":
class_name = doclet.get("name", "")
default_user_message = f"How do I instantiate or work with the class {class_name}?"
elif kind == "typedef":
typedef_name = doclet.get("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")

View 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

View File

@ -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 = []
@ -159,6 +164,18 @@ def get_projects(pro_json_path, platform):
if is_append:
result.append(root_dir + record)
# delete duplicates
old_results = result
result = []
map_results = set()
for item in old_results:
proj = item.replace("\\", "/")
if proj in map_results:
continue
map_results.add(proj)
result.append(proj)
if is_log:
print(result)
return result

View File

@ -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" : [

View File

@ -1,4 +0,0 @@
{
"browser" : "chrome",
"browserUrl" : "C:/Program Files/Google/Chrome/Application/chrome.exe"
}

View File

@ -1,4 +0,0 @@
{
"browser" : "firefox",
"browserUrl" : "C:/Program Files/Mozilla Firefox/firefox.exe"
}

View File

@ -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"])

View File

@ -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)

View 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);
}

View File

@ -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);

View 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)

View File

@ -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")

View File

@ -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()

View File

@ -1 +1 @@
8.1.1
8.3.3