apex-ruby 1.0.38 → 1.0.39

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae9f539a2b1544d0df515de411fde4d0f3aeb30ab7ca7b6f479b11aebf7ffad9
4
- data.tar.gz: cc8bbde52b5809eed92e053ba83f036ebd0165359f66b2948cda19d0302a49f9
3
+ metadata.gz: f037626c97e2d9d1da593da4e5cfe66b74bac805d7bad91e8f5547ff676cb394
4
+ data.tar.gz: a1ef5604a103f328a970246606c4e9c79b2ea6ffece769532250da7499dc287d
5
5
  SHA512:
6
- metadata.gz: d249897d7cb049c2e2321d9eb611c7e7130b72c3a1d3eab7a9397b78d70de1c142cf149056ef15bb55ebe6748d1102f0e5cea56ce3343db7db0edac19848fc30
7
- data.tar.gz: 5806b9b11104918b2f115f557cb032a339b03017375b08cb4ce15168ed0886837500b6aec2deedd2aaf15af45abfacf40cc5df93a7cabdbe2b8033c9b6579ac1
6
+ metadata.gz: b6f994047113301b183aa7529089d6342f6847ace386ceb281cd80675b5ef79afc6a7c423318357533c03102b24916e8b7e20405c750b903189674a2cb321380
7
+ data.tar.gz: 90101aea55503ce808d9cc4f6018c466ff9a03791deac096f4e13e85858536beb5abe6dabc7d90e89a0f2fe91b7fde7bdd9d054bc841d99b54fd45679b98d344
@@ -21,8 +21,8 @@ typedef struct cmark_node cmark_node;
21
21
 
22
22
  #define APEX_VERSION_MAJOR 1
23
23
  #define APEX_VERSION_MINOR 1
24
- #define APEX_VERSION_PATCH 40
25
- #define APEX_VERSION_STRING "1.1.40"
24
+ #define APEX_VERSION_PATCH 41
25
+ #define APEX_VERSION_STRING "1.1.41"
26
26
 
27
27
  /**
28
28
  * Processor compatibility modes
@@ -278,23 +278,490 @@ void apex_process_critic_markup_in_tree(cmark_node *node, critic_mode_t mode) {
278
278
  }
279
279
  }
280
280
 
281
+ static const char *critic_next_line(const char *p) {
282
+ while (*p && *p != '\n' && *p != '\r') p++;
283
+ if (*p == '\r' && p[1] == '\n') return p + 2;
284
+ return *p ? p + 1 : p;
285
+ }
286
+
287
+ /* Length of a ``` or ~~~ fence run opening the line, or 0. */
288
+ static size_t critic_fence_run(const char *line, char *fence_char, const char **after_run) {
289
+ const char *p = line;
290
+ while (*p == ' ' || *p == '\t' || *p == '>') p++;
291
+ if (*p != '`' && *p != '~') return 0;
292
+ char c = *p;
293
+ const char *run = p;
294
+ while (*p == c) p++;
295
+ if (p - run < 3) return 0;
296
+ *fence_char = c;
297
+ *after_run = p;
298
+ return (size_t)(p - run);
299
+ }
300
+
301
+ /* Bytes at pos that are a fenced block or code span and must be copied
302
+ * through untouched. Returns 0 when pos does not start code. */
303
+ static size_t critic_code_region_len(const char *base, const char *pos) {
304
+ bool line_start = (pos == base || pos[-1] == '\n' || pos[-1] == '\r');
305
+ if (line_start) {
306
+ char fence_char;
307
+ const char *after;
308
+ size_t open_len = critic_fence_run(pos, &fence_char, &after);
309
+ if (open_len) {
310
+ bool valid = true;
311
+ if (fence_char == '`') {
312
+ for (const char *q = after; *q && *q != '\n' && *q != '\r'; q++) {
313
+ if (*q == '`') { valid = false; break; }
314
+ }
315
+ }
316
+ if (valid) {
317
+ const char *line = critic_next_line(pos);
318
+ while (*line) {
319
+ char close_char;
320
+ const char *close_after;
321
+ size_t close_len = critic_fence_run(line, &close_char, &close_after);
322
+ if (close_len >= open_len && close_char == fence_char) {
323
+ while (*close_after == ' ' || *close_after == '\t') close_after++;
324
+ if (!*close_after || *close_after == '\n' || *close_after == '\r') {
325
+ return (size_t)(critic_next_line(line) - pos);
326
+ }
327
+ }
328
+ line = critic_next_line(line);
329
+ }
330
+ /* Unclosed fence: protect only the opening line */
331
+ return (size_t)(critic_next_line(pos) - pos);
332
+ }
333
+ }
334
+ }
335
+
336
+ if (*pos != '`') return 0;
337
+
338
+ size_t backslashes = 0;
339
+ for (const char *b = pos; b > base && b[-1] == '\\'; b--) backslashes++;
340
+ if (backslashes % 2) return 1;
341
+
342
+ const char *p = pos;
343
+ while (*p == '`') p++;
344
+ size_t open_len = (size_t)(p - pos);
345
+
346
+ /* Code spans may wrap lines but end at a blank line */
347
+ while (*p) {
348
+ if (*p == '\n' || *p == '\r') {
349
+ const char *next = critic_next_line(p);
350
+ const char *q = next;
351
+ while (*q == ' ' || *q == '\t' || *q == '>') q++;
352
+ if (!*q || *q == '\n' || *q == '\r') break;
353
+ p = next;
354
+ continue;
355
+ }
356
+ if (*p == '`') {
357
+ const char *run = p;
358
+ while (*p == '`') p++;
359
+ if ((size_t)(p - run) == open_len) return (size_t)(p - pos);
360
+ continue;
361
+ }
362
+ p++;
363
+ }
364
+ return open_len;
365
+ }
366
+
367
+ /* Indented code blocks follow the CommonMark rules: four spaces or a tab
368
+ * at the current container, and they cannot interrupt a paragraph. List
369
+ * continuations and paragraph lines stay eligible for critic markup. */
370
+
371
+ typedef enum {
372
+ CRITIC_BOX_QUOTE,
373
+ CRITIC_BOX_ITEM
374
+ } critic_box_kind;
375
+
376
+ typedef struct {
377
+ critic_box_kind kind;
378
+ int need; /* ITEM: columns required to continue */
379
+ bool has_content; /* ITEM has a child block */
380
+ } critic_box;
381
+
382
+ typedef struct {
383
+ critic_box stack[32];
384
+ int depth;
385
+ bool in_paragraph;
386
+ bool in_code;
387
+ } critic_blocks;
388
+
389
+ static int critic_col_add(int col, char c) {
390
+ if (c == '\t') return col + (4 - (col % 4));
391
+ return col + 1;
392
+ }
393
+
394
+ static bool critic_line_blank(const char *p) {
395
+ while (*p == ' ' || *p == '\t') p++;
396
+ return *p == '\0' || *p == '\n' || *p == '\r';
397
+ }
398
+
399
+ static const char *critic_skip_indent(const char *p, int *col, int *indent) {
400
+ int start = *col;
401
+ while (*p == ' ' || *p == '\t') {
402
+ *col = critic_col_add(*col, *p);
403
+ p++;
404
+ }
405
+ *indent = *col - start;
406
+ return p;
407
+ }
408
+
409
+ /* Consume exactly `columns` of indent. A tab may be only partly consumed. */
410
+ static const char *critic_advance_columns(const char *p, int *col, int columns) {
411
+ int target = *col + columns;
412
+ while (*p && *p != '\n' && *p != '\r' && *col < target) {
413
+ if (*p == '\t') {
414
+ int next = critic_col_add(*col, '\t');
415
+ if (next > target) {
416
+ *col = target;
417
+ return p;
418
+ }
419
+ *col = next;
420
+ p++;
421
+ } else {
422
+ *col += 1;
423
+ p++;
424
+ }
425
+ }
426
+ return p;
427
+ }
428
+
429
+ static bool critic_match_quote(const char *p, int col, const char **out, int *out_col) {
430
+ int c = col;
431
+ int indent = 0;
432
+ const char *q = critic_skip_indent(p, &c, &indent);
433
+ if (indent > 3 || *q != '>') return false;
434
+ q++;
435
+ c += 1;
436
+ if (*q == ' ' || *q == '\t') {
437
+ c = critic_col_add(c, *q);
438
+ q++;
439
+ }
440
+ *out = q;
441
+ *out_col = c;
442
+ return true;
443
+ }
444
+
445
+ /* 1 = bullet, 2 = ordered. len is the marker without trailing space. */
446
+ static int critic_list_marker(const char *p, int *len, int *ordered_start) {
447
+ if (*p == '-' || *p == '+' || *p == '*') {
448
+ char n = p[1];
449
+ if (n == ' ' || n == '\t' || n == '\n' || n == '\r' || n == '\0') {
450
+ *len = 1;
451
+ *ordered_start = 0;
452
+ return 1;
453
+ }
454
+ return 0;
455
+ }
456
+ if (*p >= '1' && *p <= '9') {
457
+ int i = 0;
458
+ int num = 0;
459
+ while (i < 9 && p[i] >= '0' && p[i] <= '9') {
460
+ num = num * 10 + (p[i] - '0');
461
+ i++;
462
+ }
463
+ if (i > 0 && (p[i] == '.' || p[i] == ')')) {
464
+ char n = p[i + 1];
465
+ if (n == ' ' || n == '\t' || n == '\n' || n == '\r' || n == '\0') {
466
+ *len = i + 1;
467
+ *ordered_start = num;
468
+ return 2;
469
+ }
470
+ }
471
+ }
472
+ return 0;
473
+ }
474
+
475
+ static bool critic_is_atx(const char *p) {
476
+ int n = 0;
477
+ while (p[n] == '#' && n < 7) n++;
478
+ if (n < 1 || n > 6) return false;
479
+ return p[n] == ' ' || p[n] == '\t' || p[n] == '\n' || p[n] == '\r' || p[n] == '\0';
480
+ }
481
+
482
+ static bool critic_is_fence_opener(const char *p) {
483
+ char c = *p;
484
+ if (c != '`' && c != '~') return false;
485
+ int n = 0;
486
+ while (p[n] == c) n++;
487
+ if (n < 3) return false;
488
+ if (c == '`') {
489
+ for (int i = n; p[i] && p[i] != '\n' && p[i] != '\r'; i++) {
490
+ if (p[i] == '`') return false;
491
+ }
492
+ }
493
+ return true;
494
+ }
495
+
496
+ /* Thematic break, or a setext underline when a paragraph is open. */
497
+ static bool critic_is_rule(const char *p, bool in_paragraph) {
498
+ char kind = 0;
499
+ int count = 0;
500
+ bool spaced = false;
501
+ for (const char *q = p; *q && *q != '\n' && *q != '\r'; q++) {
502
+ if (*q == ' ' || *q == '\t') {
503
+ if (count > 0) spaced = true;
504
+ continue;
505
+ }
506
+ if (*q != '-' && *q != '_' && *q != '*' && *q != '=') return false;
507
+ if (!kind) kind = *q;
508
+ else if (*q != kind) return false;
509
+ count++;
510
+ }
511
+ if (count < 1 || !kind) return false;
512
+ if (!spaced && in_paragraph && (kind == '-' || kind == '=') && count >= 1) return true;
513
+ if (!spaced && count >= 3 && (kind == '-' || kind == '_' || kind == '*')) return true;
514
+ if (spaced && count >= 3 && (kind == '-' || kind == '_' || kind == '*')) return true;
515
+ return false;
516
+ }
517
+
518
+ static int critic_match_boxes(const char *line, const critic_blocks *st,
519
+ const char **p, int *col) {
520
+ *p = line;
521
+ *col = 0;
522
+ int matched = 0;
523
+ bool blank = critic_line_blank(line);
524
+ for (int i = 0; i < st->depth; i++) {
525
+ if (st->stack[i].kind == CRITIC_BOX_QUOTE) {
526
+ const char *q;
527
+ int c;
528
+ if (!critic_match_quote(*p, *col, &q, &c)) break;
529
+ *p = q;
530
+ *col = c;
531
+ matched++;
532
+ } else if (blank) {
533
+ if (!st->stack[i].has_content) break;
534
+ matched++;
535
+ } else {
536
+ int indent = 0;
537
+ int c = *col;
538
+ critic_skip_indent(*p, &c, &indent);
539
+ if (indent < st->stack[i].need) break;
540
+ c = *col;
541
+ *p = critic_advance_columns(*p, &c, st->stack[i].need);
542
+ *col = c;
543
+ matched++;
544
+ }
545
+ }
546
+ return matched;
547
+ }
548
+
549
+ static void critic_mark_content(critic_blocks *st) {
550
+ for (int i = 0; i < st->depth; i++) {
551
+ if (st->stack[i].kind == CRITIC_BOX_ITEM) st->stack[i].has_content = true;
552
+ }
553
+ }
554
+
555
+ /* Would this line open a block that closes an open paragraph? */
556
+ static bool critic_interrupts_paragraph(const char *p, int col) {
557
+ int indent = 0;
558
+ int c = col;
559
+ const char *q = critic_skip_indent(p, &c, &indent);
560
+ if (indent >= 4) return false; /* indented code cannot interrupt */
561
+ if (*q == '>') return true;
562
+ int marker_len = 0;
563
+ int ordered_start = 0;
564
+ int kind = critic_list_marker(q, &marker_len, &ordered_start);
565
+ if (kind == 1) return true;
566
+ if (kind == 2 && ordered_start == 1) return true;
567
+ if (critic_is_atx(q) || critic_is_fence_opener(q) || critic_is_rule(q, true)) return true;
568
+ return false;
569
+ }
570
+
571
+ /* Interpret the remainder of a line after open containers have been matched.
572
+ * Returns true when the line is indented code and must be copied verbatim. */
573
+ static bool critic_interpret_line(critic_blocks *st, const char *p, int col) {
574
+ for (;;) {
575
+ int indent = 0;
576
+ int c = col;
577
+ const char *q = critic_skip_indent(p, &c, &indent);
578
+ if (*q == '\0' || *q == '\n' || *q == '\r') {
579
+ st->in_paragraph = false;
580
+ return false;
581
+ }
582
+ if (indent >= 4) {
583
+ if (!st->in_paragraph) {
584
+ st->in_code = true;
585
+ critic_mark_content(st);
586
+ return true;
587
+ }
588
+ return false;
589
+ }
590
+ if (*q == '>') {
591
+ if (st->depth < 32) {
592
+ st->stack[st->depth].kind = CRITIC_BOX_QUOTE;
593
+ st->stack[st->depth].need = 0;
594
+ st->stack[st->depth].has_content = false;
595
+ st->depth++;
596
+ }
597
+ q++;
598
+ c += 1;
599
+ if (*q == ' ' || *q == '\t') {
600
+ c = critic_col_add(c, *q);
601
+ q++;
602
+ }
603
+ p = q;
604
+ col = c;
605
+ st->in_paragraph = false;
606
+ continue;
607
+ }
608
+ int marker_len = 0;
609
+ int ordered_start = 0;
610
+ int kind = critic_list_marker(q, &marker_len, &ordered_start);
611
+ if (kind && !(st->in_paragraph && kind == 2 && ordered_start != 1)) {
612
+ int marker_offset = indent;
613
+ const char *after = q + marker_len;
614
+ int after_col = c + marker_len;
615
+ int save_col = after_col;
616
+ const char *s = after;
617
+ int sc = after_col;
618
+ while (*s == ' ' || *s == '\t') {
619
+ if (sc - save_col > 5) break;
620
+ int next = critic_col_add(sc, *s);
621
+ if (sc - save_col <= 5) {
622
+ sc = next;
623
+ s++;
624
+ if (sc - save_col > 5) break;
625
+ } else {
626
+ break;
627
+ }
628
+ }
629
+ int i = sc - save_col;
630
+ int padding;
631
+ const char *content;
632
+ int content_col;
633
+ if (i >= 5 || i < 1 || *s == '\0' || *s == '\n' || *s == '\r') {
634
+ padding = marker_len + 1;
635
+ content_col = after_col;
636
+ content = after;
637
+ if (i > 0) content = critic_advance_columns(after, &content_col, 1);
638
+ } else {
639
+ padding = marker_len + i;
640
+ content = s;
641
+ content_col = sc;
642
+ }
643
+ if (st->depth < 32) {
644
+ st->stack[st->depth].kind = CRITIC_BOX_ITEM;
645
+ st->stack[st->depth].need = marker_offset + padding;
646
+ st->stack[st->depth].has_content = false;
647
+ st->depth++;
648
+ }
649
+ p = content;
650
+ col = content_col;
651
+ st->in_paragraph = false;
652
+ st->in_code = false;
653
+ continue;
654
+ }
655
+ if (critic_is_fence_opener(q) || critic_is_atx(q) || critic_is_rule(q, st->in_paragraph)) {
656
+ st->in_paragraph = false;
657
+ st->in_code = false;
658
+ critic_mark_content(st);
659
+ return false;
660
+ }
661
+ st->in_paragraph = true;
662
+ st->in_code = false;
663
+ critic_mark_content(st);
664
+ return false;
665
+ }
666
+ }
667
+
668
+ /* True when this line is indented code and should be copied unchanged.
669
+ * Updates block state either way. Call only at the start of a line. */
670
+ static bool critic_indented_code_line(const char *line, critic_blocks *st) {
671
+ const char *p;
672
+ int col;
673
+ int matched = critic_match_boxes(line, st, &p, &col);
674
+ bool blank = critic_line_blank(line);
675
+
676
+ if (matched < st->depth) {
677
+ if (st->in_paragraph && !blank && !critic_interrupts_paragraph(p, col)) {
678
+ return false; /* lazy continuation */
679
+ }
680
+ st->depth = matched;
681
+ st->in_code = false;
682
+ st->in_paragraph = false;
683
+ }
684
+
685
+ if (blank) {
686
+ st->in_paragraph = false;
687
+ if (matched < st->depth) st->in_code = false;
688
+ return false;
689
+ }
690
+
691
+ if (st->in_code) {
692
+ int indent = 0;
693
+ int c = col;
694
+ critic_skip_indent(p, &c, &indent);
695
+ if (indent >= 4) return true;
696
+ st->in_code = false;
697
+ }
698
+
699
+ return critic_interpret_line(st, p, col);
700
+ }
701
+
702
+ /* remaining counts bytes available before the terminating NUL. */
703
+ static bool critic_append(char **buf, char **write, size_t *remaining,
704
+ const char *src, size_t len) {
705
+ if (len >= *remaining) {
706
+ size_t written = (size_t)(*write - *buf);
707
+ size_t new_cap = (written + len + 1) * 2;
708
+ char *grown = realloc(*buf, new_cap);
709
+ if (!grown) return false;
710
+ *buf = grown;
711
+ *write = grown + written;
712
+ *remaining = new_cap - written - 1;
713
+ }
714
+ if (len) {
715
+ memcpy(*write, src, len);
716
+ *write += len;
717
+ *remaining -= len;
718
+ }
719
+ return true;
720
+ }
721
+
281
722
  /**
282
723
  * Process Critic Markup in raw text (preprocessing approach)
283
- * This is better than postprocessing because it avoids smart typography interference
724
+ * This is better than postprocessing because it avoids smart typography interference.
725
+ * Code spans, fenced blocks, and indented code blocks are copied through
726
+ * so the syntax can be shown literally.
284
727
  */
285
728
  char *apex_process_critic_markup_text(const char *text, critic_mode_t mode) {
286
729
  if (!text) return NULL;
287
730
 
288
731
  size_t len = strlen(text);
289
- size_t output_capacity = len * 2; /* Generous estimate */
732
+ size_t output_capacity = len * 2 + 1;
290
733
  char *output = malloc(output_capacity);
291
734
  if (!output) return NULL;
292
735
 
293
736
  const char *read_pos = text;
294
737
  char *write_pos = output;
295
- size_t remaining_capacity = output_capacity;
738
+ size_t remaining_capacity = output_capacity - 1;
739
+ critic_blocks blocks;
740
+ memset(&blocks, 0, sizeof(blocks));
296
741
 
297
742
  while (*read_pos) {
743
+ bool line_start = (read_pos == text || read_pos[-1] == '\n' ||
744
+ (read_pos[-1] == '\r' && *read_pos != '\n'));
745
+ if (line_start && critic_indented_code_line(read_pos, &blocks)) {
746
+ const char *next = critic_next_line(read_pos);
747
+ if (!critic_append(&output, &write_pos, &remaining_capacity, read_pos, (size_t)(next - read_pos))) {
748
+ free(output);
749
+ return NULL;
750
+ }
751
+ read_pos = next;
752
+ continue;
753
+ }
754
+
755
+ size_t code_len = critic_code_region_len(text, read_pos);
756
+ if (code_len > 0) {
757
+ if (!critic_append(&output, &write_pos, &remaining_capacity, read_pos, code_len)) {
758
+ free(output);
759
+ return NULL;
760
+ }
761
+ read_pos += code_len;
762
+ continue;
763
+ }
764
+
298
765
  /* Look for { */
299
766
  if (*read_pos == '{') {
300
767
  int consumed;
@@ -310,11 +777,10 @@ char *apex_process_critic_markup_text(const char *text, critic_mode_t mode) {
310
777
  /* Found valid critic markup - convert to HTML */
311
778
  char *html = critic_to_html(type, content, content_len, old_text, old_len, mode);
312
779
  if (html) {
313
- size_t html_len = strlen(html);
314
- if (html_len < remaining_capacity) {
315
- memcpy(write_pos, html, html_len);
316
- write_pos += html_len;
317
- remaining_capacity -= html_len;
780
+ if (!critic_append(&output, &write_pos, &remaining_capacity, html, strlen(html))) {
781
+ free(html);
782
+ free(output);
783
+ return NULL;
318
784
  }
319
785
  free(html);
320
786
  }
@@ -324,9 +790,9 @@ char *apex_process_critic_markup_text(const char *text, critic_mode_t mode) {
324
790
  }
325
791
 
326
792
  /* Not critic markup, copy character */
327
- if (remaining_capacity > 0) {
328
- *write_pos++ = *read_pos;
329
- remaining_capacity--;
793
+ if (!critic_append(&output, &write_pos, &remaining_capacity, read_pos, 1)) {
794
+ free(output);
795
+ return NULL;
330
796
  }
331
797
  read_pos++;
332
798
  }
data/lib/apex/version.rb CHANGED
@@ -6,7 +6,7 @@ module Apex
6
6
  # via Apex::Native.version.
7
7
  module Version
8
8
  # Current gem version.
9
- VERSION = "1.0.38"
9
+ VERSION = "1.0.39"
10
10
  end
11
11
 
12
12
  # Shortcut to gem version string.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apex-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.38
4
+ version: 1.0.39
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra