c0data 0.3.1

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/lib/c0/stream.rb ADDED
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module C0
4
+ # Stream mode: ETB-committed append logs (DESIGN.md "Stream Mode").
5
+ #
6
+ # r = C0::Stream::Reader.new(File.binread("events.c0"))
7
+ # r.torn? # => data trails the last commit
8
+ # r.each_record { |rec| ... }
9
+ module Stream
10
+ # Scans a buffer for commits. An unescaped ETB is always a commit, even
11
+ # inside open text or a level; such a block is damaged and yields nothing.
12
+ # The tail after the last commit is torn unless it holds only layout,
13
+ # notes, EOT or EM. Enumerates the committed blocks' bytes.
14
+ class Reader
15
+ include Enumerable
16
+
17
+ attr_reader :buf, :committed_end
18
+
19
+ def initialize(buf)
20
+ @buf = C0.to_bin(buf)
21
+ @committed_end, @torn, @blocks = Ext.stream(@buf)
22
+ end
23
+
24
+ def self.read(path) = new(File.binread(path))
25
+
26
+ def torn? = @torn
27
+
28
+ # The bytes up to and including the last commit.
29
+ def committed = @buf.byteslice(0, @committed_end)
30
+
31
+ # The bytes after the last commit.
32
+ def tail = @buf.byteslice(@committed_end, @buf.bytesize - @committed_end)
33
+
34
+ def block_count = @blocks.size
35
+
36
+ alias size block_count
37
+
38
+ # Block i's bytes (previous commit to ETB, marker and payload excluded).
39
+ def block(index)
40
+ s, e, = @blocks[index]
41
+ s && @buf.byteslice(s, e - s)
42
+ end
43
+
44
+ def blocks = @blocks.map { |s, e, _| @buf.byteslice(s, e - s) }
45
+
46
+ def each_block(&) = blocks.each(&)
47
+
48
+ alias each each_block
49
+
50
+ # Indexes of the damaged blocks (cut by an ETB inside open text or a level).
51
+ def damaged = @blocks.each_index.select { |i| @blocks[i][2] }
52
+
53
+ def damaged?(index) = @blocks[index] ? @blocks[index][2] : false
54
+
55
+ # The committed region as a document, undamaged blocks only.
56
+ def document = Document.new(Ext.stream_undamaged(@buf))
57
+
58
+ def body = document.body
59
+
60
+ # The committed records as a group (the first group, or an empty one).
61
+ def table
62
+ b = body
63
+ b.groups.first || Group.new("".b, 0, 0, 0, 0, 0)
64
+ end
65
+
66
+ # Every committed record, across all groups.
67
+ def records = body.groups.flat_map(&:records)
68
+
69
+ def each_record(&) = records.each(&)
70
+ end
71
+
72
+ # Position just past the last commit, found by a backward scan: a
73
+ # shortcut for well-formed input (Reader is authoritative).
74
+ def self.last_commit_end(buf) = Ext.last_commit_end(C0.to_bin(buf))
75
+ end
76
+ end
data/lib/c0.rb ADDED
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ # C0DATA 1.0 — structured data using ASCII C0 control codes.
4
+ #
5
+ # A thin Ruby layer over the c0 C core (the C0::Ext extension). The read path
6
+ # is zero-copy: accessors return shared byteslices into the input buffer.
7
+ #
8
+ # buf = C0.build do |b|
9
+ # b.syn(shape: "tabular")
10
+ # b.group("users", %w[name amount]) do
11
+ # b.record("Alice", "1502.30")
12
+ # b.record("Bob", "340.00")
13
+ # end
14
+ # b.eot
15
+ # end
16
+ # doc = C0::Document.new(buf)
17
+ # doc["users"].record(0).value(0) # => "Alice"
18
+ #
19
+ # Reading is a two-step contract, as in the C core: `C0.validate` checks a
20
+ # buffer against the grammar once; the navigators (Document, Body, Group,
21
+ # Record) then assume well-formed input and raise only on the malformations
22
+ # they happen to meet.
23
+
24
+ begin
25
+ require "c0/c0_ext"
26
+ rescue LoadError
27
+ require_relative "../ext/c0/c0_ext"
28
+ end
29
+
30
+ module C0
31
+ # Assigned C0 control codes (DESIGN.md "Assigned Control Codes").
32
+ SOH = 0x01 # Heading: a key for the value after it, or SOH RS a header row
33
+ STX = 0x02 # Start of text (a literal value)
34
+ ETX = 0x03 # End of text
35
+ EOT = 0x04 # End of document
36
+ ENQ = 0x05 # Reference
37
+ BEL = 0x07 # Comment: a note, or marks the next element dead
38
+ DLE = 0x10 # Recurse: opens a nested level (outside text); escape (inside text)
39
+ SYN = 0x16 # Start of document, then the heading
40
+ ETB = 0x17 # Commit marker (stream mode)
41
+ CAN = 0x18 # Close a nested level
42
+ EM = 0x19 # End of medium: a raw blob follows
43
+ SUB = 0x1a # Substitution (C0-DIFF only)
44
+ FS = 0x1c # File separator
45
+ GS = 0x1d # Group separator
46
+ RS = 0x1e # Record separator
47
+ US = 0x1f # Unit separator
48
+
49
+ # Layout codes outside text.
50
+ HT = 0x09
51
+ LF = 0x0a
52
+ CR = 0x0d
53
+ SP = 0x20
54
+
55
+ # What a raw unit holds, by its first non-layout byte (matches the C enum).
56
+ module UnitType
57
+ PLAIN = 0
58
+ TEXT = 1
59
+ LEVEL = 2
60
+ REF = 3
61
+ end
62
+
63
+ module_function
64
+
65
+ # Coerce to a binary (ASCII-8BIT) String without copying when already binary.
66
+ def to_bin(str)
67
+ str = str.to_s unless str.is_a?(String)
68
+ str.encoding == Encoding::BINARY ? str : str.b
69
+ end
70
+
71
+ # Whether a byte is one of the sixteen assigned codes.
72
+ def assigned?(byte) = Ext.is_assigned(byte)
73
+
74
+ # Whether a byte is layout outside text (SP, HT, LF, CR).
75
+ def layout?(byte) = Ext.is_layout(byte)
76
+
77
+ # Whether a byte is a control code (below 0x20).
78
+ def control?(byte) = byte < 0x20
79
+
80
+ # Whether a value can be written plain: no byte below 0x20, no edge whitespace.
81
+ def plain?(value) = Ext.is_plain(to_bin(value))
82
+
83
+ # Whether bytes are a legal name (label, key, header name): the same test as plain.
84
+ def name?(value) = Ext.is_plain(to_bin(value))
85
+
86
+ # Layout trimmed from both ends of a raw slice.
87
+ def trim(raw) = Ext.trim(to_bin(raw))
88
+
89
+ # Check a whole buffer against the grammar. Raises a C0::SyntaxError (or a
90
+ # subclass) on the first error; returns nil when well-formed.
91
+ def validate(buf)
92
+ Ext.validate(to_bin(buf))
93
+ end
94
+
95
+ # Whether bytes are a canonical unit: well-formed, no layout, one spelling
96
+ # per value, unique keys in each keyed record, no framing, no comments.
97
+ def canonical?(buf) = Ext.canonical(to_bin(buf))
98
+
99
+ # The logical value of a raw unit: a plain value trimmed, or the inside of a
100
+ # text with its escapes decoded. Raises C0::Error on a level or reference.
101
+ def value(raw)
102
+ b = to_bin(raw)
103
+ Ext.value(b, 0, b.bytesize)
104
+ end
105
+
106
+ # Decode the inside of a text (the bytes between STX and ETX).
107
+ def decode_text(inside) = Ext.decode_text(to_bin(inside))
108
+
109
+ # Build a buffer by driving a fresh Builder, returning its bytes.
110
+ def build(&) = Builder.build(&)
111
+
112
+ # Pretty form (Unicode Control Pictures) of compact bytes.
113
+ def pretty_format(buf, indent: " ", mode: :compact) = Pretty.format(buf, indent: indent, mode: mode)
114
+
115
+ # Compact bytes from pretty form.
116
+ def pretty_parse(text) = Pretty.parse(text)
117
+ end
118
+
119
+ require_relative "c0/body"
120
+ require_relative "c0/document"
121
+ require_relative "c0/builder"
122
+ require_relative "c0/stream"
123
+ require_relative "c0/pretty"
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: c0data
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Sawyer
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A thin, fast Ruby binding over the c0 C core. The read path is zero-copy;
13
+ the scan-heavy work runs as native C.
14
+ email:
15
+ - transfire@gmail.com
16
+ executables: []
17
+ extensions:
18
+ - ext/c0/extconf.rb
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE
22
+ - README.md
23
+ - c0-c/c0.h
24
+ - ext/c0/c0_ext.c
25
+ - ext/c0/extconf.rb
26
+ - lib/c0.rb
27
+ - lib/c0/body.rb
28
+ - lib/c0/builder.rb
29
+ - lib/c0/document.rb
30
+ - lib/c0/pretty.rb
31
+ - lib/c0/stream.rb
32
+ homepage: https://github.com/c0data/c0-rb
33
+ licenses:
34
+ - MIT
35
+ metadata: {}
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '3.0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.6.9
51
+ specification_version: 4
52
+ summary: C0DATA — structured data using ASCII C0 control codes
53
+ test_files: []