Skip to content

Commit

Permalink
Merge pull request #13 from danini-the-panini/kdl-2
Browse files Browse the repository at this point in the history
KDL 2.0
  • Loading branch information
danini-the-panini authored Dec 24, 2024
2 parents 8fb8455 + 98bef46 commit 9ec4fae
Show file tree
Hide file tree
Showing 66 changed files with 3,032 additions and 655 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
test:
strategy:
matrix:
ruby: [2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, head]
ruby: [3.1, 3.2, 3.3, head]

runs-on: ubuntu-latest

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
Gemfile.lock

lib/kdl/kdl.tab.rb
lib/kdl/v1/kdl.tab.rb
kdl.output
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
[submodule "test/kdl-org"]
path = test/kdl-org
url = [email protected]:kdl-org/kdl
[submodule "test/v1/kdl-org"]
path = test/v1/kdl-org
url = [email protected]:kdl-org/kdl
branch = release/v1
56 changes: 49 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,64 @@ Or install it yourself as:
```ruby
require 'kdl'

KDL.parse_document(a_string) #=> KDL::Document
KDL.parse(a_string) #=> KDL::Document
KDL.load_file('path/to/file') #=> KDL::Document
```

You can optionally provide your own type annotation handlers:

```ruby
KDL.parse_document(a_string, type_parsers: {
'foo' => -> (value, type) {
Foo.new(value.value, type: type)
}
class Foo < KDL::Value::Custom
end

KDL.parse(a_string, type_parsers: {
'foo' => Foo
})
```

The `foo` proc will be called with instances of Value or Node with the type annotation `(foo)`.
The `foo` custom type will be called with instances of Value or Node with the type annotation `(foo)`.

Custom types are expected to have a `call` method that takes the Value or Node, and the type annotation itself, as arguments, and is expected to return either an instance of `KDL::Value::Custom` or `KDL::Node::Custom` (depending on the input type) or `nil` to return the original value as is. Take a look at [the built in custom types](lib/kdl/types) as a reference.

You can also disable type annotation parsing entirely (including the built in ones):

```ruby
KDL.parse(a_string, parse_types: false)
```

## KDL v1

kdl-rb maintains backwards compatibility with the KDL v1 spec. By default, KDL will attempt to parse a file with the v1 parser if it fails to parse with v2. This behaviour can be changed by specifying the `version` option:

```ruby
KDL.parse(a_string, version: 2)
```

The resulting document will also serialize back to the same version it was parsed as. For example, if you parse a v2 document and call `to_s` on it, it will output a v2 document, and similarly with v1. This behaviour can be changed by specifying the `output_version` option:

```ruby
KDL.parse(a_string, output_version: 2)
```

This allows you to to convert document between versions:

```ruby
KDL.parse('foo "bar" true', version: 1, output_version: 2).to_s #=> 'foo bar #true'
```

You can also set the default version globally:

Parsers are expected to have a `call` method that takes the Value or Node, and the type annotation itself, as arguments, and is expected to return either an instance of Value or Node (depending on the input type) or `nil` to return the original value as is. Take a look at [the built in parsers](lib/kdl/types) as a reference.
```ruby
KDL.default_version = 2
KDL.default_output_version = 2
```

Version directives are also respected:

```ruby
KDL.parse("/- kdl-version 2\nfoo bar", version: 1)
#=> Version mismatch, document specified v2, but this is a v1 parser (Racc::ParseError)
```

## Development

Expand Down
7 changes: 6 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ file 'lib/kdl/kdl.tab.rb' => ['lib/kdl/kdl.yy'] do
end
task :racc => 'lib/kdl/kdl.tab.rb'

Rake::TestTask.new(:test => :racc) do |t|
file 'lib/kdl/v1/kdl.tab.rb' => ['lib/kdl/v1/kdl.yy'] do
raise "racc command failed (v1)" unless system 'bin/racc lib/kdl/v1/kdl.yy'
end
task :racc_v1 => 'lib/kdl/v1/kdl.tab.rb'

Rake::TestTask.new(:test => [:racc, :racc_v1]) do |t|
t.libs << 'test'
t.libs << 'lib'
t.test_files = FileList['test/**/*_test.rb']
Expand Down
2 changes: 1 addition & 1 deletion bin/kdl
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ require "kdl"

system 'bin/rake racc'

puts KDL.parse_document(ARGF.read).to_s
puts KDL.parse(ARGF.read).to_s
4 changes: 2 additions & 2 deletions kdl.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
spec.description = %q{Ruby implementation of the KDL Document Language Spec}
spec.homepage = "https://kdl.dev"
spec.license = "MIT"
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0")

spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "https://github.com/danini-the-panini/kdl-rb"
Expand All @@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end + ['lib/kdl/kdl.tab.rb']
end + ['lib/kdl/kdl.tab.rb', 'lib/kdl/v1/kdl.tab.rb']
spec.bindir = "exe"
spec.require_paths = ["lib"]

Expand Down
41 changes: 40 additions & 1 deletion lib/kdl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,49 @@
require "kdl/node"
require "kdl/string_dumper"
require "kdl/types"
require "kdl/parser_common"
require "kdl/kdl.tab"
require "kdl/v1"

module KDL
class << self
attr_accessor :default_version
attr_accessor :default_output_version
end

def self.parse_document(input, options = {})
Parser.new.parse(input, options)
warn "[DEPRECATION] `KDL.parse_document' is deprecated. Please use `KDL.parse' instead."
parse(input, **options)
end

def self.parse(input, version: default_version, output_version: default_output_version, **options)
case version
when 2
Parser.new(output_module: output_module(output_version || 2), **options).parse(input)
when 1
V1::Parser.new.parse(input, output_module: output_module(output_version || 1), **options)
else
auto_parse(input, output_version:, **options)
end
end

def self.load_file(filespec, **options)
parse(File.read(filespec, encoding: Encoding::UTF_8), **options)
end

def self.auto_parse(input, output_version: default_output_version, **options)
parse(input, version: 2, output_version: output_version || 2, **options)
rescue => e
parse(input, version: 1, output_version: output_version || 1, **options) rescue raise e
end

def self.output_module(version)
case version
when 1 then KDL::V1
when 2 then KDL
else
warn "Unknown output_version `#{version}', defaulting to v2"
KDL
end
end
end
60 changes: 58 additions & 2 deletions lib/kdl/document.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,75 @@
module KDL
class Document
include Enumerable

attr_accessor :nodes

def initialize(nodes)
@nodes = nodes
end

def [](key)
case key
when Integer
nodes[key]
when String, Symbol
nodes.find { _1.name == key.to_s }
else
raise ArgumentError, "document can only be indexed by Integer, String, or Symbol"
end
end

def arg(key)
self[key]&.arguments&.first&.value
end

def args(key)
self[key]&.arguments&.map(&:value)
end

def each_arg(key, &block)
args(key)&.each(&block)
end

def dash_vals(key)
self[key]
&.children
&.select { _1.name == "-" }
&.map { _1.arguments.first&.value }
end

def each_dash_val(key, &block)
dash_vals(key)&.each(&block)
end

def each(&block)
nodes.each(&block)
end

def to_s
@nodes.map(&:to_s).join("\n") + "\n"
nodes.map(&:to_s).join("\n") + "\n"
end

def inspect
nodes.map(&:inspect).join("\n") + "\n"
end

def ==(other)
return false unless other.is_a?(Document)

nodes == other.nodes
end

def version
2
end

def to_v2
self
end

def to_v1
KDL::V1::Document.new(nodes.map(&:to_v1))
end
end
end
Loading

0 comments on commit 9ec4fae

Please sign in to comment.