-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Serializable #6
Conversation
1ec5642
to
0991900
Compare
@BigBoyBarney ok I've got an initial working solution, do you mind playing around with it and let me know if it works? One limitation is |
@danini-the-panini thank you for your hard work! I've been playing around with it for a bit, and got it working with varying degrees of success. I think the underlying API is pretty reasonable, very creative usage of annotations! I left a few comments on the code! |
The following document doc = KDL.parse <<-KDL
Collected-Pokemon {
Snorlax "The Sleepy" status=asleep pokemon-level=10
Jigglypuff "The Cute" status=awake pokemon-level=1
}
Trainers {
trainer Sylphrena
trainer DaniniThePanini
}
KDL can be parsed with class Info
include KDL::Serializable
@[KDL::Child(name: "Collected-Pokemon")]
property collected_pokemon : Pokemon
@[KDL::Child(name: "Trainers")]
property trainers : Trainers
end
class Pokemon
include KDL::Serializable
@[KDL::Child(name: "Snorlax")]
property snorlax : PokemonInfo
@[KDL::Child(name: "Jigglypuff")]
property jigglypuff : PokemonInfo
end
class PokemonInfo
include KDL::Serializable
@[KDL::Argument]
property nick : String
@[KDL::Property]
property status : String
@[KDL::Property(name: "pokemon-level")]
property level : Int32
end
class Trainers
include KDL::Serializable
@[KDL::Children(name: "trainer")]
property trainers : Array(Trainer)
end
class Trainer
include KDL::Serializable
@[KDL::Argument]
property name : String
end
obj = Info.from_kdl doc Which is really cool! However, as you can see, to get the actual array of trainers, one would need to call @[KDL::Child(name: "Trainers", unwrap: "children", children_name: "trainer")]
property trainers : Array(String) Or something along these lines. :D |
spec/kdl/serializable_spec.cr
Outdated
def initialize(@first, @second, @numbers, @foo, @bar, @map, @arg, @args, @props, @norf, @things) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is technically not necessary and can be omitted altogether :D
src/kdl/serializable.cr
Outdated
{% elsif value[:unwrap] == "properties" %} | ||
%var{name} = node.child({{name.stringify}}).properties.transform_values { |v, _| convert(v.value, {{ value[:type].type_vars[1] }}) } | ||
{% else %} | ||
%var{name} = {{value[:type]}}.from_kdl(node.child({{name.stringify}})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- %var{name} = {{value[:type]}}.from_kdl(node.child({{name.stringify}}))
+ %var{name} = {{value[:type]}}.from_kdl(node.child({{value[:name]}}))
We have to use value[:name]
otherwise the name
property will have no effect in certain cases.
src/kdl/serializable.cr
Outdated
nilable: ivar.type.nilable?, | ||
type: ivar.type | ||
} | ||
all_properties[ivar.id] = other_children[ivar.id] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
other_children
doesn't exist, so if the code gets here, it will throw a compile time error 100% of the time (which happens whenever a property has no annotation)
I just discovered that including class DB
include KDL::Serializable
@[KDL::Argument(name: "Version")]
property version = 1
@[KDL::Argument(name: "Path")]
property path = "~/.local/share/anime_database.db"
end
43 | property db = DB.new
^--
Error: wrong number of arguments for 'Shisho::Util::DB.new' (given 0, expected 1)
Overloads are:
- Shisho::Util::DB.new(node : ::KDL::Node)
- Shisho::Util::DB.new(doc : ::KDL::Document) This can be circumvented by defining an empty |
src/kdl/serializable.cr
Outdated
{% for name, value in child_annos %} | ||
# child | ||
{% if value[:unwrap] == "argument" %} | ||
%var{name} = convert(node.arg({{name.stringify}}), {{ value[:type] }}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as below
src/kdl/serializable.cr
Outdated
{% if value[:unwrap] == "argument" %} | ||
%var{name} = convert(node.arg({{name.stringify}}), {{ value[:type] }}) | ||
{% elsif value[:unwrap] == "arguments" %} | ||
%var{name} = node.args({{name.stringify}}).map { |v| convert(v, {{ value[:type].type_vars[0] }}) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as below
src/kdl/serializable.cr
Outdated
{% elsif value[:unwrap] == "arguments" %} | ||
%var{name} = node.args({{name.stringify}}).map { |v| convert(v, {{ value[:type].type_vars[0] }}) } | ||
{% elsif value[:unwrap] == "properties" %} | ||
%var{name} = node.child({{name.stringify}}).properties.transform_values { |v, _| convert(v.value, {{ value[:type].type_vars[1] }}) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as below
obj.things[0].value.should eq "foo" | ||
obj.things[1].value.should eq "bar" | ||
obj.things[2].value.should eq "baz" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This syntax is quite common, e.g.:
some-paths {
path "path/to/something"
path "path/to/something/else"
}
Having to call #value
on it is a bit cumbersome. Directly newing up Array(String)
for such cases would be nicer. This is related to my other comment about arrays in serializable.cr
:D
src/kdl/serializable.cr
Outdated
def Object.from_kdl(doc) | ||
new doc | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be global, but should be in the module instead, as we can only call #from_kdl
on Types that include ::KDL::Serializable
src/kdl/serializable.cr
Outdated
def Array.from_kdl(node : ::KDL::Node) | ||
node.arguments.map do |arg| | ||
arg.value | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is needed?
type: ivar.type, | ||
converter: ann[:converter], | ||
presence: ann[:presence] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unwrap: ann[:unwrap]
together with the change further down at #children
enables child array unwrapping
src/kdl/serializable.cr
Outdated
%var{name} = node.children.select { |n| n.name == {{value[:name]}} }.map { |n| {{value[:type].type_vars[0]}}.from_kdl(n) } | ||
%found{name} = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replace with
# children
{% if value[:unwrap] == "argument" %}
%var{name} = node.children.select { |n| n.name == {{value[:name]}} }.map { |n| convert(n.arguments[0].value, {{value[:type].type_vars[0]}}) }
{% else %}
%var{name} = node.children.select { |n| n.name == {{value[:name]}} }.map { |n| {{value[:type].type_vars[0]}}.from_kdl(n) }
{% end %}
%found{name} = true
To allow argument unwrapping for example. Can be extended to allow properties and multiples arguments too
Code comments are getting a bit out of hand, especially without permission to propose changes, so I propose that you merge whatever you feel like merging, and I'll PR the suggested changes :D |
@BigBoyBarney I've added your suggestions, and I've add a new
@[KDL::Child(unwrap: "dash_vals")
property things : Array(String) |
Looks overall good to me! This would effectively be As an example, see provisional KDL config file and the corresponding class The only other thing worth mentioning is my 2nd to last comment in def Object.from_kdl(doc)
new doc
end should not be defined globally. This has to be in the included macro, otherwise this defines a |
Ok, I've added more of the unwrap options you suggested |
Looks good to me! Parses my app's config file out of the box now. Excellent work, thank you for your time and effort! |
def self.new(node : ::KDL::Node) | ||
new_from_kdl_node(node) | ||
end | ||
|
||
def self.new(doc : ::KDL::Document) | ||
new_from_kdl_node(::KDL::Node.new("__root", children: doc)) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An additional initialize method would be needed to allow newing up fully defined classes / structs.
def initialize
end
Otherwise something like
class Mappings
include KDL::Serializable
@[KDL::Property(name: "update-frequency")]
property update_frequency = 1
@[KDL::Property(name: "last-update")]
property last_update = 0
end
will complain that Mappings.new
needs an argument.
Error: wrong number of arguments for 'Shisho::Util::Mappings.new' (given 0, expected 1)
Overloads are:
- Shisho::Util::DB.new(node : ::KDL::Node)
- Shisho::Util::DB.new(doc : ::KDL::Document)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually this breaks the spec, so will have to figure something else out 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we can't have a default initializer unless all properties have a default value. Alternatively, we ditch the new
and just do everything through from_kdl
Just realiased this has to go through new
anyway because it is constructing an object.
Users of Serializable
will just have to add their own explicit constructors then
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any way would work for me :D Currently I'm manually defining a default initializer in all fully defined classes to make it compile, but an automatic solution would be cool
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll see what I can do. If all ivars have default values I can auto-define a default initializer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok not sure how to get it to work unfortunately :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was incorrectly under the assumption that ::JSON::Serializable
did it automatically. It does not and requires a manual def initialize; end
as well.
Never mind then :D
If you ask me, I think the PR is ready to be merged in that case. Excellent work! 👏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for all the help!
resolves #2