writing your own dsl

41
Writing your own DSL Yes, it is that easy!

Upload: rob-kinyon

Post on 22-Jan-2018

435 views

Category:

Internet


0 download

TRANSCRIPT

Page 1: Writing your own DSL

Writing your own DSLYes, it is that easy!

Page 2: Writing your own DSL
Page 3: Writing your own DSL

Who am I?

● Rob Kinyon○ @rkinyon○ [email protected]

● Devops lead for many years● Developer in Ruby, Python, Perl, JS, and

others.

Page 4: Writing your own DSL

What is a DSL?

● Domain-Specific Language

Page 5: Writing your own DSL

What is a DSL?

● Domain-Specific Language● Language - A vehicle for communication

Page 6: Writing your own DSL

What is a DSL?

● Domain-Specific Language● Language - A vehicle for communication● Domain - A restrained set of concepts

Page 7: Writing your own DSL

What is a DSL?

● Domain-Specific Language● Language - A vehicle for communication● Domain - A restrained set of concepts● Specific - Limited to.

Page 8: Writing your own DSL

What is a DSL?

● Domain-Specific Language● Language - A vehicle for communication● Domain - A restrained set of concepts● Specific - Limited to.

○ No, really. :)

Page 9: Writing your own DSL

Language and Communication

● Communicate in one direction○ Author -> Executor

Page 10: Writing your own DSL

Language and Communication

● Communicate in two directions○ Author -> Executor○ Author -> Maintainer

Page 11: Writing your own DSL

Language and Communication

● Communicate in three directions○ Author -> Executor○ Author -> Maintainer○ Specifier -> Author

Page 12: Writing your own DSL

Language and Communication

● Communicate in four directions○ Author -> Executor○ Author -> Maintainer○ Specifier -> Author○ Author -> Verifier

Page 13: Writing your own DSL

Language and Communication

● Communicate in MANY directions○ Author -> Executor○ Author -> Maintainer○ Specifier -> Author○ Author -> Verifier○ Author -> Teammate(s)○ Developer -> Sysadmin/Devops○ … -> …

Page 14: Writing your own DSL

Language and Communication

● Communicate in MANY directions○ Author -> Executor○ Author -> Maintainer○ Specifier -> Author○ Author -> Verifier○ Author -> Teammate(s)○ Developer -> Sysadmin/Devops○ … -> …

The ONLY computer

Page 15: Writing your own DSL

Language and Communication

● Communicate in MANY directions○ Author -> Executor○ Author -> Maintainer○ Specifier -> Author○ Author -> Verifier○ Author -> Teammate(s)○ Developer -> Sysadmin/Devops○ … -> …

All humans

Page 16: Writing your own DSL

Language and Communication

● Communicate in MANY directions○ Author -> Executor○ Author <-> Maintainer○ Specifier <-> Author○ Author <-> Verifier○ Author <-> Teammate(s)○ Developer <-> Sysadmin/Devops○ … <-> …

All human communication is two-way

Page 17: Writing your own DSL

Domain-Specific

● Eskimos supposedly have 50+ words for “snow”○ Depends on how you count it

● Saami has 1000+ words dealing with reindeer○ snarri - a reindeer with short, branched horns○ busat - a bull with a single, large testicle

Page 18: Writing your own DSL

Domain-specific : Busat

Busat - The quality of having appropriately-specific expressiveness for the domain.

Page 19: Writing your own DSL

DSLs you already use

● SQL○ set manipulation DSL

● CSS○ tree-visitor-defining DSL for setting metadata

● HAML○ HTML-definition DSL

● Bash○ A crappy way of issue shell commands with logic

Page 20: Writing your own DSL

Places for a DSL

● Packaging and orchestration○ most devops/operations activities

● Configuration file generation○ web servers○ monitoring○ datastores

● Configuration value management across environments● Anything extremely complicated (such as SQL)● Anything repetitive (such as CSS)

Page 21: Writing your own DSL

Reasons for a DSL

● Let the important things shine● General-purpose is overly-verbose● Bugs hide in boilerplate● Non-developers can read and comprehend

○ And maybe even propose changes through PRs?

Page 22: Writing your own DSL

Reasons for a DSL

DSL is to Rubyas

Ruby is to Java

Page 23: Writing your own DSL

Writing a DSL

● Three passes○ Parsing○ Validation○ Production

Page 24: Writing your own DSL

Writing a DSL - Parsing

● DSL::Maker for parsing

Page 25: Writing your own DSL

Car = Struct.new(:make, :year, :engine)Engine = Struct.new(:hemi)

class VehicleDSL < DSL::Maker add_entrypoint(:car, { :make => String, :year => Integer, :engine => generate_dsl({ :hemi => Boolean, }) do Engine.new(hemi) end, }) do |*args| default(:make, args, 0) Car.new(make, model, engine) endend

car { make ‘Accord’ year 1990 engine { hemi Yes }}

car ‘Civic’ { year 2014}

Page 26: Writing your own DSL

Car = Struct.new(:make, :year, :engine)Engine = Struct.new(:hemi)

class VehicleDSL < DSL::Maker add_entrypoint(:car, { :make => String, :year => Integer, :engine => generate_dsl({ :hemi => Boolean, }) do Engine.new(hemi) end, }) do |*args| default(:make, args, 0) Car.new(make, model, engine) endend

car { make ‘Accord’ year 1990 engine { hemi Yes }}

car ‘Civic’ { year 2014}

Page 27: Writing your own DSL

Car = Struct.new(:make, :year, :engine)Engine = Struct.new(:hemi)

class VehicleDSL < DSL::Maker add_entrypoint(:car, { :make => String, :year => Integer, :engine => generate_dsl({ :hemi => Boolean, }) do Engine.new(hemi) end, }) do |*args| default(:make, args, 0) Car.new(make, model, engine) endend

car { make ‘Accord’ year 1990 engine { hemi Yes }}

car ‘Civic’ { year 2014}

Page 28: Writing your own DSL

Car = Struct.new(:make, :year, :engine)Engine = Struct.new(:hemi)

class VehicleDSL < DSL::Maker add_entrypoint(:car, { :make => String, :year => Integer, :engine => generate_dsl({ :hemi => Boolean, }) do Engine.new(hemi) end, }) do |*args| default(:make, args, 0) Car.new(make, model, engine) endend

car { make ‘Accord’ year 1990 engine { hemi Yes }}

car ‘Civic’ { year 2014}

Page 29: Writing your own DSL

Car = Struct.new(:make, :year, :engine)Engine = Struct.new(:hemi)

class VehicleDSL < DSL::Maker add_entrypoint(:car, { :make => String, :year => Integer, :engine => generate_dsl({ :hemi => Boolean, }) do Engine.new(hemi) end, }) do |*args| default(:make, args, 0) Car.new(make, model, engine) endend

car { make ‘Accord’ year 1990 engine { hemi Yes }}

car ‘Civic’ { year 2014}

Page 30: Writing your own DSL

Car = Struct.new(:make, :year, :engine)Engine = Struct.new(:hemi)

class VehicleDSL < DSL::Maker add_entrypoint(:car, { :make => String, :year => Integer, :engine => generate_dsl({ :hemi => Boolean, }) do Engine.new(hemi) end, }) do |*args| default(:make, args, 0) Car.new(make, model, engine) endend

car { make ‘Accord’ year 1990 engine { hemi Yes }}

car ‘Civic’ { year 2014}

Page 31: Writing your own DSL

#!/usr/bin/env ruby

require ‘vehicle/dsl’

filename = ARGV.shift || raise “No filename provided.”

vehicles = Vehicle::DSL.parse_dsl( IO.read(filename),)

# Do something here with vehicles

[ Car[ :make => ‘Accord’, :year => 1990, :engine => Engine[ :hemi => true, ], ], Car[ :make => Civic, :year => 2014, :engine => nil, ],]

Page 32: Writing your own DSL

. . .

truck ‘F-150’ { year 1999}

. . .

. . .Truck = Struct.new(:make, :year, :engine). . .class VehicleDSL < DSL::Maker . . . add_entrypoint(:truck, { :make => String, :year => Integer, :engine => . . ., }) do |*args| default(:make, args, 0) Truck.new(make, model, nil) endend

Page 33: Writing your own DSL

#!/usr/bin/env ruby

require ‘vehicle/dsl’

filename = ARGV.shift || raise “No filename provided.”

vehicles = Vehicle::DSL.parse_dsl( IO.read(filename),)

# Do something here with vehicles

[ . . . Truck[ :make => ‘F-150’, :year => 1999, :engine => nil ], . . .]

Page 34: Writing your own DSL

Writing a DSL - Validation

● DSL::Maker for parsing● DSL::Maker for validation

Page 35: Writing your own DSL

. . .

class VehicleDSL < DSL::Maker . . . add_validation(:car) do |car| unless car.engine return “Cars must have an engine” end endend

car { make ‘Accord’ year 1990 engine { hemi Yes }}

car ‘Civic’ { year 2014}

Page 36: Writing your own DSL

. . .

class VehicleDSL < DSL::Maker . . . add_validation(:car) do |car| unless car.engine return “Cars must have an engine” end endend

car { make ‘Accord’ year 1990 engine { hemi Yes }}

car ‘Civic’ { year 2014}

Page 37: Writing your own DSL

#!/usr/bin/env ruby

require ‘vehicle/dsl’

filename = ARGV.shift || raise “No filename provided.”

# This raises the errorvehicles = Vehicle::DSL.parse_dsl( IO.read(filename),)

# Do something here with vehicles

Error: Cars must have an engine

Page 38: Writing your own DSL

Writing a DSL - Production

● DSL::Maker for parsing● DSL::Maker for validation● You’re on your own for production

Page 39: Writing your own DSL

Writing a DSL - Production

● Work from outside in.○ Parsing is done inside-out.

● Transform in a series of passes.○ Expand everything (it’s just data)

● Don’t do anything irrevocable until the end○ Work in temp directories, stage everything

Page 40: Writing your own DSL

Conclusion

● DSL::Maker 0.1.0 is available right now● Patches welcome

○ 100% test coverage● I’m blogging about this at http:

//streamlined-book.blogspot.com○ First post on the topic

Page 41: Writing your own DSL

Questions?