dtext 2.0.6

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.
data/ext/dtext/dtext.h ADDED
@@ -0,0 +1,125 @@
1
+ #ifndef DTEXT_H
2
+ #define DTEXT_H
3
+
4
+ #include <string>
5
+ #include <vector>
6
+ #include <stdexcept>
7
+
8
+ typedef enum element_t {
9
+ DSTACK_EMPTY = 0,
10
+ BLOCK_P,
11
+ BLOCK_QUOTE,
12
+ BLOCK_SECTION,
13
+ BLOCK_SPOILER,
14
+ BLOCK_CODE,
15
+ BLOCK_TABLE,
16
+ BLOCK_THEAD,
17
+ BLOCK_TBODY,
18
+ BLOCK_UL,
19
+ BLOCK_LI,
20
+ BLOCK_TR,
21
+ BLOCK_TH,
22
+ BLOCK_TD,
23
+ BLOCK_H1,
24
+ BLOCK_H2,
25
+ BLOCK_H3,
26
+ BLOCK_H4,
27
+ BLOCK_H5,
28
+ BLOCK_H6,
29
+ INLINE_B,
30
+ INLINE_I,
31
+ INLINE_U,
32
+ INLINE_S,
33
+ INLINE_SUP,
34
+ INLINE_SUB,
35
+ INLINE_COLOR,
36
+ INLINE_SPOILER,
37
+ INLINE_CODE,
38
+ } element_t;
39
+
40
+ class DTextError : public std::runtime_error {
41
+ using std::runtime_error::runtime_error;
42
+ };
43
+
44
+ struct DTextOptions {
45
+ bool f_inline = false;
46
+ bool allow_color = false;
47
+ int max_thumbs = 25;
48
+ std::string base_url;
49
+ };
50
+
51
+ struct DTextResult {
52
+ std::string dtext;
53
+ std::vector<long> posts;
54
+ };
55
+
56
+ class StateMachine {
57
+ public:
58
+ static DTextResult parse_dtext(const std::string_view dtext, DTextOptions options);
59
+ static std::string parse_basic_inline(const std::string_view dtext);
60
+
61
+ private:
62
+ StateMachine(const std::string_view dtext, int initial_state, const DTextOptions options);
63
+ DTextResult parse();
64
+
65
+ inline void append(const std::string_view c);
66
+ inline void append(const char c);
67
+ inline void append_block(const std::string_view s);
68
+ inline void append_block(const char s);
69
+ inline void append_html_escaped(char s);
70
+ inline void append_html_escaped(const std::string_view input);
71
+ inline void append_uri_escaped(const std::string_view uri_part, const char whitelist = '-');
72
+ inline void append_url(const char* url);
73
+ inline void append_unnamed_url(const std::string_view url);
74
+ inline void append_named_url(const std::string_view url, const std::string_view title);
75
+ inline void append_post_search_link(const std::string_view tag, const std::string_view title);
76
+ inline void append_wiki_link(const std::string_view tag, const std::string_view title);
77
+ inline void append_id_link(const char * title, const char * id_name, const char * url);
78
+ inline void append_section(const std::string_view summary, bool initially_open);
79
+ inline void append_closing_p();
80
+
81
+ inline void dstack_close_leaf_blocks();
82
+ inline void dstack_close_until(element_t element);
83
+ inline void dstack_close_all();
84
+ inline void dstack_close_list();
85
+ inline void dstack_close_inline(element_t type, const char * close_html);
86
+ inline bool dstack_close_block(element_t type, const char * close_html);
87
+ inline void dstack_close_before_block();
88
+
89
+ inline void dstack_open_block(element_t type, const char * html);
90
+ inline void dstack_open_list(int depth);
91
+ inline void dstack_open_inline(element_t type, const char * html);
92
+ inline bool dstack_is_open(element_t element);
93
+ inline void dstack_push(element_t element);
94
+ inline bool dstack_check(element_t expected_element);
95
+ inline void dstack_rewind();
96
+ inline int dstack_count(element_t element);
97
+ inline element_t dstack_peek();
98
+ inline element_t dstack_pop();
99
+
100
+ DTextOptions options;
101
+
102
+ size_t top;
103
+ int cs;
104
+ int act = 0;
105
+ const char * p = NULL;
106
+ const char * pb = NULL;
107
+ const char * pe = NULL;
108
+ const char * eof = NULL;
109
+ const char * ts = NULL;
110
+ const char * te = NULL;
111
+ const char * a1 = NULL;
112
+ const char * a2 = NULL;
113
+ const char * b1 = NULL;
114
+ const char * b2 = NULL;
115
+
116
+ bool header_mode = false;
117
+ int ignored_sup_sub_tags = 0;
118
+
119
+ std::vector<long> posts;
120
+ std::string output;
121
+ std::vector<int> stack;
122
+ std::vector<element_t> dstack;
123
+ };
124
+
125
+ #endif
@@ -0,0 +1,8 @@
1
+ require "mkmf"
2
+
3
+ CONFIG["MKMF_VERBOSE"] = "1"
4
+
5
+ $warnflags = "-Wall -Wextra -Werror -Wno-unused-parameter -Wuninitialized -Wnull-dereference -Wformat=2 -Wformat-overflow=2 -Wstrict-overflow=5"
6
+ $CXXFLAGS << " -std=c++20 -O2 -pipe -flto -fno-strict-aliasing -D_GNU_SOURCE -DNDEBUG"
7
+
8
+ create_makefile "dtext/dtext"
@@ -0,0 +1,54 @@
1
+ #include "dtext.h"
2
+
3
+ #include <ruby.h>
4
+ #include <ruby/encoding.h>
5
+
6
+ static VALUE cDText = Qnil;
7
+ static VALUE cDTextError = Qnil;
8
+
9
+ static VALUE c_parse(VALUE self, VALUE input, VALUE f_inline, VALUE f_allow_color, VALUE f_max_thumbs, VALUE base_url) {
10
+ if (NIL_P(input)) {
11
+ return Qnil;
12
+ }
13
+
14
+ StringValue(input);
15
+
16
+ DTextOptions options = {};
17
+ options.f_inline = RTEST(f_inline);
18
+ options.allow_color = RTEST(f_allow_color);
19
+ options.max_thumbs = FIX2LONG(f_max_thumbs);
20
+
21
+ if (!NIL_P(base_url)) {
22
+ options.base_url = StringValueCStr(base_url); // base_url.to_str # raises ArgumentError if base_url contains null bytes.
23
+ }
24
+
25
+ if (memchr(RSTRING_PTR(input), 0, RSTRING_LEN(input))) {
26
+ rb_raise(cDTextError, "invalid byte sequence in UTF-8");
27
+ }
28
+
29
+ try {
30
+ std::string_view dtext(RSTRING_PTR(input), RSTRING_LEN(input));
31
+ auto result = StateMachine::parse_dtext(dtext, options);
32
+
33
+ VALUE retStr = rb_utf8_str_new(result.dtext.c_str(), result.dtext.size());
34
+ VALUE retPostIds = rb_ary_new_capa(result.posts.size());
35
+
36
+ for (long post_id : result.posts) {
37
+ rb_ary_push(retPostIds, LONG2FIX(post_id));
38
+ }
39
+
40
+ VALUE ret = rb_ary_new_capa(2);
41
+ rb_ary_push(ret, retStr);
42
+ rb_ary_push(ret, retPostIds);
43
+
44
+ return ret;
45
+ } catch (std::exception& e) {
46
+ rb_raise(cDTextError, "%s", e.what());
47
+ }
48
+ }
49
+
50
+ extern "C" void Init_dtext() {
51
+ cDText = rb_define_class("DText", rb_cObject);
52
+ cDTextError = rb_define_class_under(cDText, "Error", rb_eStandardError);
53
+ rb_define_singleton_method(cDText, "c_parse", c_parse, 5);
54
+ }
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DText
4
+ VERSION = "2.0.6"
5
+ end
data/lib/dtext.rb ADDED
@@ -0,0 +1,33 @@
1
+ require "dtext/dtext"
2
+ require "dtext/version"
3
+
4
+ class DText
5
+ class Error < StandardError; end
6
+
7
+ def self.parse(str, inline: false, allow_color: false, max_thumbs: 25, base_url: nil)
8
+ return nil if str.nil?
9
+ raise TypeError unless str.respond_to?(:gsub)
10
+ str = preprocess_for_tables(str)
11
+ c_parse(str, inline, allow_color, max_thumbs, base_url)
12
+ end
13
+
14
+ private
15
+
16
+ def self.preprocess_for_tables(str)
17
+ str.gsub(/(?:\[ltable\])(.*?)(?:\[\/ltable\]|\z)/mi) do
18
+ contents = Regexp.last_match[1].strip
19
+ row_num = 0
20
+ rows = contents.split(/\n/).map do |row|
21
+ cols = row.split(/(?<!\\)\|/).map do |col|
22
+ row_num == 0 ? "[th]#{col}[/th]" : "[td]#{col}[/td]"
23
+ end
24
+ new_row = row_num == 0 ? "[thead][tr]#{cols.join('')}[/tr][/thead][tbody]" : "[tr]#{cols.join('')}[/tr]"
25
+ row_num += 1
26
+ new_row
27
+ end
28
+ "[table]#{rows.join('')}[/tbody][/table]"
29
+ end
30
+ rescue ArgumentError => e
31
+ raise Error.new(e.message)
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dtext
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.6
5
+ platform: ruby
6
+ authors:
7
+ - r888888888
8
+ - evazion
9
+ - earlopain
10
+ - sindrake
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2026-08-12 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: minitest
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - "~>"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.10'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '5.10'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake-compiler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - "~>"
35
+ - !ruby/object:Gem::Version
36
+ version: '1.0'
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '1.0'
44
+ description:
45
+ email:
46
+ executables: []
47
+ extensions:
48
+ - ext/dtext/extconf.rb
49
+ extra_rdoc_files: []
50
+ files:
51
+ - ext/dtext/dtext.cpp
52
+ - ext/dtext/dtext.h
53
+ - ext/dtext/extconf.rb
54
+ - ext/dtext/rb_dtext.cpp
55
+ - lib/dtext.rb
56
+ - lib/dtext/version.rb
57
+ homepage: http://github.com/e621ng/dtext
58
+ licenses:
59
+ - MIT
60
+ metadata:
61
+ homepage_uri: http://github.com/e621ng/dtext
62
+ source_code_uri: http://github.com/e621ng/dtext
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 3.0.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.5.9
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: E621 DText parser
82
+ test_files: []