Merge pull request #856 from ONLYOFFICE/fix/plugins-docs

Jsdocs api plugins generation script
This commit is contained in:
Oleg Korshul
2024-08-05 03:35:02 -07:00
committed by GitHub
14 changed files with 326 additions and 10 deletions

View File

@ -1,7 +1,7 @@
# 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
@ -24,7 +24,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 `Onlyoffice/document-builder-declarations/document-builder`.
### `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 `Onlyoffice/document-builder-declarations/document-builder-plugin`.
### `generate_docs_md.py`
@ -45,6 +57,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

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

@ -4,12 +4,14 @@ import json
import argparse
import re
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 = {
@ -55,7 +57,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:
@ -85,7 +87,7 @@ def generate(output_dir, md=False):
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
@ -125,11 +127,11 @@ 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}/document-builder-declarations/document-builder" # Default value
)
args = parser.parse_args()
branch_name = get_current_branch("../../../../sdkjs")
branch_name = get_current_branch(f"{root}/sdkjs")
if branch_name:
args.destination = f"{args.destination}/{branch_name}"

View File

@ -0,0 +1,132 @@
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):
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"npx jsdoc -c {config} -X > {output_file}"
print(f"Generating {editor_name}.json: {command}")
subprocess.run(command, shell=True)
# Append examples to JSON documentation
for config in configs:
editor_name = config.split('/')[-1].replace('.json', '')
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)
# Modify JSON data
for doclet in data:
if 'see' in doclet:
if doclet['see'] is not None:
if editor_name == 'forms':
doclet['see'][0] = doclet['see'][0].replace('{Editor}', 'Word')
else:
doclet['see'][0] = doclet['see'][0].replace('{Editor}', editor_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
# Format content for doclet['example']
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")
# 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()
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")
parser.add_argument(
"destination",
type=str,
help="Destination directory for the generated documentation",
nargs='?', # Indicates the argument is optional
default=f"{root}/document-builder-declarations/document-builder-plugin" # Default value
)
args = parser.parse_args()
branch_name = get_current_branch(f"{root}/sdkjs")
if branch_name:
args.destination = f"{args.destination}/{branch_name}"
generate(args.destination)