# Best / cleanest DSL for manipulating data files?

**URL:** <https://www.ruby-forum.com/t/best-cleanest-dsl-for-manipulating-data-files/184916>\
**Category:** Ruby\
**Created:** [March 10, 2010, 11:53am UTC](https://www.ruby-forum.com/t/best-cleanest-dsl-for-manipulating-data-files/184916 "2010-03-10T11:53:33Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![dreamcat4](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/848f3c/32.png) [@dreamcat4](https://www.ruby-forum.com/u/dreamcat4)\
**Post date:** [March 10, 2010, 11:53am UTC](https://www.ruby-forum.com/t/best-cleanest-dsl-for-manipulating-data-files/184916/1 "2010-03-10T11:53:33Z")

</div>

Hi,

At the stage of implementing the interface for a new RubyGem. The main  
goal is to make something that is intuitive, easy to use. Its a library  
for manipulating apple plists. So really we are just manipulating a  
file, BUT its of a certain type and predifined structure.

The current feature set includes:

- Read, write, and edit (modify-in-place)
- Can remember operations, but act later (passing in a block), then  
calling ‘finalize’, ‘close’ or something when the work is to be done.
- Different _types_ of plist. Like we can restrict our valid data if our  
Plist is for Launchd, or an Appbundle’s Info.plist
- Whether binary or xml can be autodetected on read. But want to provide  
an option to specify how to write. I don’t really know if that should be  
a seperate class, method, or just an optional parameter?
- Maybe a couple of global options (or not!) Like whether to override /  
enable the subtypes (Info,Launchd) and their validation. Another  
possible option is to allow users to override the default backend and  
specify their own “backend” to use.
- Have already decided / implemented for users to manipulate their plist  
data through the familiar ruby block syntax. eg `plist do setter_methods.. end`. So im not going to change that part.

This exercise is more about avoiding confusion and providing the neatest  
possible interface. In previous work, i have never felt completely  
comfortable passing hashes to the initializer for options. Its just  
something im not very experienced at doing. But that, or anything else  
to make life easier for my users.

Guessing that some of you might yawn and think “ive seen this all before  
somewhere else”. Well its exactly you guys i need to talk to!

---

<div class="post-metadata">

**Author:** ![dreamcat4](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/848f3c/32.png) [@dreamcat4](https://www.ruby-forum.com/u/dreamcat4)\
**Post date:** [March 10, 2010, 12:10pm UTC](https://www.ruby-forum.com/t/best-cleanest-dsl-for-manipulating-data-files/184916/2 "2010-03-10T12:10:33Z")

</div>

You did google for ‘ruby plist’ already?

I’d suggest you try out the various plist packages already out there,  
see what you like and what you dislike from their APIs. Then you can  
implement something which you like better.

As a side benefit, you can use this in your announcement. E.g. “the code  
to do foo takes 20 lines using that package, but only 5 lines in mine”  
🙂

---

<div class="post-metadata">

**Author:** ![dreamcat4](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/848f3c/32.png) [@dreamcat4](https://www.ruby-forum.com/u/dreamcat4)\
**Post date:** [March 10, 2010, 12:27pm UTC](https://www.ruby-forum.com/t/best-cleanest-dsl-for-manipulating-data-files/184916/3 "2010-03-10T12:27:56Z")

</div>

So i’ve been looking at the current API interface to my classes. To be  
honest, Ive been playing with examples all morning, but cannot find a  
way to make them user friendly enough. Hopefully at least one person  
here would be kind enough to suggest to me a regular object to  
initializer eg `.new()` with an options hash?

I wonder a lot how people have done this for other kinds of files. A  
while ago I write a gem for manipulating yaml files called `yamldoc`  
which tackled the problem of reading / writing yaml settings files.

> **[GitHub - dreamcat4/yamldoc: A simple solution for ERB enabled YAML file.](https://github.com/dreamcat4/yamldoc)**
>
> A simple solution for ERB enabled YAML file. Contribute to dreamcat4/yamldoc development by creating an account on GitHub.

Yamldoc uses a very different approach. So im not saying that its  
necessarily applicable to this problem here. Just maybe another example  
to draw from.

How best to solve a problem like this? Can any of you point to an  
examples from elsewhere in the Ruby world?

These are my attempts so-far. Can’t stress enough how im emphatically  
_not at all_ happy with them yet. So don’t waste your time replying to  
say how bad they are, or whats wrong with them. I already know 🙂

But hopefully you can get the idea of what its trying to do.

# 1. First attempt (but none of the options)

launchd\_plist filename do  
label “com.github.homebrew.myprogram”  
program\_arguments [“/usr/bin/myprogram”]  
run\_at\_load true  
working\_directory “/var/db/myprogram”  
standard\_out\_path “/var/log/myprogram.log”

sockets do  
sock\_service\_name “netbios-ssn”  
end  
sockets do  
sock\_service\_name “netbios”  
bonjour [‘smb’]  
end  
end  
plist.finalize

# Module to avoid namespace conflicts

include ::Plist4r

# 2. This is not much different to 1 except the ‘w’ argument

# it doesnt really seem to work for me

p = plist4r(“/Library/LaunchDaemons/org.cups.cupsd.plist”,‘w’)  
p.keys do  
key1 “value1”  
key2 true  
end  
p.finalize

# 3. If a plist is just a file then maybe ::File syntax?

::Plist4r.open(plist,‘w’) do  
key1 “value1”  
key2 true  
end

::Plist4r.open(plist,‘r’) do |hash|  
puts hash.inspect  
end

# 4. Like 3 but we are writing a binary plist?

::Plist4r.open(plist,‘w’, :binary =\>true) do  
key1 “value1”  
key2 true  
end

# 5. Different again

plist = ::Plist4r.open(plist,‘w’, :delayed\_write =\> true) do  
key1 “value1”  
key2 true  
end

plist.format = :binary # force to binary  
plist.finalize # actually reads and writes the plist

---

<div class="post-metadata">

**Author:** ![dreamcat4](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/848f3c/32.png) [@dreamcat4](https://www.ruby-forum.com/u/dreamcat4)\
**Post date:** [March 10, 2010, 12:44pm UTC](https://www.ruby-forum.com/t/best-cleanest-dsl-for-manipulating-data-files/184916/4 "2010-03-10T12:44:30Z")

</div>

Brian C. wrote:

> You did google for ‘ruby plist’ already?

Yes. Although none of them provide the same kind of options that I shall  
be providing. Actually, this gem aims to do is use those as pluggable  
`backends`. Eg for reading/writing a binary plist on OS-X, the gem can  
detect and use RubyCocoa. (which may be faster / natively supported  
Apply code). However for reading / writing a binary plist on Linux, it  
might failover to either Ben’s github gist or ckruse/CFPropertyList (a  
ruby library).

They are here:

> **[GitHub - patsplat/plist: All-purpose Property List manipulation library](https://github.com/patsplat/plist)**
>
> All-purpose Property List manipulation library. Contribute to patsplat/plist development by creating an account on GitHub.

> <https://gist.github.com/bensummers/303378>

> **[GitHub - ckruse/CFPropertyList: Read, write and manipulate both binary and...](https://github.com/ckruse/CFPropertyList)**
>
> Read, write and manipulate both binary and XML property lists as defined by apple - GitHub - ckruse/CFPropertyList: Read, write and manipulate both binary and XML property lists as defined by apple

I also have my own xml based parser / writer which uses libxml / haml.  
Its not known yet which implementations are the more stable, reliable  
and effecient. Hence a pluggable backends strategy kindda seems to make  
sense to me. I’ve already worked with something similar in the GeoKit  
gem.

One thing I was hoping to find out by coming here was:

Maybe someone had written a ruby DSL for manipulating other kinds of  
files. Like .jpeg images, pdf files, or some other neat interface for  
writing their structured data. Then it might help this plist editing  
interface better by taking their lessons learned.

---

<div class="post-metadata">

**Author:** ![dreamcat4](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/848f3c/32.png) [@dreamcat4](https://www.ruby-forum.com/u/dreamcat4)\
**Post date:** [March 10, 2010, 5:51pm UTC](https://www.ruby-forum.com/t/best-cleanest-dsl-for-manipulating-data-files/184916/5 "2010-03-10T17:51:26Z")

</div>

Hmm,  
This seems to be invalid:

p \<\< do  
args \<\< “evaluated”  
end

But this is okay

p.\<\< do  
args \<\< “evaluated”  
end

Well, for REE 1.8.7 interpreter at least.

---

<div class="post-metadata">

**Author:** ![dreamcat4](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/848f3c/32.png) [@dreamcat4](https://www.ruby-forum.com/u/dreamcat4)\
**Post date:** [March 10, 2010, 5:35pm UTC](https://www.ruby-forum.com/t/best-cleanest-dsl-for-manipulating-data-files/184916/6 "2010-03-10T17:35:39Z")

</div>

Standard initializers seem better. Leaning toward b)

# a)

plist\_opts = {  
:filename =\> “car.plist”,  
:type =\> :vehicle\_plist,  
:save\_format =\> binary,  
:autoload =\> true,  
:autosave =\> false  
}

car = Plist4r.new(plist\_opts) {  
road\_legal true  
brake\_light\_color “red”  
}

car.save

# b)

car = Plist4r.new(“car.plist”)

car.type = :vehicle\_plist  
car.load # also detects plist type

# not sure what to call this method

# it overwrites any existing keys,

# appending the new/replacement keys

car \<\< do  
road\_legal true  
brake\_light\_color “red”  
end

car.save(:binary =\> true)

---

<div class="post-metadata">

**Author:** ![dreamcat4](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/848f3c/32.png) [@dreamcat4](https://www.ruby-forum.com/u/dreamcat4)\
**Post date:** [March 13, 2010, 12:41pm UTC](https://www.ruby-forum.com/t/best-cleanest-dsl-for-manipulating-data-files/184916/7 "2010-03-13T12:41:36Z")

</div>

Hi,  
For any of you who were interested, here is the interface which was  
finally settled upon.

# Example 1

Plist4r::Config.default\_dir “/Library/LaunchDaemons”  
filename = “com.github.myservice”  
p = Plist4r.new(filename)

p.\<\< do  
ProgramArguments ["/usr/local/bin/myservice"]  
end

p.edit do  
WatchPaths ["/var/db/myservice"]  
end

p.save

# Example 2

class MyXmlReader \< ::Plist4r::ReaderBase  
end  
Plist4r::Config.readers \<\< MyXmlReader

class MyBinaryWriter \< ::Plist4r::WriterBase  
end  
Plist4r::Config.writers \<\< MyBinaryWriter

Plist4r::Config.default\_dir “/Library/LaunchDaemons”

car = Plist4r.new(“car.plist”)

car.load # autodetects plist file\_format and plist\_type

car.file\_format :binary  
car.plist\_type :car

car.save

car.\<\< do  
road\_legal true  
brake\_light\_color “red”  
end

car.save\_as(“car2.plist”, :binary =\> true)

car.\<\< do  
eyes “blue”  
end

# =\> Exception, invalid plist key name “Eyes”

car.\<\< do  
tyres “Pirelli”  
end
