gettextpo 0.1.0

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.
@@ -0,0 +1,945 @@
1
+ /**
2
+ * Copyright (C) 2026 gemmaro
3
+ *
4
+ * This program is free software: you can redistribute it and/or modify
5
+ * it under the terms of the GNU General Public License as published by
6
+ * the Free Software Foundation, either version 3 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ #include "gettextpo.h"
19
+ #include <gettext-po.h>
20
+ #include <ruby/internal/anyargs.h>
21
+ #include <ruby/internal/arithmetic/int.h>
22
+ #include <ruby/internal/core/rdata.h>
23
+ #include <ruby/internal/core/rstring.h>
24
+ #include <ruby/internal/core/rtypeddata.h>
25
+ #include <ruby/internal/eval.h>
26
+ #include <ruby/internal/globals.h>
27
+ #include <ruby/internal/intern/array.h>
28
+ #include <ruby/internal/intern/hash.h>
29
+ #include <ruby/internal/intern/object.h>
30
+ #include <ruby/internal/intern/proc.h>
31
+ #include <ruby/internal/intern/range.h>
32
+ #include <ruby/internal/intern/string.h>
33
+ #include <ruby/internal/intern/vm.h>
34
+ #include <ruby/internal/module.h>
35
+ #include <ruby/internal/scan_args.h>
36
+ #include <ruby/internal/special_consts.h>
37
+ #include <ruby/internal/symbol.h>
38
+ #include <ruby/internal/value.h>
39
+ #include <stdbool.h>
40
+ #include <stddef.h>
41
+ #include <stdlib.h>
42
+ #include <time.h>
43
+
44
+ VALUE rb_cMessage;
45
+ VALUE rb_cMessageIterator;
46
+ VALUE rb_cFilePos;
47
+ VALUE rb_eError;
48
+
49
+ /* ********** error ********** */
50
+
51
+ static struct
52
+ {
53
+ bool error;
54
+ VALUE *user_xerror;
55
+ VALUE *user_xerror2;
56
+ } gettextpo_xerror_context = {};
57
+
58
+ static void
59
+ gettextpo_xerror_context_reset (void)
60
+ {
61
+ gettextpo_xerror_context.error = false;
62
+ gettextpo_xerror_context.user_xerror = NULL;
63
+ gettextpo_xerror_context.user_xerror2 = NULL;
64
+ }
65
+
66
+ static void
67
+ gettextpo_xerror (const int severity, const po_message_t message,
68
+ const char *const filename, const size_t lineno,
69
+ const size_t column, const int multiline_p,
70
+ const char *const message_text)
71
+ {
72
+ gettextpo_xerror_context.error = true;
73
+ if (!gettextpo_xerror_context.user_xerror
74
+ || NIL_P (*gettextpo_xerror_context.user_xerror))
75
+ return;
76
+ VALUE kwargs = rb_hash_new ();
77
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("severity")), INT2NUM (severity));
78
+ if (message)
79
+ {
80
+ VALUE message_value = rb_obj_alloc (rb_cMessage);
81
+ DATA_PTR (message_value) = message;
82
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("message")), message_value);
83
+ }
84
+ if (filename)
85
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("filename")),
86
+ rb_str_new_cstr (filename));
87
+ if (filename && (lineno != (size_t)(-1)))
88
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("lineno")), INT2NUM (lineno));
89
+ if (filename && lineno != (size_t)(-1) && column != (size_t)(-1))
90
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("column")), INT2NUM (column));
91
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("multiline")),
92
+ multiline_p ? Qtrue : Qfalse);
93
+ if (message_text)
94
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("message_text")),
95
+ rb_str_new_cstr (message_text));
96
+ VALUE args = rb_ary_new ();
97
+ rb_ary_push (args, kwargs);
98
+ rb_proc_call_kw (*gettextpo_xerror_context.user_xerror, args,
99
+ RB_PASS_KEYWORDS);
100
+ if (severity == PO_SEVERITY_FATAL_ERROR)
101
+ abort ();
102
+ }
103
+
104
+ static void
105
+ gettextpo_xerror2 (const int severity, const po_message_t message1,
106
+ const char *const filename1, const size_t lineno1,
107
+ const size_t column1, const int multiline_p1,
108
+ const char *const message_text1,
109
+ const po_message_t message2, const char *const filename2,
110
+ const size_t lineno2, const size_t column2,
111
+ const int multiline_p2, const char *const message_text2)
112
+ {
113
+ gettextpo_xerror_context.error = true;
114
+ if (!gettextpo_xerror_context.user_xerror2
115
+ || NIL_P (*gettextpo_xerror_context.user_xerror2))
116
+ return;
117
+ VALUE kwargs = rb_hash_new ();
118
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("severity")), INT2NUM (severity));
119
+ if (message1)
120
+ {
121
+ VALUE message_value1 = rb_obj_alloc (rb_cMessage);
122
+ DATA_PTR (message_value1) = message1;
123
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("message1")), message_value1);
124
+ }
125
+ if (filename1)
126
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("filename1")),
127
+ rb_str_new_cstr (filename1));
128
+ if (filename1 && (lineno1 != (size_t)(-1)))
129
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("lineno1")), INT2NUM (lineno1));
130
+ if (filename1 && lineno1 != (size_t)(-1) && column1 != (size_t)(-1))
131
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("column1")), INT2NUM (column1));
132
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("multiline1")),
133
+ multiline_p1 ? Qtrue : Qfalse);
134
+ if (message_text1)
135
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("message_text1")),
136
+ rb_str_new_cstr (message_text1));
137
+ if (message2)
138
+ {
139
+ VALUE message_value2 = rb_obj_alloc (rb_cMessage);
140
+ DATA_PTR (message_value2) = message2;
141
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("message2")), message_value2);
142
+ }
143
+ if (filename2)
144
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("filename2")),
145
+ rb_str_new_cstr (filename2));
146
+ if (filename2 && (lineno2 != (size_t)(-1)))
147
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("lineno2")), INT2NUM (lineno2));
148
+ if (filename2 && lineno2 != (size_t)(-1) && column2 != (size_t)(-1))
149
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("column2")), INT2NUM (column2));
150
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("multiline2")),
151
+ multiline_p2 ? Qtrue : Qfalse);
152
+ if (message_text2)
153
+ rb_hash_aset (kwargs, ID2SYM (rb_intern ("message_text2")),
154
+ rb_str_new_cstr (message_text2));
155
+ VALUE args = rb_ary_new ();
156
+ rb_ary_push (args, kwargs);
157
+ rb_proc_call_kw (*gettextpo_xerror_context.user_xerror, args,
158
+ RB_PASS_KEYWORDS);
159
+ if (severity == PO_SEVERITY_FATAL_ERROR)
160
+ abort ();
161
+ }
162
+
163
+ static const struct po_xerror_handler gettextpo_xerror_handler = {
164
+ .xerror = gettextpo_xerror,
165
+ .xerror2 = gettextpo_xerror2,
166
+ };
167
+
168
+ /**
169
+ * Document-class: GettextPO::Message
170
+ */
171
+
172
+ static const rb_data_type_t gettextpo_po_message_type = {
173
+ .wrap_struct_name = "gettextpo PO message",
174
+ .function = {
175
+ .dfree = RUBY_TYPED_NEVER_FREE,
176
+ },
177
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
178
+ };
179
+
180
+ VALUE
181
+ gettextpo_po_message_alloc (VALUE self)
182
+ {
183
+ return TypedData_Wrap_Struct (self, &gettextpo_po_message_type, NULL);
184
+ }
185
+
186
+ #define OPTIONAL_STRING_GETTER(field) \
187
+ VALUE \
188
+ gettextpo_po_message_m_##field (VALUE self) \
189
+ { \
190
+ const char *string = po_message_##field (DATA_PTR (self)); \
191
+ return string ? rb_str_new_cstr (string) : Qnil; \
192
+ }
193
+ #define OPTIONAL_STRING_SETTER(field) \
194
+ VALUE \
195
+ gettextpo_po_message_m_##field##_set (VALUE self, VALUE string) \
196
+ { \
197
+ po_message_set_##field ( \
198
+ DATA_PTR (self), NIL_P (string) ? NULL : StringValueCStr (string)); \
199
+ return Qnil; \
200
+ }
201
+
202
+ /**
203
+ * Document-method: msgctxt
204
+ *
205
+ * Possibly returns +nil+.
206
+ */
207
+ OPTIONAL_STRING_GETTER (msgctxt);
208
+
209
+ /**
210
+ * Document-method: msgctxt=
211
+ * call-seq: msgctxt = context
212
+ *
213
+ * +context+ can be +nil+ to remove it.
214
+ */
215
+ OPTIONAL_STRING_SETTER (msgctxt);
216
+
217
+ #define STRING_GETTER(field) \
218
+ VALUE \
219
+ gettextpo_po_message_m_##field (VALUE self) \
220
+ { \
221
+ return rb_str_new_cstr (po_message_##field (DATA_PTR (self))); \
222
+ }
223
+ #define STRING_SETTER(field) \
224
+ VALUE \
225
+ gettextpo_po_message_m_##field##_set (VALUE self, VALUE string) \
226
+ { \
227
+ po_message_set_##field (DATA_PTR (self), StringValueCStr (string)); \
228
+ return Qnil; \
229
+ }
230
+
231
+ /**
232
+ * Document-method: msgid
233
+ */
234
+ STRING_GETTER (msgid);
235
+
236
+ /**
237
+ * Document-method: msgid=
238
+ * call-seq: msgid = id
239
+ */
240
+ STRING_SETTER (msgid);
241
+
242
+ /**
243
+ * Document-method: msgid_plural
244
+ *
245
+ * Possibly returns +nil+.
246
+ */
247
+ OPTIONAL_STRING_GETTER (msgid_plural);
248
+
249
+ /**
250
+ * Document-method: msgid_plural=
251
+ * call-seq: msgid_plural = id
252
+ *
253
+ * +id+ can be +nil+ to remove it.
254
+ */
255
+ OPTIONAL_STRING_SETTER (msgid_plural);
256
+
257
+ /**
258
+ * Document-method: msgstr
259
+ */
260
+ STRING_GETTER (msgstr);
261
+
262
+ /**
263
+ * Document-method: msgstr=
264
+ * call-seq: msgstr = str
265
+ */
266
+ STRING_SETTER (msgstr);
267
+
268
+ /**
269
+ * call-seq: msgstr_plural (index)
270
+ *
271
+ * Possibly returns +nil+.
272
+ */
273
+ VALUE
274
+ gettextpo_po_message_m_msgstr_plural (VALUE self, VALUE index)
275
+ {
276
+ const char *msgstr_plural
277
+ = po_message_msgstr_plural (DATA_PTR (self), NUM2INT (index));
278
+ return msgstr_plural ? rb_str_new_cstr (msgstr_plural) : Qnil;
279
+ }
280
+
281
+ /**
282
+ * call-seq: set_msgstr_plural (index, str)
283
+ *
284
+ * +str+ can be +nil+ to remove it.
285
+ */
286
+ VALUE
287
+ gettextpo_po_message_m_msgstr_plural_set (VALUE self, VALUE index,
288
+ VALUE msgstr_plural)
289
+ {
290
+ po_message_set_msgstr_plural (
291
+ DATA_PTR (self), NUM2INT (index),
292
+ NIL_P (msgstr_plural) ? NULL : StringValueCStr (msgstr_plural));
293
+ return Qnil;
294
+ }
295
+
296
+ /**
297
+ * Document-method: comments
298
+ */
299
+ STRING_GETTER (comments);
300
+
301
+ /**
302
+ * Document-method: comments=
303
+ * call-seq: comments = str
304
+ */
305
+ STRING_SETTER (comments);
306
+
307
+ /**
308
+ * Document-method: extracted_comments
309
+ */
310
+ STRING_GETTER (extracted_comments);
311
+
312
+ /**
313
+ * Document-method: extracted_comments=
314
+ * call-seq: extracted_comments = str
315
+ */
316
+ STRING_SETTER (extracted_comments);
317
+
318
+ /**
319
+ * Document-method: prev_msgctxt
320
+ *
321
+ * Possibly returns +nil+.
322
+ */
323
+ OPTIONAL_STRING_GETTER (prev_msgctxt);
324
+
325
+ /**
326
+ * Document-method: prev_msgctxt=
327
+ * call-seq: prev_msgctxt = context
328
+ *
329
+ * +context+ can be +nil+ to remove it.
330
+ */
331
+ OPTIONAL_STRING_SETTER (prev_msgctxt);
332
+
333
+ /**
334
+ * Document-method: prev_msgid
335
+ *
336
+ * Possibly returns +nil+.
337
+ */
338
+ OPTIONAL_STRING_GETTER (prev_msgid);
339
+
340
+ /**
341
+ * Document-method: prev_msgid=
342
+ * call-seq: prev_msgid = id
343
+ *
344
+ * +id+ can be +nil+ to remove it.
345
+ */
346
+ OPTIONAL_STRING_SETTER (prev_msgid);
347
+
348
+ /**
349
+ * Document-method: prev_msgid_plural
350
+ *
351
+ * Possibly returns +nil+.
352
+ */
353
+ OPTIONAL_STRING_GETTER (prev_msgid_plural);
354
+
355
+ /**
356
+ * Document-method: prev_msgid_plural=
357
+ * call-seq: prev_msgid_plural = id
358
+ *
359
+ * +id+ can be +nil+ to remove it.
360
+ */
361
+ OPTIONAL_STRING_SETTER (prev_msgid_plural);
362
+
363
+ #define BOOL_GETTER(field) \
364
+ VALUE \
365
+ gettextpo_po_message_m_##field (VALUE self) \
366
+ { \
367
+ return po_message_is_##field (DATA_PTR (self)) ? Qtrue : Qfalse; \
368
+ }
369
+ #define BOOL_SETTER(field) \
370
+ VALUE \
371
+ gettextpo_po_message_m_##field##_set (VALUE self, VALUE boolean) \
372
+ { \
373
+ po_message_set_##field (DATA_PTR (self), RB_TEST (boolean)); \
374
+ return Qnil; \
375
+ }
376
+
377
+ /**
378
+ * Document-method: obsolete?
379
+ */
380
+ BOOL_GETTER (obsolete);
381
+
382
+ /**
383
+ * Document-method: obsolete=
384
+ * call-seq: obsolete = bool
385
+ */
386
+ BOOL_SETTER (obsolete);
387
+
388
+ /**
389
+ * Document-method: fuzzy?
390
+ */
391
+ BOOL_GETTER (fuzzy);
392
+
393
+ /**
394
+ * Document-method: fuzzy=
395
+ * call-seq: fuzzy = bool
396
+ */
397
+ BOOL_SETTER (fuzzy);
398
+
399
+ /**
400
+ * call-seq: format? (type)
401
+ */
402
+ VALUE
403
+ gettextpo_po_message_m_format (VALUE self, VALUE format)
404
+ {
405
+ return po_message_is_format (DATA_PTR (self), StringValueCStr (format))
406
+ ? Qtrue
407
+ : Qfalse;
408
+ }
409
+
410
+ /**
411
+ * call-seq: update_format (type, opposite: false, remove: false)
412
+ *
413
+ * Set by default. +opposite+ sets a "no-" prefixed format type.
414
+ */
415
+ VALUE
416
+ gettextpo_po_message_m_format_set (int argc, VALUE *argv, VALUE self)
417
+ {
418
+ VALUE format, kwargs;
419
+ rb_scan_args (argc, argv, "1:", &format, &kwargs);
420
+ ID kwargs_ids[] = { rb_intern ("opposite"), rb_intern ("remove") };
421
+ VALUE kwargs_vals[] = { Qundef, Qundef };
422
+ rb_get_kwargs (kwargs, kwargs_ids, 0, 2, kwargs_vals);
423
+ bool opposite = !RB_UNDEF_P (kwargs_vals[0]) && RB_TEST (kwargs_vals[0]);
424
+ bool remove = !RB_UNDEF_P (kwargs_vals[1]) && RB_TEST (kwargs_vals[1]);
425
+ if (opposite && remove)
426
+ rb_raise (rb_eError, "opposite and remove cannot be set at the same time");
427
+ po_message_set_format (DATA_PTR (self), StringValueCStr (format),
428
+ opposite ? 0 : (remove ? -1 : 1));
429
+ return Qnil;
430
+ }
431
+
432
+ /**
433
+ * call-seq: range? (range)
434
+ */
435
+ VALUE
436
+ gettextpo_po_message_m_range (VALUE self, VALUE range)
437
+ {
438
+ VALUE min, max;
439
+ int exclude;
440
+ rb_range_values (range, &min, &max, &exclude);
441
+ int min_int = NUM2INT (min);
442
+ int max_int = NUM2INT (max);
443
+ return po_message_is_range (DATA_PTR (self), &min_int, &max_int) ? Qtrue
444
+ : Qfalse;
445
+ }
446
+
447
+ /**
448
+ * call-seq: range = range
449
+ */
450
+ VALUE
451
+ gettextpo_po_message_m_range_set (VALUE self, VALUE range)
452
+ {
453
+ VALUE min, max;
454
+ int exclude;
455
+ rb_range_values (range, &min, &max, &exclude);
456
+ po_message_set_range (DATA_PTR (self), min, max);
457
+ return Qnil;
458
+ }
459
+
460
+ /**
461
+ * call-seq: filepos (index) -> GettextPO::FilePos
462
+ *
463
+ * Possibly returns +nil+.
464
+ */
465
+ VALUE
466
+ gettextpo_po_message_m_filepos (VALUE self, VALUE index)
467
+ {
468
+ po_filepos_t pos = po_message_filepos (DATA_PTR (self), NUM2INT (index));
469
+ if (pos)
470
+ {
471
+ VALUE filepos = rb_obj_alloc (rb_cFilePos);
472
+ DATA_PTR (filepos) = pos;
473
+ return filepos;
474
+ }
475
+ else
476
+ return Qnil;
477
+ }
478
+
479
+ /**
480
+ * call-seq: remove_filepos (index)
481
+ */
482
+ VALUE
483
+ gettextpo_po_message_m_remove_filepos (VALUE self, VALUE index)
484
+ {
485
+ po_message_remove_filepos (DATA_PTR (self), NUM2INT (index));
486
+ return Qnil;
487
+ }
488
+
489
+ /**
490
+ * call-seq: add_filepos (file, start_line)
491
+ */
492
+ VALUE
493
+ gettextpo_po_message_m_add_filepos (VALUE self, VALUE file, VALUE start_line)
494
+ {
495
+ po_message_add_filepos (DATA_PTR (self), StringValueCStr (file),
496
+ NUM2INT (start_line));
497
+ return Qnil;
498
+ }
499
+
500
+ /**
501
+ * call-seq: check_all (iterator, xerror: nil, xerror2: nil)
502
+ *
503
+ * See also GettextPO::File.read.
504
+ */
505
+ VALUE
506
+ gettextpo_po_message_m_check_all (int argc, VALUE *argv, VALUE self)
507
+ {
508
+ VALUE iterator, kwargs;
509
+ rb_scan_args (argc, argv, "1:", &iterator, &kwargs);
510
+ ID kwargs_ids[] = { rb_intern ("xerror"), rb_intern ("xerror2") };
511
+ VALUE kwargs_vals[] = { Qundef, Qundef };
512
+ rb_get_kwargs (kwargs, kwargs_ids, 0, 2, kwargs_vals);
513
+ gettextpo_xerror_context_reset ();
514
+ if (kwargs_vals[0] != Qundef)
515
+ gettextpo_xerror_context.user_xerror = &kwargs_vals[0];
516
+ if (kwargs_vals[1] != Qundef)
517
+ gettextpo_xerror_context.user_xerror2 = &kwargs_vals[1];
518
+ po_message_check_all (DATA_PTR (self), DATA_PTR (iterator),
519
+ &gettextpo_xerror_handler);
520
+ if (gettextpo_xerror_context.error)
521
+ rb_raise (rb_eError, "check all for message failed");
522
+ return Qnil;
523
+ }
524
+
525
+ /**
526
+ * call-seq: check_format (xerror: nil, xerror2: nil)
527
+ *
528
+ * See also GettextPO::File.read.
529
+ */
530
+ VALUE
531
+ gettextpo_po_message_m_check_format (int argc, VALUE *argv, VALUE self)
532
+ {
533
+ VALUE kwargs;
534
+ rb_scan_args (argc, argv, ":", &kwargs);
535
+ ID kwargs_ids[] = { rb_intern ("xerror"), rb_intern ("xerror2") };
536
+ VALUE kwargs_vals[] = { Qundef, Qundef };
537
+ rb_get_kwargs (kwargs, kwargs_ids, 0, 2, kwargs_vals);
538
+ gettextpo_xerror_context_reset ();
539
+ if (kwargs_vals[0] != Qundef)
540
+ gettextpo_xerror_context.user_xerror = &kwargs_vals[0];
541
+ if (kwargs_vals[1] != Qundef)
542
+ gettextpo_xerror_context.user_xerror2 = &kwargs_vals[1];
543
+ po_message_check_format (DATA_PTR (self), &gettextpo_xerror_handler);
544
+ if (gettextpo_xerror_context.error)
545
+ rb_raise (rb_eError, "check format for message failed");
546
+ return Qnil;
547
+ }
548
+
549
+ /* ********** file ********** */
550
+
551
+ static const rb_data_type_t gettextpo_po_file_type = {
552
+ .wrap_struct_name = "gettextpo PO file",
553
+ .function = {
554
+ .dfree = (void (*)(void *)) po_file_free,
555
+ },
556
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
557
+ };
558
+
559
+ VALUE
560
+ gettextpo_po_file_alloc (VALUE self)
561
+ {
562
+ return TypedData_Wrap_Struct (self, &gettextpo_po_file_type, NULL);
563
+ }
564
+
565
+ VALUE
566
+ gettextpo_po_file_m_initialize (VALUE self)
567
+ {
568
+ DATA_PTR (self) = po_file_create ();
569
+ return self;
570
+ }
571
+
572
+ /**
573
+ * call-seq: read (filename, xerror: nil, xerror2: nil) -> GettextPO::File
574
+ *
575
+ * +xerror+ and +xerror2+ are +Proc+ objects. +xerror+ takes keyword
576
+ * arguments +severity+, +message+, +filename+, +lineno+, +column+,
577
+ * +multiline+, and +message_text+. +xerror2+ takes keyword arguments
578
+ * +severity+, +message1+, +filename1+, +lineno1+, +column1+,
579
+ * +multiline1+, +message_text1+, +message2+, +filename2+, +lineno2+,
580
+ * +column2+, +multiline2+, and +message_text2+.
581
+ */
582
+ VALUE
583
+ gettextpo_po_file_m_read (int argc, VALUE *argv, VALUE klass)
584
+ {
585
+ VALUE filename, kwargs;
586
+ rb_scan_args (argc, argv, "1:", &filename, &kwargs);
587
+ ID kwargs_ids[] = { rb_intern ("xerror"), rb_intern ("xerror2") };
588
+ VALUE kwargs_vals[] = { Qundef, Qundef };
589
+ rb_get_kwargs (kwargs, kwargs_ids, 0, 2, kwargs_vals);
590
+ gettextpo_xerror_context_reset ();
591
+ if (kwargs_vals[0] != Qundef)
592
+ gettextpo_xerror_context.user_xerror = &kwargs_vals[0];
593
+ if (kwargs_vals[1] != Qundef)
594
+ gettextpo_xerror_context.user_xerror2 = &kwargs_vals[1];
595
+ VALUE self = rb_obj_alloc (klass);
596
+ DATA_PTR (self)
597
+ = po_file_read (StringValueCStr (filename), &gettextpo_xerror_handler);
598
+ if (gettextpo_xerror_context.error)
599
+ rb_raise (rb_eError, "failed to read");
600
+ return self;
601
+ }
602
+
603
+ /**
604
+ * call-seq: write (filename, xerror: nil, xerror2: nil)
605
+ *
606
+ * See also ::read.
607
+ */
608
+ VALUE
609
+ gettextpo_po_file_m_write (int argc, VALUE *argv, VALUE self)
610
+ {
611
+ VALUE filename, kwargs;
612
+ rb_scan_args (argc, argv, "1:", &filename, &kwargs);
613
+ ID kwargs_ids[] = { rb_intern ("xerror"), rb_intern ("xerror2") };
614
+ VALUE kwargs_vals[] = { Qundef, Qundef };
615
+ rb_get_kwargs (kwargs, kwargs_ids, 0, 2, kwargs_vals);
616
+ gettextpo_xerror_context_reset ();
617
+ if (kwargs_vals[0] != Qundef)
618
+ gettextpo_xerror_context.user_xerror = &kwargs_vals[0];
619
+ if (kwargs_vals[1] != Qundef)
620
+ gettextpo_xerror_context.user_xerror2 = &kwargs_vals[1];
621
+ po_file_write (DATA_PTR (self), StringValueCStr (filename),
622
+ &gettextpo_xerror_handler);
623
+ if (gettextpo_xerror_context.error)
624
+ rb_raise (rb_eError, "failed to write");
625
+ return Qnil;
626
+ }
627
+
628
+ VALUE
629
+ gettextpo_po_file_m_domains (VALUE self)
630
+ {
631
+ const char *const *domains = po_file_domains (DATA_PTR (self));
632
+ VALUE result = rb_ary_new ();
633
+ for (size_t index = 0; domains[index]; index++)
634
+ rb_ary_push (result, rb_str_new_cstr (domains[index]));
635
+ return result;
636
+ }
637
+
638
+ /**
639
+ * call-seq: message_iterator (domain = nil) -> GettextPO::MessageIterator
640
+ */
641
+ VALUE
642
+ gettextpo_po_file_m_message_iterator (int argc, VALUE *argv, VALUE self)
643
+ {
644
+ VALUE domain;
645
+ rb_scan_args (argc, argv, "01", &domain);
646
+ VALUE iterator = rb_obj_alloc (rb_cMessageIterator);
647
+ DATA_PTR (iterator) = po_message_iterator (
648
+ DATA_PTR (self), NIL_P (domain) ? NULL : StringValueCStr (domain));
649
+ return iterator;
650
+ }
651
+
652
+ /**
653
+ * call-seq: domain_header (domain)
654
+ *
655
+ * +domain+ can be +nil+ to use a default. Possibly returns +nil+.
656
+ *
657
+ * See also GettextPO.header_entry_value.
658
+ */
659
+ VALUE
660
+ gettextpo_po_file_m_domain_header (int argc, VALUE *argv, VALUE self)
661
+ {
662
+ VALUE domain;
663
+ rb_scan_args (argc, argv, "01", &domain);
664
+ const char *header = po_file_domain_header (
665
+ DATA_PTR (self), NIL_P (domain) ? NULL : StringValueCStr (domain));
666
+ return header ? rb_str_new_cstr (header) : Qnil;
667
+ }
668
+
669
+ /**
670
+ * call-seq: check_all (xerror: nil, xerror2: nil)
671
+ *
672
+ * See also ::read.
673
+ */
674
+ VALUE
675
+ gettextpo_po_file_m_check_all (int argc, VALUE *argv, VALUE self)
676
+ {
677
+ VALUE kwargs;
678
+ rb_scan_args (argc, argv, ":", &kwargs);
679
+ ID kwargs_ids[] = { rb_intern ("xerror"), rb_intern ("xerror2") };
680
+ VALUE kwargs_vals[] = { Qundef, Qundef };
681
+ rb_get_kwargs (kwargs, kwargs_ids, 0, 2, kwargs_vals);
682
+ gettextpo_xerror_context_reset ();
683
+ if (kwargs_vals[0] != Qundef)
684
+ gettextpo_xerror_context.user_xerror = &kwargs_vals[0];
685
+ if (kwargs_vals[1] != Qundef)
686
+ gettextpo_xerror_context.user_xerror2 = &kwargs_vals[1];
687
+ po_file_check_all (DATA_PTR (self), &gettextpo_xerror_handler);
688
+ if (gettextpo_xerror_context.error)
689
+ rb_raise (rb_eError, "check all for file failed");
690
+ return Qnil;
691
+ }
692
+
693
+ /* ********** iterator ********** */
694
+
695
+ static const rb_data_type_t gettextpo_po_message_iterator_type = {
696
+ .wrap_struct_name = "gettextpo PO message iterator",
697
+ .function = {
698
+ .dfree = (void (*)(void *)) po_message_iterator_free,
699
+ },
700
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
701
+ };
702
+
703
+ VALUE
704
+ gettextpo_po_message_iterator_alloc (VALUE self)
705
+ {
706
+ return TypedData_Wrap_Struct (self, &gettextpo_po_message_iterator_type,
707
+ NULL);
708
+ }
709
+
710
+ /**
711
+ * call-seq: next -> GettextPO::Message
712
+ */
713
+ VALUE
714
+ gettextpo_po_message_iterator_m_next (VALUE self)
715
+ {
716
+ po_message_t message = po_next_message (DATA_PTR (self));
717
+ if (message)
718
+ {
719
+ VALUE message_value = rb_obj_alloc (rb_cMessage);
720
+ DATA_PTR (message_value) = message;
721
+ return message_value;
722
+ }
723
+ else
724
+ rb_raise (rb_eStopIteration, "end of PO message iterator");
725
+ }
726
+
727
+ /**
728
+ * call-seq: insert (msgid, msgstr) -> GettextPO::Message
729
+ */
730
+ VALUE
731
+ gettextpo_po_message_iterator_m_insert (VALUE self, VALUE msgid, VALUE msgstr)
732
+ {
733
+ po_message_t message = po_message_create ();
734
+ po_message_set_msgid (message, StringValueCStr (msgid));
735
+ po_message_set_msgstr (message, StringValueCStr (msgstr));
736
+ po_message_insert (DATA_PTR (self), message);
737
+ VALUE value = rb_obj_alloc (rb_cMessage);
738
+ DATA_PTR (value) = message;
739
+ return value;
740
+ }
741
+
742
+ /**
743
+ * Document-class: GettextPO::FilePos
744
+ */
745
+
746
+ static const rb_data_type_t gettextpo_po_message_filepos_type = {
747
+ .wrap_struct_name = "gettextpo PO message filepos",
748
+ .function = {
749
+ .dfree = RUBY_TYPED_NEVER_FREE,
750
+ },
751
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
752
+ };
753
+
754
+ VALUE
755
+ gettextpo_po_filepos_alloc (VALUE self)
756
+ {
757
+ return TypedData_Wrap_Struct (self, &gettextpo_po_message_filepos_type,
758
+ NULL);
759
+ }
760
+
761
+ /**
762
+ * Possibly returns +nil+.
763
+ */
764
+ VALUE
765
+ gettextpo_po_filepos_m_file (VALUE self)
766
+ {
767
+ const char *file = po_filepos_file (DATA_PTR (self));
768
+ return file ? rb_str_new_cstr (file) : Qnil;
769
+ }
770
+
771
+ /**
772
+ * Possibly returns +nil+.
773
+ */
774
+ VALUE
775
+ gettextpo_po_filepos_m_start_line (VALUE self)
776
+ {
777
+ size_t start_line = po_filepos_start_line (DATA_PTR (self));
778
+ return start_line == (size_t)(-1) ? Qnil : INT2NUM (start_line);
779
+ }
780
+
781
+ /* ********** others ********** */
782
+
783
+ /**
784
+ * call-seq: header_entry_value (header, field)
785
+ *
786
+ * See also GettextPO::File#domain_header.
787
+ */
788
+ VALUE
789
+ gettextpo_m_header_entry_value (VALUE self, VALUE header, VALUE field)
790
+ {
791
+ char *string
792
+ = po_header_field (StringValueCStr (header), StringValueCStr (field));
793
+ if (string)
794
+ {
795
+ VALUE value = rb_str_new_cstr (string);
796
+ free (string);
797
+ return value;
798
+ }
799
+ else
800
+ return Qnil;
801
+ }
802
+
803
+ /**
804
+ * call-seq: header_with_updated_entry_value (header, field, value)
805
+ *
806
+ * See also GettextPO::File#domain_header.
807
+ */
808
+ VALUE
809
+ gettextpo_m_header_with_updated_entry_value (VALUE self, VALUE header,
810
+ VALUE field, VALUE value)
811
+ {
812
+ char *updated_header
813
+ = po_header_set_field (StringValueCStr (header), StringValueCStr (field),
814
+ StringValueCStr (value));
815
+ VALUE header_value = rb_str_new_cstr (updated_header);
816
+ free (updated_header);
817
+ return header_value;
818
+ }
819
+
820
+ VALUE
821
+ gettextpo_po_format_list (VALUE self)
822
+ {
823
+ VALUE list = rb_ary_new ();
824
+ const char *const *formats = po_format_list ();
825
+ for (size_t index = 0; formats[index]; index++)
826
+ rb_ary_push (list, rb_str_new_cstr (formats[index]));
827
+ return list;
828
+ }
829
+
830
+ /**
831
+ * call-seq: format_pretty_name (format)
832
+ *
833
+ * Possibly returns +nil+.
834
+ */
835
+ VALUE
836
+ gettextpo_po_format_pretty_name (VALUE self, VALUE format)
837
+ {
838
+ const char *name = po_format_pretty_name (StringValueCStr (format));
839
+ return name ? rb_str_new_cstr (name) : Qnil;
840
+ }
841
+
842
+ /* ********** init ********** */
843
+
844
+ RUBY_FUNC_EXPORTED void
845
+ Init_gettextpo (void)
846
+ {
847
+ VALUE rb_mGettextPO = rb_define_module ("GettextPO");
848
+ rb_define_singleton_method (rb_mGettextPO, "header_entry_value",
849
+ gettextpo_m_header_entry_value, 2);
850
+ rb_define_singleton_method (rb_mGettextPO, "header_with_updated_entry_value",
851
+ gettextpo_m_header_with_updated_entry_value, 3);
852
+ rb_define_singleton_method (rb_mGettextPO, "formats",
853
+ gettextpo_po_format_list, 0);
854
+ rb_define_singleton_method (rb_mGettextPO, "format_pretty_name",
855
+ gettextpo_po_format_pretty_name, 1);
856
+ VALUE rb_cFile = rb_define_class_under (rb_mGettextPO, "File", rb_cObject);
857
+ rb_define_alloc_func (rb_cFile, gettextpo_po_file_alloc);
858
+ rb_define_singleton_method (rb_cFile, "read", gettextpo_po_file_m_read, -1);
859
+ rb_define_method (rb_cFile, "initialize", gettextpo_po_file_m_initialize, 0);
860
+ rb_define_method (rb_cFile, "write", gettextpo_po_file_m_write, -1);
861
+ rb_define_method (rb_cFile, "domains", gettextpo_po_file_m_domains, 0);
862
+ rb_define_method (rb_cFile, "message_iterator",
863
+ gettextpo_po_file_m_message_iterator, -1);
864
+ rb_define_method (rb_cFile, "domain_header",
865
+ gettextpo_po_file_m_domain_header, -1);
866
+ rb_define_method (rb_cFile, "check_all", gettextpo_po_file_m_check_all, -1);
867
+ rb_cMessage = rb_define_class_under (rb_mGettextPO, "Message", rb_cObject);
868
+ rb_define_alloc_func (rb_cMessage, gettextpo_po_message_alloc);
869
+ rb_define_method (rb_cMessage, "msgctxt", gettextpo_po_message_m_msgctxt, 0);
870
+ rb_define_method (rb_cMessage,
871
+ "msgctxt=", gettextpo_po_message_m_msgctxt_set, 1);
872
+ rb_define_method (rb_cMessage, "msgid", gettextpo_po_message_m_msgid, 0);
873
+ rb_define_method (rb_cMessage, "msgid=", gettextpo_po_message_m_msgid_set,
874
+ 1);
875
+ rb_define_method (rb_cMessage, "msgid_plural",
876
+ gettextpo_po_message_m_msgid_plural, 0);
877
+ rb_define_method (rb_cMessage,
878
+ "msgid_plural=", gettextpo_po_message_m_msgid_plural_set,
879
+ 1);
880
+ rb_define_method (rb_cMessage, "msgstr", gettextpo_po_message_m_msgstr, 0);
881
+ rb_define_method (rb_cMessage, "msgstr=", gettextpo_po_message_m_msgstr_set,
882
+ 1);
883
+ rb_define_method (rb_cMessage, "msgstr_plural",
884
+ gettextpo_po_message_m_msgstr_plural, 1);
885
+ rb_define_method (rb_cMessage, "set_msgstr_plural",
886
+ gettextpo_po_message_m_msgstr_plural_set, 2);
887
+ rb_define_method (rb_cMessage, "comments", gettextpo_po_message_m_comments,
888
+ 0);
889
+ rb_define_method (rb_cMessage,
890
+ "comments=", gettextpo_po_message_m_comments_set, 1);
891
+ rb_define_method (rb_cMessage, "extracted_comments",
892
+ gettextpo_po_message_m_extracted_comments, 0);
893
+ rb_define_method (rb_cMessage, "extracted_comments=",
894
+ gettextpo_po_message_m_extracted_comments_set, 1);
895
+ rb_define_method (rb_cMessage, "prev_msgctxt",
896
+ gettextpo_po_message_m_prev_msgctxt, 0);
897
+ rb_define_method (rb_cMessage,
898
+ "prev_msgctxt=", gettextpo_po_message_m_prev_msgctxt_set,
899
+ 1);
900
+ rb_define_method (rb_cMessage, "prev_msgid",
901
+ gettextpo_po_message_m_prev_msgid, 0);
902
+ rb_define_method (rb_cMessage,
903
+ "prev_msgid=", gettextpo_po_message_m_prev_msgid_set, 1);
904
+ rb_define_method (rb_cMessage, "prev_msgid_plural",
905
+ gettextpo_po_message_m_prev_msgid_plural, 0);
906
+ rb_define_method (rb_cMessage, "prev_msgid_plural=",
907
+ gettextpo_po_message_m_prev_msgid_plural_set, 1);
908
+ rb_define_method (rb_cMessage, "obsolete?", gettextpo_po_message_m_obsolete,
909
+ 0);
910
+ rb_define_method (rb_cMessage,
911
+ "obsolete=", gettextpo_po_message_m_obsolete_set, 1);
912
+ rb_define_method (rb_cMessage, "fuzzy?", gettextpo_po_message_m_fuzzy, 0);
913
+ rb_define_method (rb_cMessage, "fuzzy=", gettextpo_po_message_m_fuzzy_set,
914
+ 1);
915
+ rb_define_method (rb_cMessage, "format?", gettextpo_po_message_m_format, 1);
916
+ rb_define_method (rb_cMessage, "update_format",
917
+ gettextpo_po_message_m_format_set, -1);
918
+ rb_define_method (rb_cMessage, "range?", gettextpo_po_message_m_range, 1);
919
+ rb_define_method (rb_cMessage, "range=", gettextpo_po_message_m_range_set,
920
+ 1);
921
+ rb_define_method (rb_cMessage, "filepos", gettextpo_po_message_m_filepos, 1);
922
+ rb_define_method (rb_cMessage, "remove_filepos",
923
+ gettextpo_po_message_m_remove_filepos, 1);
924
+ rb_define_method (rb_cMessage, "add_filepos",
925
+ gettextpo_po_message_m_add_filepos, 2);
926
+ rb_define_method (rb_cMessage, "check_all", gettextpo_po_message_m_check_all,
927
+ -1);
928
+ rb_define_method (rb_cMessage, "check_format",
929
+ gettextpo_po_message_m_check_format, -1);
930
+ rb_cMessageIterator
931
+ = rb_define_class_under (rb_mGettextPO, "MessageIterator", rb_cObject);
932
+ rb_define_alloc_func (rb_cMessageIterator,
933
+ gettextpo_po_message_iterator_alloc);
934
+ rb_define_method (rb_cMessageIterator, "next",
935
+ gettextpo_po_message_iterator_m_next, 0);
936
+ rb_define_method (rb_cMessageIterator, "insert",
937
+ gettextpo_po_message_iterator_m_insert, 2);
938
+ rb_cFilePos = rb_define_class_under (rb_mGettextPO, "FilePos", rb_cObject);
939
+ rb_define_alloc_func (rb_cFilePos, gettextpo_po_filepos_alloc);
940
+ rb_define_method (rb_cFilePos, "file", gettextpo_po_filepos_m_file, 0);
941
+ rb_define_method (rb_cFilePos, "start_line",
942
+ gettextpo_po_filepos_m_start_line, 0);
943
+ rb_eError
944
+ = rb_define_class_under (rb_mGettextPO, "Error", rb_eStandardError);
945
+ }