chanda 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chanda/__init__.py +46 -0
- chanda/cli.py +311 -0
- chanda/core.py +980 -0
- chanda/data/chanda_ardhasama.csv +17 -0
- chanda/data/chanda_jaati.csv +28 -0
- chanda/data/chanda_matra.csv +10 -0
- chanda/data/chanda_sama.csv +348 -0
- chanda/data/chanda_vishama.csv +36 -0
- chanda/data/examples.json +8 -0
- chanda/exceptions.py +28 -0
- chanda/utils.py +200 -0
- chanda-0.1.0.dist-info/METADATA +283 -0
- chanda-0.1.0.dist-info/RECORD +17 -0
- chanda-0.1.0.dist-info/WHEEL +5 -0
- chanda-0.1.0.dist-info/entry_points.txt +2 -0
- chanda-0.1.0.dist-info/licenses/LICENSE +679 -0
- chanda-0.1.0.dist-info/top_level.txt +1 -0
chanda/__init__.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chandojñānam: Sanskrit Meter Identification Library
|
|
3
|
+
===================================================
|
|
4
|
+
|
|
5
|
+
A comprehensive Python library for identifying and analyzing Sanskrit poetic meters (chanda/छन्द).
|
|
6
|
+
|
|
7
|
+
Basic Usage:
|
|
8
|
+
>>> from chanda import identify_meter
|
|
9
|
+
>>> result = identify_meter("इक्ष्वाकुवंशप्रभवो रामो नाम जनैः श्रुतः")
|
|
10
|
+
>>> print(result['display_chanda'])
|
|
11
|
+
'Anuṣṭup'
|
|
12
|
+
|
|
13
|
+
Main Components:
|
|
14
|
+
- Chanda: Core meter identification and analysis class
|
|
15
|
+
- identify_meter: Convenient function for quick meter identification
|
|
16
|
+
- analyze_text: Multi-line and verse analysis
|
|
17
|
+
|
|
18
|
+
Supported Features:
|
|
19
|
+
- 200+ Sanskrit meters (Sama, Ardhasama, Vishama, Matra-based)
|
|
20
|
+
- Exact and fuzzy pattern matching
|
|
21
|
+
- Multi-script support (15+ schemes: Devanagari, IAST, ITRANS, etc.)
|
|
22
|
+
- Syllable-level scansion (Laghu-Guru marking)
|
|
23
|
+
- Gana notation conversion
|
|
24
|
+
- Verse-level analysis
|
|
25
|
+
- Matra (morae) counting for matra-based meters
|
|
26
|
+
|
|
27
|
+
Author: Hrishikesh Terdalkar
|
|
28
|
+
License: AGPL-3.0-or-later
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
__version__ = "0.1.0"
|
|
32
|
+
__author__ = "Hrishikesh Terdalkar"
|
|
33
|
+
|
|
34
|
+
from .core import Chanda
|
|
35
|
+
from .utils import identify_meter, analyze_text, format_result
|
|
36
|
+
from .exceptions import ChandaError, InvalidInputError, MeterNotFoundError
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
'Chanda',
|
|
40
|
+
'identify_meter',
|
|
41
|
+
'analyze_text',
|
|
42
|
+
'format_result',
|
|
43
|
+
'ChandaError',
|
|
44
|
+
'InvalidInputError',
|
|
45
|
+
'MeterNotFoundError',
|
|
46
|
+
]
|
chanda/cli.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Command-line interface for Chandojñānam.
|
|
5
|
+
|
|
6
|
+
This module provides a CLI tool for identifying Sanskrit meters from the command line.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import sys
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from .core import Chanda
|
|
16
|
+
from .utils import identify_meter, analyze_text, format_result, get_default_data_path, get_supported_meters
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
"""Main entry point for the CLI."""
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
prog='chanda',
|
|
23
|
+
description='Sanskrit Meter Identification Tool',
|
|
24
|
+
epilog='For more information, visit: https://github.com/hrishikeshrt/chanda'
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Input options
|
|
28
|
+
input_group = parser.add_mutually_exclusive_group(required=False)
|
|
29
|
+
input_group.add_argument(
|
|
30
|
+
'text',
|
|
31
|
+
nargs='?',
|
|
32
|
+
help='Sanskrit text to analyze (single line or verse)'
|
|
33
|
+
)
|
|
34
|
+
input_group.add_argument(
|
|
35
|
+
'-f', '--file',
|
|
36
|
+
type=str,
|
|
37
|
+
metavar='FILE',
|
|
38
|
+
help='Input file containing Sanskrit text'
|
|
39
|
+
)
|
|
40
|
+
input_group.add_argument(
|
|
41
|
+
'-i', '--interactive',
|
|
42
|
+
action='store_true',
|
|
43
|
+
help='Interactive mode - read from stdin'
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Analysis options
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
'-v', '--verse',
|
|
49
|
+
action='store_true',
|
|
50
|
+
help='Verse mode: group lines into 4-line verses'
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
'--no-fuzzy',
|
|
54
|
+
action='store_true',
|
|
55
|
+
help='Disable fuzzy matching (only exact matches)'
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
'-k', '--top-k',
|
|
59
|
+
type=int,
|
|
60
|
+
default=10,
|
|
61
|
+
metavar='K',
|
|
62
|
+
help='Number of fuzzy matches to show (default: 10)'
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Output options
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
'-o', '--output',
|
|
68
|
+
type=str,
|
|
69
|
+
metavar='FILE',
|
|
70
|
+
help='Output file (default: stdout)'
|
|
71
|
+
)
|
|
72
|
+
parser.add_argument(
|
|
73
|
+
'--format',
|
|
74
|
+
choices=['text', 'json', 'simple'],
|
|
75
|
+
default='text',
|
|
76
|
+
help='Output format (default: text)'
|
|
77
|
+
)
|
|
78
|
+
parser.add_argument(
|
|
79
|
+
'-s', '--scheme',
|
|
80
|
+
type=str,
|
|
81
|
+
choices=['devanagari', 'iast', 'itrans', 'hk', 'slp1', 'wx', 'velthuis'],
|
|
82
|
+
help='Output transliteration scheme'
|
|
83
|
+
)
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
'--summary',
|
|
86
|
+
action='store_true',
|
|
87
|
+
help='Show summary statistics'
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Data options
|
|
91
|
+
parser.add_argument(
|
|
92
|
+
'--data-path',
|
|
93
|
+
type=str,
|
|
94
|
+
metavar='PATH',
|
|
95
|
+
help='Path to meter definition data directory'
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Info options
|
|
99
|
+
parser.add_argument(
|
|
100
|
+
'--list-meters',
|
|
101
|
+
action='store_true',
|
|
102
|
+
help='List all supported meters and exit'
|
|
103
|
+
)
|
|
104
|
+
parser.add_argument(
|
|
105
|
+
'--version',
|
|
106
|
+
action='version',
|
|
107
|
+
version='%(prog)s 1.0.0'
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
args = parser.parse_args()
|
|
111
|
+
|
|
112
|
+
# Handle list-meters option
|
|
113
|
+
if args.list_meters:
|
|
114
|
+
show_meter_list(args.data_path)
|
|
115
|
+
return 0
|
|
116
|
+
|
|
117
|
+
# Get input text
|
|
118
|
+
text = get_input_text(args)
|
|
119
|
+
if not text:
|
|
120
|
+
parser.print_help()
|
|
121
|
+
return 1
|
|
122
|
+
|
|
123
|
+
# Perform analysis
|
|
124
|
+
try:
|
|
125
|
+
results = perform_analysis(text, args)
|
|
126
|
+
output_results(results, args)
|
|
127
|
+
return 0
|
|
128
|
+
except Exception as e:
|
|
129
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
130
|
+
return 1
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_input_text(args) -> Optional[str]:
|
|
134
|
+
"""Get input text from command line arguments."""
|
|
135
|
+
if args.text:
|
|
136
|
+
return args.text
|
|
137
|
+
elif args.file:
|
|
138
|
+
with open(args.file, 'r', encoding='utf-8') as f:
|
|
139
|
+
return f.read()
|
|
140
|
+
elif args.interactive or not sys.stdin.isatty():
|
|
141
|
+
return sys.stdin.read()
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def perform_analysis(text: str, args):
|
|
146
|
+
"""Perform meter identification analysis."""
|
|
147
|
+
data_path = args.data_path or get_default_data_path()
|
|
148
|
+
fuzzy = not args.no_fuzzy
|
|
149
|
+
|
|
150
|
+
# Check if single line or multiple lines
|
|
151
|
+
lines = text.strip().split('\n')
|
|
152
|
+
if len(lines) == 1 and not args.verse:
|
|
153
|
+
# Single line analysis
|
|
154
|
+
result = identify_meter(
|
|
155
|
+
text,
|
|
156
|
+
fuzzy=fuzzy,
|
|
157
|
+
output_scheme=args.scheme,
|
|
158
|
+
data_path=data_path
|
|
159
|
+
)
|
|
160
|
+
return {'type': 'single', 'result': result}
|
|
161
|
+
else:
|
|
162
|
+
# Multi-line analysis
|
|
163
|
+
results = analyze_text(
|
|
164
|
+
text,
|
|
165
|
+
verse_mode=args.verse,
|
|
166
|
+
fuzzy=fuzzy,
|
|
167
|
+
output_scheme=args.scheme,
|
|
168
|
+
data_path=data_path
|
|
169
|
+
)
|
|
170
|
+
return {'type': 'multi', 'result': results}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def output_results(results, args):
|
|
174
|
+
"""Output analysis results in the specified format."""
|
|
175
|
+
output = format_output(results, args)
|
|
176
|
+
|
|
177
|
+
if args.output:
|
|
178
|
+
with open(args.output, 'w', encoding='utf-8') as f:
|
|
179
|
+
f.write(output)
|
|
180
|
+
else:
|
|
181
|
+
print(output)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def format_output(results, args) -> str:
|
|
185
|
+
"""Format results according to specified output format."""
|
|
186
|
+
if args.format == 'json':
|
|
187
|
+
return format_json_output(results, args)
|
|
188
|
+
elif args.format == 'simple':
|
|
189
|
+
return format_simple_output(results, args)
|
|
190
|
+
else: # text format
|
|
191
|
+
return format_text_output(results, args)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def format_json_output(results, args) -> str:
|
|
195
|
+
"""Format results as JSON."""
|
|
196
|
+
if results['type'] == 'single':
|
|
197
|
+
output_data = results['result']
|
|
198
|
+
else:
|
|
199
|
+
output_data = results['result']['result']
|
|
200
|
+
|
|
201
|
+
return json.dumps(output_data, ensure_ascii=False, indent=2)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def format_simple_output(results, args) -> str:
|
|
205
|
+
"""Format results in simple, concise format."""
|
|
206
|
+
output_lines = []
|
|
207
|
+
|
|
208
|
+
if results['type'] == 'single':
|
|
209
|
+
result = results['result']
|
|
210
|
+
if result.get('found'):
|
|
211
|
+
chanda = result.get('display_chanda', 'Unknown')
|
|
212
|
+
output_lines.append(f"{result['display_line']}")
|
|
213
|
+
output_lines.append(f"Meter: {chanda}")
|
|
214
|
+
output_lines.append(f"Pattern: {' '.join(result.get('display_lg', []))}")
|
|
215
|
+
else:
|
|
216
|
+
output_lines.append(f"{result['display_line']}")
|
|
217
|
+
output_lines.append("Meter: Not found")
|
|
218
|
+
if result.get('fuzzy'):
|
|
219
|
+
top = result['fuzzy'][0]
|
|
220
|
+
output_lines.append(f"Closest: {top['display_chanda']} ({top['similarity']:.2%})")
|
|
221
|
+
else:
|
|
222
|
+
line_results = results['result']['result']['line']
|
|
223
|
+
for line_res in line_results:
|
|
224
|
+
res = line_res['result']
|
|
225
|
+
line = res.get('display_line', '')
|
|
226
|
+
chanda = res.get('display_chanda', 'Unknown') if res.get('found') else 'Not found'
|
|
227
|
+
output_lines.append(f"{line} -> {chanda}")
|
|
228
|
+
|
|
229
|
+
return '\n'.join(output_lines)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def format_text_output(results, args) -> str:
|
|
233
|
+
"""Format results in detailed text format."""
|
|
234
|
+
output_lines = []
|
|
235
|
+
|
|
236
|
+
if results['type'] == 'single':
|
|
237
|
+
result = results['result']
|
|
238
|
+
output_lines.append(format_result(result))
|
|
239
|
+
else:
|
|
240
|
+
line_results = results['result']['result']['line']
|
|
241
|
+
verse_results = results['result']['result'].get('verse', [])
|
|
242
|
+
|
|
243
|
+
if verse_results:
|
|
244
|
+
# Verse mode output
|
|
245
|
+
for verse_idx, verse in enumerate(verse_results, 1):
|
|
246
|
+
output_lines.append(f"{'='*80}")
|
|
247
|
+
output_lines.append(f"Verse {verse_idx}")
|
|
248
|
+
output_lines.append(f"{'='*80}")
|
|
249
|
+
if verse.get('chanda'):
|
|
250
|
+
best_matches, score = verse['chanda']
|
|
251
|
+
output_lines.append(
|
|
252
|
+
f"Best meter(s): {' / '.join(best_matches)} (score: {score})"
|
|
253
|
+
)
|
|
254
|
+
else:
|
|
255
|
+
output_lines.append("Best meter(s): Not found")
|
|
256
|
+
output_lines.append("")
|
|
257
|
+
|
|
258
|
+
for line_idx in verse['lines']:
|
|
259
|
+
line_res = line_results[line_idx]
|
|
260
|
+
output_lines.append(format_result(line_res['result']))
|
|
261
|
+
output_lines.append("")
|
|
262
|
+
|
|
263
|
+
output_lines.append("")
|
|
264
|
+
else:
|
|
265
|
+
# Line mode output
|
|
266
|
+
for idx, line_res in enumerate(line_results, 1):
|
|
267
|
+
output_lines.append(f"Line {idx}:")
|
|
268
|
+
output_lines.append(format_result(line_res['result']))
|
|
269
|
+
output_lines.append("")
|
|
270
|
+
|
|
271
|
+
# Add summary if requested
|
|
272
|
+
if args.summary and len(line_results) > 1:
|
|
273
|
+
output_lines.append(f"{'='*80}")
|
|
274
|
+
output_lines.append("SUMMARY")
|
|
275
|
+
output_lines.append(f"{'='*80}")
|
|
276
|
+
from .core import Chanda
|
|
277
|
+
summary = Chanda.format_summary(
|
|
278
|
+
Chanda(args.data_path or get_default_data_path()).summarize_results(
|
|
279
|
+
results['result']['result']
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
output_lines.append(summary)
|
|
283
|
+
|
|
284
|
+
return '\n'.join(output_lines)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def show_meter_list(data_path: Optional[str] = None):
|
|
288
|
+
"""Display list of all supported meters."""
|
|
289
|
+
meters = get_supported_meters(data_path)
|
|
290
|
+
|
|
291
|
+
print("Chandojñānam - Supported Sanskrit Meters")
|
|
292
|
+
print("=" * 80)
|
|
293
|
+
print(f"Total meters: {meters['total']}")
|
|
294
|
+
print(f" - Sama-vṛtta (same pattern in all padas): {meters['sama']}")
|
|
295
|
+
print(f" - Ardhasama/Viṣama-vṛtta (varying patterns): {meters['ardhasama_vishama']}")
|
|
296
|
+
print(f" - Mātrā-vṛtta (matra-based): {meters['matra']}")
|
|
297
|
+
print()
|
|
298
|
+
print("Examples of popular meters:")
|
|
299
|
+
print(" - Anuṣṭup (श्लोक)")
|
|
300
|
+
print(" - Indravajrā")
|
|
301
|
+
print(" - Upendravajrā")
|
|
302
|
+
print(" - Vasantatilakā")
|
|
303
|
+
print(" - Mālinī")
|
|
304
|
+
print(" - Śārdūlavikrīḍita")
|
|
305
|
+
print(" - Āryā (मात्रा-वृत्त)")
|
|
306
|
+
print()
|
|
307
|
+
print("Use --help for more information on usage.")
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
if __name__ == '__main__':
|
|
311
|
+
sys.exit(main())
|