Best / cleanest DSL for manipulating data files?

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!

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”
:slight_smile:

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.

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 :slight_smile:

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

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:

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.

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.

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)

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