Skip to content

Commit 61bb1ce

Browse files
committed
feat: Implement JSDoc parser and composer
- Add parser module to parse JSDoc strings into structured dictionaries. - Add composer module to compose JSDoc strings from structured dictionaries. - Introduce utility functions for extracting type information, merging JSDoc objects, and removing components from JSDoc objects. - Create a command-line interface for parsing and composing JSDoc strings. - Add tests for parser, composer, utilities, and integration to ensure functionality and correctness. - Initialize package with pyproject.toml for packaging and distribution.
0 parents  commit 61bb1ce

File tree

12 files changed

+1247
-0
lines changed

12 files changed

+1247
-0
lines changed

Pipfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[[source]]
2+
url = "https://pypi.org/simple"
3+
verify_ssl = true
4+
name = "pypi"
5+
6+
[packages]
7+
8+
[dev-packages]
9+
10+
[requires]
11+
python_version = "3.11"

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# JSDoc Parser
2+
3+
A Python library for parsing and composing JSDoc strings.
4+
5+
## Installation
6+
7+
```bash
8+
pip install jsdoc-parser
9+
```
10+
11+
## Usage
12+
13+
```python
14+
from jsdoc_parser import parse_jsdoc, compose_jsdoc
15+
16+
# Parse a JSDoc string into an object
17+
jsdoc_str = """/**
18+
* Calculates the sum of two numbers
19+
* @param {number} a - First number
20+
* @param {number} b - Second number
21+
* @returns {number} The sum of a and b
22+
*/"""
23+
24+
parsed = parse_jsdoc(jsdoc_str)
25+
print(parsed)
26+
# Output: {'description': 'Calculates the sum of two numbers', 'params': [{'name': 'a', 'type': 'number', 'description': 'First number'}, {'name': 'b', 'type': 'number', 'description': 'Second number'}], 'returns': {'type': 'number', 'description': 'The sum of a and b'}}
27+
28+
# Modify the parsed object
29+
parsed['params'][0]['description'] = 'Modified description'
30+
31+
# Compose the modified object back to a JSDoc string
32+
new_jsdoc = compose_jsdoc(parsed)
33+
print(new_jsdoc)
34+
```
35+
36+
## Features
37+
38+
- Parse JSDoc strings into structured Python objects
39+
- Compose JSDoc objects back into properly formatted JSDoc strings
40+
- Support for various JSDoc tags: @param, @returns, @throws, etc.
41+
- Easy manipulation of JSDoc components
42+
43+
## Contributing
44+
45+
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
46+
47+
Please make sure to update tests as appropriate.
48+
49+
## License
50+
51+
[MIT](https://choosealicense.com/licenses/mit/)

docstring_parser/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""JSDoc parser package initialization."""
2+
3+
from docstring_parser.parser import parse_jsdoc
4+
from docstring_parser.composer import compose_jsdoc
5+
from docstring_parser.utils import (
6+
extract_type_info,
7+
merge_jsdoc_objects,
8+
remove_jsdoc_component
9+
)
10+
11+
__all__ = [
12+
'parse_jsdoc',
13+
'compose_jsdoc',
14+
'extract_type_info',
15+
'merge_jsdoc_objects',
16+
'remove_jsdoc_component'
17+
]

docstring_parser/cli.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env python
2+
"""
3+
Command-line interface for the JSDoc parser library.
4+
5+
This script provides a simple command-line interface for parsing and composing JSDoc strings.
6+
"""
7+
8+
import argparse
9+
import json
10+
import sys
11+
from docstring_parser.parser import parse_jsdoc
12+
from docstring_parser.composer import compose_jsdoc
13+
from docstring_parser.utils import remove_jsdoc_component
14+
15+
16+
def main():
17+
"""Entry point for the command-line interface."""
18+
parser = argparse.ArgumentParser(description='Parse and compose JSDoc strings')
19+
subparsers = parser.add_subparsers(dest='command', required=True, help='Command to execute')
20+
21+
# Parse command
22+
parse_parser = subparsers.add_parser('parse', help='Parse a JSDoc string into a JSON object')
23+
parse_parser.add_argument('file', type=str, nargs='?', help='File containing a JSDoc string (or use stdin)')
24+
parse_parser.add_argument('-o', '--output', type=str, help='Output file (default: stdout)')
25+
26+
# Compose command
27+
compose_parser = subparsers.add_parser('compose', help='Compose a JSDoc string from a JSON object')
28+
compose_parser.add_argument('file', type=str, nargs='?', help='JSON file containing a JSDoc object (or use stdin)')
29+
compose_parser.add_argument('-o', '--output', type=str, help='Output file (default: stdout)')
30+
31+
# Remove component command
32+
remove_parser = subparsers.add_parser('remove', help='Remove a component from a JSDoc object')
33+
remove_parser.add_argument('file', type=str, nargs='?', help='JSON file containing a JSDoc object (or use stdin)')
34+
remove_parser.add_argument('-t', '--type', type=str, required=True,
35+
choices=['description', 'param', 'returns', 'throws', 'example', 'tag'],
36+
help='Type of component to remove')
37+
remove_parser.add_argument('-i', '--identifier', type=str,
38+
help='Identifier of the component (e.g., param name, tag name)')
39+
remove_parser.add_argument('-o', '--output', type=str, help='Output file (default: stdout)')
40+
remove_parser.add_argument('-f', '--format', type=str, choices=['json', 'jsdoc'], default='json',
41+
help='Output format: json or jsdoc (default: json)')
42+
43+
args = parser.parse_args()
44+
45+
# Handle input
46+
input_data = ''
47+
if args.file:
48+
with open(args.file, 'r', encoding='utf-8') as f:
49+
input_data = f.read()
50+
else:
51+
input_data = sys.stdin.read()
52+
53+
# Process commands
54+
if args.command == 'parse':
55+
try:
56+
result = parse_jsdoc(input_data)
57+
output = json.dumps(result, indent=2)
58+
except Exception as e:
59+
print(f"Error parsing JSDoc: {str(e)}", file=sys.stderr)
60+
sys.exit(1)
61+
62+
elif args.command == 'compose':
63+
try:
64+
jsdoc_obj = json.loads(input_data)
65+
output = compose_jsdoc(jsdoc_obj)
66+
except json.JSONDecodeError:
67+
print("Error: Input is not valid JSON", file=sys.stderr)
68+
sys.exit(1)
69+
except Exception as e:
70+
print(f"Error composing JSDoc: {str(e)}", file=sys.stderr)
71+
sys.exit(1)
72+
73+
elif args.command == 'remove':
74+
try:
75+
jsdoc_obj = json.loads(input_data)
76+
result = remove_jsdoc_component(jsdoc_obj, args.type, args.identifier)
77+
78+
if args.format == 'json':
79+
output = json.dumps(result, indent=2)
80+
else: # jsdoc format
81+
output = compose_jsdoc(result)
82+
except json.JSONDecodeError:
83+
print("Error: Input is not valid JSON", file=sys.stderr)
84+
sys.exit(1)
85+
except Exception as e:
86+
print(f"Error removing component: {str(e)}", file=sys.stderr)
87+
sys.exit(1)
88+
89+
# Handle output
90+
if args.output:
91+
with open(args.output, 'w', encoding='utf-8') as f:
92+
f.write(output)
93+
else:
94+
print(output)
95+
96+
97+
if __name__ == '__main__':
98+
main()

docstring_parser/composer.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Composer module for JSDoc strings."""
2+
3+
from typing import Dict, List, Any, Union
4+
5+
6+
def compose_jsdoc(jsdoc_obj: Dict[str, Any]) -> str:
7+
"""
8+
Compose a JSDoc string from a structured dictionary.
9+
10+
Args:
11+
jsdoc_obj (Dict[str, Any]): The dictionary representing the JSDoc structure
12+
13+
Returns:
14+
str: The formatted JSDoc string
15+
16+
Example:
17+
>>> compose_jsdoc({'description': 'Description', 'params': [{'name': 'name', 'type': 'string', 'description': 'The name'}]})
18+
'/**\\n * Description\\n * @param {string} name - The name\\n */'
19+
"""
20+
lines = ['/**']
21+
22+
# Add the description
23+
if 'description' in jsdoc_obj and jsdoc_obj['description']:
24+
for line in jsdoc_obj['description'].split('\n'):
25+
lines.append(f' * {line}')
26+
lines.append(' *')
27+
28+
# Add the params
29+
if 'params' in jsdoc_obj:
30+
for param in jsdoc_obj['params']:
31+
param_str = ' * @param'
32+
33+
if 'type' in param and param['type']:
34+
param_str += f' {{{param["type"]}}}'
35+
36+
if 'name' in param and param['name']:
37+
param_str += f' {param["name"]}'
38+
39+
if 'description' in param and param['description']:
40+
param_str += f' - {param["description"]}'
41+
42+
lines.append(param_str)
43+
44+
# Add the returns
45+
if 'returns' in jsdoc_obj and jsdoc_obj['returns']:
46+
returns_str = ' * @returns'
47+
48+
if 'type' in jsdoc_obj['returns'] and jsdoc_obj['returns']['type']:
49+
returns_str += f' {{{jsdoc_obj["returns"]["type"]}}}'
50+
51+
if 'description' in jsdoc_obj['returns'] and jsdoc_obj['returns']['description']:
52+
returns_str += f' {jsdoc_obj["returns"]["description"]}'
53+
54+
lines.append(returns_str)
55+
56+
# Add the throws
57+
if 'throws' in jsdoc_obj:
58+
for throws in jsdoc_obj['throws']:
59+
throws_str = ' * @throws'
60+
61+
if 'type' in throws and throws['type']:
62+
throws_str += f' {{{throws["type"]}}}'
63+
64+
if 'description' in throws and throws['description']:
65+
throws_str += f' {throws["description"]}'
66+
67+
lines.append(throws_str)
68+
69+
# Add the examples
70+
if 'examples' in jsdoc_obj:
71+
for example in jsdoc_obj['examples']:
72+
lines.append(f' * @example {example}')
73+
74+
# Add other tags
75+
if 'tags' in jsdoc_obj:
76+
for tag, values in jsdoc_obj['tags'].items():
77+
for value in values:
78+
lines.append(f' * @{tag} {value}')
79+
80+
# Add the closing marker
81+
lines.append(' */')
82+
83+
# Return the composed JSDoc string
84+
return '\n'.join(lines)

0 commit comments

Comments
 (0)