whipped-cream 0.0.1pre1

Sign up to get free protection for your applications and to get access to all the features.
data/.cane ADDED
@@ -0,0 +1,3 @@
1
+ --abc-max 10
2
+ --color
3
+ --gte coverage/.last_run.json,95
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - ruby-head
6
+ matrix:
7
+ allow_failures:
8
+ - rvm: ruby-head
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ ruby '1.9.3'
4
+
5
+ gemspec
6
+
7
+ group :development do
8
+ gem 'guard-cane'
9
+ gem 'guard-rspec'
10
+ end
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ guard :rspec, all_on_start: true, all_after_pass: true, env: { skip_coverage: true } do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
6
+
7
+ guard :cane do
8
+ watch(/.*\.rb/)
9
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Justin Campbell
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # whipped-cream
2
+
3
+ ## DSL
4
+
5
+ ```rb
6
+ name "Garage"
7
+
8
+ camera
9
+
10
+ button "Open/Close", pin: 1 do
11
+ tap
12
+ end
13
+
14
+ sensor "Door",
15
+ pin: 2,
16
+ low: "Closed",
17
+ high: "Open",
18
+ on_high: :door_opened
19
+
20
+ sensor "Temperature", pin: 3, unit: "F" do
21
+ binary_to_farenheit(value)
22
+ end
23
+
24
+ helpers do
25
+ def binary_to_farenheit(binary)
26
+ binary.to_f * 123.45
27
+ end
28
+
29
+ def door_opened
30
+ WhippedCream.find("Front Door").ring_bell
31
+ end
32
+ end
33
+ ```
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require 'cane/rake_task'
3
+ require 'rspec/core/rake_task'
4
+
5
+ task default: :ci
6
+
7
+ task ci: [:spec, :cane]
8
+
9
+ Cane::RakeTask.new
10
+ RSpec::Core::RakeTask.new
data/WELCOME ADDED
@@ -0,0 +1,14 @@
1
+
2
+ Thanks for installing whipped-cream! Please don't hesitate to report bugs
3
+ and feature requests at:
4
+
5
+ https://github.com/justincampbell/whipped-cream/issues
6
+
7
+ To run a demo, just do:
8
+
9
+ whipped-cream demo
10
+
11
+ Or use the help command to see more options:
12
+
13
+ whipped-cream help
14
+
data/bin/whipped-cream ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'whipped-cream'
6
+
7
+ WhippedCream::CLI.start
data/demo.rb ADDED
@@ -0,0 +1,9 @@
1
+ name "Demo"
2
+
3
+ button "Open/Close", pin: 1
4
+
5
+ sensor "Door", pin: 2, high: "Open", low: "Closed"
6
+
7
+ sensor "Ruby Version" do
8
+ RUBY_VERSION
9
+ end
@@ -0,0 +1,8 @@
1
+ require "whipped-cream/builder"
2
+ require "whipped-cream/button"
3
+ require "whipped-cream/cli"
4
+ require "whipped-cream/plugin"
5
+ require "whipped-cream/runner"
6
+ require "whipped-cream/sensor"
7
+ require "whipped-cream/server"
8
+ require "whipped-cream/version"
@@ -0,0 +1,60 @@
1
+ module WhippedCream
2
+ # Creates Plugin instances from a DSL
3
+ class Builder
4
+ def self.build(&block)
5
+ builder = new(&block)
6
+ builder.build
7
+ builder.plugin
8
+ end
9
+
10
+ def self.from_file(path)
11
+ contents = File.read(path)
12
+
13
+ from_string(contents)
14
+ end
15
+
16
+ def self.from_string(string)
17
+ builder = new(string)
18
+ builder.build
19
+ builder.plugin
20
+ end
21
+
22
+ attr_reader :block, :string
23
+
24
+ def initialize(string = nil, &block)
25
+ @block = block
26
+ @string = string
27
+ end
28
+
29
+ def build
30
+ @build ||= string ?
31
+ instance_eval(string) :
32
+ instance_eval(&block)
33
+ end
34
+
35
+ def plugin
36
+ @plugin ||= Plugin.new
37
+ end
38
+
39
+ def button(name, options = {}, &block)
40
+ options = options.merge({ block: block })
41
+ plugin.controls << Button.new(name, options)
42
+ end
43
+
44
+ def camera
45
+ plugin.camera = true
46
+ end
47
+
48
+ def helpers(&block)
49
+ plugin.instance_eval(&block)
50
+ end
51
+
52
+ def name(string)
53
+ plugin.name = string
54
+ end
55
+
56
+ def sensor(name, options = {}, &block)
57
+ plugin.controls << Sensor.new(name, options, &block)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,15 @@
1
+ require 'whipped-cream/control'
2
+
3
+ module WhippedCream
4
+ # A Button represents a one-time action, such as momentarily turning on a
5
+ # pin, or sending a message to an object
6
+ class Button < Control
7
+ attr_reader :name, :pin, :block
8
+
9
+ def initialize(name, options = {})
10
+ @name = name
11
+ @pin = options[:pin]
12
+ @block = options[:block]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ require 'thor'
2
+
3
+ module WhippedCream
4
+ # The CLI gets invoked from the binary, and encapsulates all user interaction
5
+ # logic
6
+ class CLI < Thor
7
+ default_task :usage
8
+
9
+ desc "usage", "Display usage banner", hide: true
10
+ def usage
11
+ puts [
12
+ "whipped-cream #{WhippedCream::VERSION}",
13
+ "https://github.com/justincampbell/whipped-cream"
14
+ ].join("\n")
15
+
16
+ puts "\n"
17
+
18
+ help
19
+ end
20
+
21
+ desc "demo", "Start the demo plugin"
22
+ def demo
23
+ plugin = Plugin.from_file(File.expand_path('../../../demo.rb', __FILE__))
24
+ server = Server.new(plugin)
25
+ server.start
26
+ end
27
+
28
+ desc "start PLUGIN", "Start a plugin"
29
+ def start(plugin_name)
30
+ plugin_path = resolve_plugin(plugin_name)
31
+
32
+ plugin = Plugin.from_file(plugin_path)
33
+ server = Server.new(plugin)
34
+ server.start
35
+ end
36
+
37
+ no_tasks do
38
+ def resolve_plugin(name)
39
+ name # TODO: resolve name to filename
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,12 @@
1
+ module WhippedCream
2
+ # An abstract class for controls to inherit from
3
+ class Control
4
+ def id
5
+ name.downcase.gsub(/[^\w]+/, '_').gsub(/^_|_$/, '').to_sym
6
+ end
7
+
8
+ def type
9
+ self.class.to_s.split('::').last.downcase.to_sym
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ # Fake PiPiper
2
+ module PiPiper
3
+ # Just enough to imitate a pin
4
+ class Pin
5
+ attr_reader :pin, :direction, :value
6
+
7
+ def initialize(options)
8
+ @pin = options[:pin]
9
+ @direction = options[:direction]
10
+ end
11
+
12
+ def on
13
+ @value = :on
14
+
15
+ log "Pin #{pin} on"
16
+ end
17
+
18
+ def off
19
+ @value = :off
20
+
21
+ log "Pin #{pin} off"
22
+ end
23
+
24
+ def log(message)
25
+ puts message unless ENV['RUBY_ENV'] == 'test'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,38 @@
1
+ module WhippedCream
2
+ # Data representation of a Plugin
3
+ class Plugin
4
+ attr_accessor :camera, :name
5
+
6
+ def self.build(&block)
7
+ Builder.build(&block)
8
+ end
9
+
10
+ def self.from_file(path)
11
+ Builder.from_file(path)
12
+ end
13
+
14
+ def self.from_string(string)
15
+ Builder.from_string(string)
16
+ end
17
+
18
+ def controls
19
+ @controls ||= []
20
+ end
21
+
22
+ def buttons
23
+ controls.select { |control| control.is_a? Button }
24
+ end
25
+
26
+ def fields
27
+ controls.select { |control| control.is_a? Field }
28
+ end
29
+
30
+ def sensors
31
+ controls.select { |control| control.is_a? Sensor }
32
+ end
33
+
34
+ def switches
35
+ controls.select { |control| control.is_a? Switch }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ body {
2
+ background-color: white;
3
+ font-family: sans-serif;
4
+ font-size: 16pt;
5
+ }
6
+
7
+ a {
8
+ text-decoration: none;
9
+ }
@@ -0,0 +1,79 @@
1
+ begin
2
+ require 'pi_piper'
3
+ rescue LoadError
4
+ require 'whipped-cream/pi_piper'
5
+ end
6
+
7
+ module WhippedCream
8
+ # Actor that manages all interaction with a plugin
9
+ class Runner
10
+ def self.instance
11
+ @instance
12
+ end
13
+
14
+ def self.create_instance(plugin)
15
+ @instance = new(plugin)
16
+ end
17
+
18
+ attr_reader :plugin
19
+
20
+ def initialize(plugin)
21
+ @plugin = plugin
22
+
23
+ configure
24
+ end
25
+
26
+ def name
27
+ plugin.name
28
+ end
29
+
30
+ def pins
31
+ @pins ||= {}
32
+ end
33
+
34
+ private
35
+
36
+ def configure
37
+ configure_buttons
38
+ configure_sensors
39
+ end
40
+
41
+ def configure_buttons
42
+ plugin.buttons.each do |button|
43
+ create_pin button, direction: :out
44
+
45
+ define_singleton_method button.id do
46
+ pin = pins[button.id]
47
+
48
+ pin.on
49
+ sleep 0.25
50
+ pin.off
51
+ end
52
+ end
53
+ end
54
+
55
+ def configure_sensors
56
+ plugin.sensors.each do |sensor|
57
+ create_pin sensor, direction: :in
58
+
59
+ define_singleton_method sensor.id do
60
+ if sensor.pin
61
+ pin = pins[sensor.id]
62
+
63
+ pin.value == 1 ? sensor.high : sensor.low
64
+ else
65
+ sensor.block.call
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ def create_pin(control, options = {})
72
+ return unless control.pin
73
+
74
+ options[:pin] = control.pin
75
+
76
+ pins[control.id] = PiPiper::Pin.new(options)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,21 @@
1
+ require 'whipped-cream/control'
2
+
3
+ module WhippedCream
4
+ # A Sensor displays the state of something, such as a pin's value, or the
5
+ # return value of the method. Sensors can also have callbacks associated with
6
+ # state changes.
7
+ class Sensor < Control
8
+ attr_reader :name, :pin, :low, :high, :on_low, :on_high, :block
9
+
10
+ def initialize(name, options = {}, &block)
11
+ @name = name
12
+ @pin = options[:pin]
13
+ @low = options[:low]
14
+ @high = options[:high]
15
+ @on_low = options[:on_low]
16
+ @on_high = options[:on_high]
17
+
18
+ @block = block
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,80 @@
1
+ require 'sinatra'
2
+
3
+ module WhippedCream
4
+ # A server handles building a plugin/runner and starting a web server
5
+ class Server
6
+ attr_reader :plugin
7
+
8
+ def initialize(plugin)
9
+ @plugin = plugin
10
+ end
11
+
12
+ def start
13
+ ensure_routes_built
14
+ ensure_runner_started
15
+
16
+ start_web
17
+ end
18
+
19
+ def runner
20
+ @runner ||= Runner.create_instance(plugin)
21
+ end
22
+
23
+ def port
24
+ 8080
25
+ end
26
+
27
+ def web
28
+ @web ||= Web
29
+ end
30
+
31
+ private
32
+
33
+ def ensure_runner_started
34
+ runner
35
+ end
36
+
37
+ def ensure_routes_built
38
+ @routes_built ||= build_routes || true
39
+ end
40
+
41
+ def start_web
42
+ Rack::Server.start app: web, port: port
43
+ end
44
+
45
+ def build_routes
46
+ build_button_routes
47
+ end
48
+
49
+ def build_button_routes
50
+ plugin.buttons.each do |button|
51
+ web.get "/#{button.id}" do
52
+ runner.send(button.id)
53
+ redirect to('/')
54
+ end
55
+ end
56
+ end
57
+
58
+ # A Sinatra application skeleton that is used to build up the web server
59
+ # for this plugin.
60
+ class Web < Sinatra::Application
61
+ get '/' do
62
+ erb :index
63
+ end
64
+
65
+ private
66
+
67
+ def controls
68
+ runner.plugin.controls
69
+ end
70
+
71
+ def runner
72
+ Runner.instance
73
+ end
74
+
75
+ def title
76
+ runner.name
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,3 @@
1
+ module WhippedCream
2
+ VERSION = "0.0.1pre1"
3
+ end
@@ -0,0 +1,3 @@
1
+ <form action="/<%= control.id %>" method="get">
2
+ <input type="submit" value="<%= control.name %>">
3
+ </form>
@@ -0,0 +1,4 @@
1
+ <% controls.each do |control| %>
2
+ <%= erb control.type, locals: { control: control } %>
3
+ <hr />
4
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE HTML>
2
+ <html>
3
+ <head>
4
+ <title><%= title %></title>
5
+ <link href="/assets/application.css" media="screen" rel="stylesheet" type="text/css">
6
+ <meta content="width=device-width" name="viewport">
7
+ </head>
8
+ <body>
9
+ <h1><%= title %></h1>
10
+ <%= yield %>
11
+ </body>
12
+ </html>
@@ -0,0 +1,2 @@
1
+ <h2><%= control.name %></h3>
2
+ <p><%= runner.send(control.id) %><p>
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+
3
+ describe WhippedCream::Builder do
4
+ subject { plugin }
5
+
6
+ let(:plugin) {
7
+ described_class.build do
8
+ name "Garage"
9
+ end
10
+ }
11
+ let(:plugin_file_path) { "foo/bar/baz" }
12
+ let(:plugin_string) {
13
+ <<-PLUGIN
14
+ name "Garage"
15
+
16
+ button "Open/Close", pin: 1
17
+ PLUGIN
18
+ }
19
+
20
+ it "returns a plugin" do
21
+ should be_a(WhippedCream::Plugin)
22
+ end
23
+
24
+ context "with helper methods" do
25
+ subject {
26
+ described_class.build do
27
+ helpers do
28
+ def foo
29
+ :bar
30
+ end
31
+ end
32
+ end
33
+ }
34
+
35
+ it "defines the methods on the object" do
36
+ expect(subject.foo).to eq(:bar)
37
+ end
38
+ end
39
+
40
+ describe ".from_file" do
41
+ before do
42
+ File.stub read: plugin_string
43
+ end
44
+
45
+ it "reads the file" do
46
+ expect(File).to receive(:read).with(plugin_file_path)
47
+
48
+ described_class.from_file plugin_file_path
49
+ end
50
+
51
+ it "parses the contents" do
52
+ plugin = described_class.from_file(plugin_file_path)
53
+
54
+ expect(plugin.name).to eq("Garage")
55
+ expect(plugin.controls.first.name).to eq("Open/Close")
56
+ end
57
+ end
58
+
59
+ describe ".from_string" do
60
+ it "parses the string" do
61
+ plugin = described_class.from_string(plugin_string)
62
+
63
+ expect(plugin.name).to eq("Garage")
64
+ expect(plugin.controls.first.name).to eq("Open/Close")
65
+ end
66
+ end
67
+
68
+ describe "#camera" do
69
+ subject { plugin.camera }
70
+
71
+ it { should be_false }
72
+
73
+ context "with camera in the plugin" do
74
+ let(:plugin) {
75
+ described_class.build do
76
+ camera
77
+ end
78
+ }
79
+
80
+ it { should be_true }
81
+ end
82
+ end
83
+
84
+ describe "#button" do
85
+ subject { plugin.buttons }
86
+
87
+ it { should be_empty }
88
+
89
+ context "with a button" do
90
+ let(:plugin) {
91
+ described_class.build do
92
+ button "Open/Close", pin: 1 do
93
+ :tap
94
+ end
95
+ end
96
+ }
97
+
98
+ it "adds a button" do
99
+ expect(plugin.buttons).to have(1).item
100
+
101
+ button = plugin.buttons.first
102
+
103
+ expect(button.name).to eq("Open/Close")
104
+ expect(button.pin).to eq(1)
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "#sensor(s)" do
110
+ subject { plugin.sensors }
111
+
112
+ it { should be_empty }
113
+
114
+ context "with a sensor" do
115
+ let(:plugin) {
116
+ described_class.build do
117
+ sensor "Door",
118
+ pin: 2,
119
+ low: "Closed",
120
+ high: "Open",
121
+ on_high: :door_opened
122
+ end
123
+ }
124
+
125
+ it "adds a sensor" do
126
+ expect(plugin.sensors).to have(1).item
127
+
128
+ sensor = plugin.sensors.first
129
+
130
+ expect(sensor.name).to eq("Door")
131
+ expect(sensor.pin).to eq(2)
132
+ expect(sensor.low).to eq("Closed")
133
+ expect(sensor.high).to eq("Open")
134
+ expect(sensor.on_high).to eq(:door_opened)
135
+ expect(sensor.block).to be_nil
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe WhippedCream::Button do
4
+ subject(:button) { described_class.new(name, pin: pin, block: block) }
5
+
6
+ let(:name) { "Open/Close" }
7
+ let(:pin) { nil }
8
+ let(:block) { nil }
9
+
10
+ its(:name) { should eq(name) }
11
+ its(:id) { should eq(:open_close) }
12
+ its(:type) { should eq(:button) }
13
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ require 'fileutils'
4
+ require 'tmpdir'
5
+
6
+ describe WhippedCream::CLI do
7
+ subject { cli }
8
+ let(:cli) { described_class.new }
9
+
10
+ let(:plugin_filename) { File.join(tmpdir, "garage.rb") }
11
+ let(:plugin_string) {
12
+ <<-PLUGIN
13
+ name "Garage"
14
+
15
+ button "Open/Close", pin: 1
16
+ PLUGIN
17
+ }
18
+ let(:tmpdir) { Dir.mktmpdir }
19
+
20
+ before do
21
+ File.open(plugin_filename, 'w') { |file| file.write plugin_string }
22
+ end
23
+
24
+ after do
25
+ FileUtils.rm_rf tmpdir
26
+ end
27
+
28
+ describe "#demo" do
29
+ it "launches a web server with an example plugin" do
30
+ expect(Rack::Server).to receive(:start)
31
+
32
+ cli.demo
33
+ end
34
+ end
35
+
36
+ describe "#usage" do
37
+ it "displays a banner and help" do
38
+ expect(cli).to receive(:puts).exactly(2).times
39
+ expect(cli).to receive(:help)
40
+
41
+ cli.usage
42
+ end
43
+ end
44
+
45
+ describe "#start" do
46
+ it "starts a server for the plugin" do
47
+ expect(Rack::Server).to receive(:start)
48
+
49
+ cli.start(plugin_filename)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe WhippedCream::Plugin do
4
+ subject(:plugin) { described_class.new }
5
+
6
+ its(:camera) { should be_nil }
7
+ its(:name) { should be_nil }
8
+
9
+ its(:controls) { should be_empty }
10
+
11
+ its(:buttons) { should be_empty }
12
+ its(:fields) { should be_empty }
13
+ its(:sensors) { should be_empty }
14
+ its(:switches) { should be_empty }
15
+
16
+ describe ".build" do
17
+ it "delegates to Builder" do
18
+ block = -> {}
19
+ expect(WhippedCream::Builder).to receive(:build).with(&block)
20
+ described_class.build(&block)
21
+ end
22
+ end
23
+
24
+ describe ".from_file" do
25
+ it "delegates to Builder" do
26
+ path = "foo/bar"
27
+ expect(WhippedCream::Builder).to receive(:from_file).with(path)
28
+ described_class.from_file(path)
29
+ end
30
+ end
31
+
32
+ describe ".from_string" do
33
+ it "delegates to Builder" do
34
+ string = "name 'Garage'"
35
+ expect(WhippedCream::Builder).to receive(:from_string).with(string)
36
+ described_class.from_string(string)
37
+ end
38
+ end
39
+
40
+ context "with a button" do
41
+ before do
42
+ plugin.controls << WhippedCream::Button.new("Open/Close")
43
+ end
44
+
45
+ its(:controls) { should_not be_empty }
46
+ its(:buttons) { should_not be_empty }
47
+ end
48
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe WhippedCream::Runner do
4
+ subject(:runner) { described_class.new(plugin) }
5
+
6
+ let(:plugin) {
7
+ WhippedCream::Plugin.build do
8
+ name "Garage"
9
+ end
10
+ }
11
+
12
+ its(:name) { should eq("Garage") }
13
+
14
+ context "with a button" do
15
+ let(:plugin) {
16
+ WhippedCream::Plugin.build do
17
+ button "Open/Close", pin: 1
18
+ end
19
+ }
20
+
21
+ it "sets up that pin with direction: :out" do
22
+ pin = runner.pins[:open_close]
23
+
24
+ expect(pin).to be_a(PiPiper::Pin)
25
+ expect(pin.pin).to eq(1)
26
+ expect(pin.direction).to eq(:out)
27
+ end
28
+
29
+ it "defines an open_close method that taps the pin" do
30
+ pin = runner.pins[:open_close]
31
+
32
+ expect(pin).to receive(:on)
33
+ expect(runner).to receive(:sleep).with(0.25)
34
+ expect(pin).to receive(:off)
35
+
36
+ runner.open_close
37
+ end
38
+ end
39
+
40
+ context "with a sensor" do
41
+ let(:plugin) {
42
+ WhippedCream::Plugin.build do
43
+ sensor "Door", pin: 2, low: "Open", high: "Closed"
44
+ end
45
+ }
46
+
47
+ it "sets up that pin with direction: :in" do
48
+ pin = runner.pins[:door]
49
+
50
+ expect(pin).to be_a(PiPiper::Pin)
51
+ expect(pin.pin).to eq(2)
52
+ expect(pin.direction).to eq(:in)
53
+ end
54
+
55
+ it "defines a method that reads and converts the pin's value" do
56
+ pin = runner.pins[:door]
57
+ pin.stub value: 1
58
+
59
+ expect(runner.door).to eq("Closed")
60
+ end
61
+
62
+ context "with a block and no pin" do
63
+ let(:plugin) {
64
+ WhippedCream::Plugin.build do
65
+ sensor "Foo" do
66
+ "Bar"
67
+ end
68
+ end
69
+ }
70
+
71
+ it "does not set up a pin" do
72
+ expect(runner.pins[:door]).to be_nil
73
+ end
74
+
75
+ it "defines a method that calls the block" do
76
+ expect(runner.foo).to eq("Bar")
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe WhippedCream::Sensor do
4
+ subject(:sensor) { described_class.new(name, options, &block) }
5
+
6
+ let(:name) { "Door" }
7
+ let(:options) {
8
+ {
9
+ pin: 2,
10
+ high: "Open",
11
+ low: "Closed",
12
+ on_high: :door_opened
13
+ }
14
+ }
15
+ let(:block) { nil }
16
+
17
+ its(:name) { should eq(name) }
18
+ its(:id) { should eq(:door) }
19
+
20
+ its(:pin) { should eq(2) }
21
+
22
+ its(:high) { should eq("Open") }
23
+ its(:low) { should eq("Closed") }
24
+
25
+ its(:on_high) { should eq(:door_opened) }
26
+ its(:on_low) { should be_nil }
27
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe WhippedCream::Server do
4
+ subject(:server) { described_class.new(plugin) }
5
+
6
+ let(:plugin) {
7
+ WhippedCream::Plugin.build do
8
+ button "Open/Close", pin: 1
9
+ end
10
+ }
11
+
12
+ before do
13
+ Rack::Server.stub :start
14
+ end
15
+
16
+ it "creates a runner with the plugin" do
17
+ server.runner.stub :sleep
18
+
19
+ server.runner.open_close
20
+ end
21
+
22
+ it "reuses the same runner" do
23
+ expect(server.runner).to eq(server.runner)
24
+ end
25
+
26
+ it "builds up a Sinatra application from a plugin" do
27
+ server.start
28
+
29
+ expect(
30
+ server.web.routes['GET'].find { |route| route.first.match('/open_close') }
31
+ ).to be_true
32
+ end
33
+
34
+ it "starts the Sinatra application" do
35
+ expect(Rack::Server).to receive(:start)
36
+
37
+ server.start
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe WhippedCream do
4
+ it "should have a version number" do
5
+ expect(WhippedCream::VERSION).to_not be_empty
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ ENV['RUBY_ENV'] ||= 'test'
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
+
5
+ puts RUBY_DESCRIPTION
6
+
7
+ unless ENV['skip_coverage']
8
+ require 'simplecov'
9
+ SimpleCov.start
10
+ end
11
+
12
+ require 'whipped-cream'
13
+
14
+ RSpec.configure do |config|
15
+ config.expect_with :rspec do |c|
16
+ c.syntax = :expect
17
+ end
18
+
19
+ config.order = :random
20
+ end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'whipped-cream/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'whipped-cream'
8
+ spec.version = WhippedCream::VERSION
9
+ spec.authors = ["Justin Campbell"]
10
+ spec.email = ["justin@justincampbell.me"]
11
+ spec.description = "HTTP topping for Raspberry Pi"
12
+ spec.summary = "HTTP topping for Raspberry Pi"
13
+ spec.homepage = 'https://github.com/justincampbell/whipped-cream'
14
+ spec.license = 'MIT'
15
+
16
+ spec.post_install_message = File.read('WELCOME')
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.required_ruby_version = '>= 1.9.3'
24
+
25
+ spec.add_runtime_dependency 'pi_piper'
26
+ spec.add_runtime_dependency 'sinatra'
27
+ spec.add_runtime_dependency 'thor'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 1.3'
30
+ spec.add_development_dependency 'cane'
31
+ spec.add_development_dependency 'rake'
32
+ spec.add_development_dependency 'rspec'
33
+ spec.add_development_dependency 'simplecov'
34
+ end
metadata ADDED
@@ -0,0 +1,226 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: whipped-cream
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1pre1
5
+ prerelease: 5
6
+ platform: ruby
7
+ authors:
8
+ - Justin Campbell
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pi_piper
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: sinatra
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: thor
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.3'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '1.3'
78
+ - !ruby/object:Gem::Dependency
79
+ name: cane
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rake
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rspec
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: simplecov
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ description: HTTP topping for Raspberry Pi
143
+ email:
144
+ - justin@justincampbell.me
145
+ executables:
146
+ - whipped-cream
147
+ extensions: []
148
+ extra_rdoc_files: []
149
+ files:
150
+ - .cane
151
+ - .gitignore
152
+ - .rspec
153
+ - .ruby-version
154
+ - .travis.yml
155
+ - Gemfile
156
+ - Guardfile
157
+ - LICENSE.txt
158
+ - README.md
159
+ - Rakefile
160
+ - WELCOME
161
+ - bin/whipped-cream
162
+ - demo.rb
163
+ - lib/whipped-cream.rb
164
+ - lib/whipped-cream/builder.rb
165
+ - lib/whipped-cream/button.rb
166
+ - lib/whipped-cream/cli.rb
167
+ - lib/whipped-cream/control.rb
168
+ - lib/whipped-cream/pi_piper.rb
169
+ - lib/whipped-cream/plugin.rb
170
+ - lib/whipped-cream/public/assets/application.css
171
+ - lib/whipped-cream/runner.rb
172
+ - lib/whipped-cream/sensor.rb
173
+ - lib/whipped-cream/server.rb
174
+ - lib/whipped-cream/version.rb
175
+ - lib/whipped-cream/views/button.erb
176
+ - lib/whipped-cream/views/index.erb
177
+ - lib/whipped-cream/views/layout.erb
178
+ - lib/whipped-cream/views/sensor.erb
179
+ - spec/lib/whipped-cream/builder_spec.rb
180
+ - spec/lib/whipped-cream/button_spec.rb
181
+ - spec/lib/whipped-cream/cli_spec.rb
182
+ - spec/lib/whipped-cream/plugin_spec.rb
183
+ - spec/lib/whipped-cream/runner_spec.rb
184
+ - spec/lib/whipped-cream/sensor_spec.rb
185
+ - spec/lib/whipped-cream/server_spec.rb
186
+ - spec/lib/whipped-cream_spec.rb
187
+ - spec/spec_helper.rb
188
+ - whipped-cream.gemspec
189
+ homepage: https://github.com/justincampbell/whipped-cream
190
+ licenses:
191
+ - MIT
192
+ post_install_message: ! "\n Thanks for installing whipped-cream! Please don't hesitate
193
+ to report bugs\n and feature requests at:\n\n https://github.com/justincampbell/whipped-cream/issues\n\n
194
+ \ To run a demo, just do:\n\n whipped-cream demo\n\n Or use the help command
195
+ to see more options:\n\n whipped-cream help\n\n"
196
+ rdoc_options: []
197
+ require_paths:
198
+ - lib
199
+ required_ruby_version: !ruby/object:Gem::Requirement
200
+ none: false
201
+ requirements:
202
+ - - ! '>='
203
+ - !ruby/object:Gem::Version
204
+ version: 1.9.3
205
+ required_rubygems_version: !ruby/object:Gem::Requirement
206
+ none: false
207
+ requirements:
208
+ - - ! '>'
209
+ - !ruby/object:Gem::Version
210
+ version: 1.3.1
211
+ requirements: []
212
+ rubyforge_project:
213
+ rubygems_version: 1.8.23
214
+ signing_key:
215
+ specification_version: 3
216
+ summary: HTTP topping for Raspberry Pi
217
+ test_files:
218
+ - spec/lib/whipped-cream/builder_spec.rb
219
+ - spec/lib/whipped-cream/button_spec.rb
220
+ - spec/lib/whipped-cream/cli_spec.rb
221
+ - spec/lib/whipped-cream/plugin_spec.rb
222
+ - spec/lib/whipped-cream/runner_spec.rb
223
+ - spec/lib/whipped-cream/sensor_spec.rb
224
+ - spec/lib/whipped-cream/server_spec.rb
225
+ - spec/lib/whipped-cream_spec.rb
226
+ - spec/spec_helper.rb