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