flammarion 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/LICENSE +19 -0
  2. data/Readme.md +74 -0
  3. data/lib/flammarion.rb +155 -0
  4. data/lib/flammarion/pane.rb +12 -0
  5. data/lib/flammarion/server.rb +103 -0
  6. data/lib/flammarion/version.rb +3 -0
  7. data/lib/flammarion/writeable.rb +202 -0
  8. data/lib/html/Gemfile +19 -0
  9. data/lib/html/Gemfile.lock +151 -0
  10. data/lib/html/build/images/layers-2x.png +0 -0
  11. data/lib/html/build/images/layers.png +0 -0
  12. data/lib/html/build/images/marker-icon-2x.png +0 -0
  13. data/lib/html/build/images/marker-icon.png +0 -0
  14. data/lib/html/build/images/marker-shadow.png +0 -0
  15. data/lib/html/build/index.html +1 -0
  16. data/lib/html/build/javascripts/actions.js +415 -0
  17. data/lib/html/build/javascripts/all.js +7401 -0
  18. data/lib/html/build/javascripts/map.js +222 -0
  19. data/lib/html/build/javascripts/plot.js +288 -0
  20. data/lib/html/build/javascripts/querystring.js +39 -0
  21. data/lib/html/build/javascripts/status.js +37 -0
  22. data/lib/html/build/javascripts/vendor/ansi_up.js +327 -0
  23. data/lib/html/build/javascripts/vendor/highlight.pack.js +1 -0
  24. data/lib/html/build/javascripts/vendor/jquery.js +5 -0
  25. data/lib/html/build/javascripts/vendor/jquery.transit.min.js +1 -0
  26. data/lib/html/build/javascripts/vendor/l.control.geosearch.js +253 -0
  27. data/lib/html/build/javascripts/vendor/l.geosearch.provider.openstreetmap.js +297 -0
  28. data/lib/html/build/javascripts/vendor/leaflet.js +10 -0
  29. data/lib/html/build/javascripts/vendor/term.js +5974 -0
  30. data/lib/html/build/javascripts/websocket.js +166 -0
  31. data/lib/html/build/spectrum_test.html +12 -0
  32. data/lib/html/build/stylesheets/all.css +992 -0
  33. data/lib/html/build/stylesheets/buttons.css +179 -0
  34. data/lib/html/build/stylesheets/colors.css +0 -0
  35. data/lib/html/build/stylesheets/frontend.css +112 -0
  36. data/lib/html/build/stylesheets/leaflet.css +479 -0
  37. data/lib/html/build/stylesheets/map.css +0 -0
  38. data/lib/html/build/stylesheets/normalize.bak +375 -0
  39. data/lib/html/build/stylesheets/railscasts.css +185 -0
  40. data/lib/html/build/stylesheets/scrollbar.css +14 -0
  41. data/lib/html/build/stylesheets/table.css +21 -0
  42. data/lib/html/build/term_test.html +28 -0
  43. data/lib/html/config.rb +72 -0
  44. data/lib/html/source/images/layers-2x.png +0 -0
  45. data/lib/html/source/images/layers.png +0 -0
  46. data/lib/html/source/images/marker-icon-2x.png +0 -0
  47. data/lib/html/source/images/marker-icon.png +0 -0
  48. data/lib/html/source/images/marker-shadow.png +0 -0
  49. data/lib/html/source/index.html.slim +31 -0
  50. data/lib/html/source/javascripts/actions.coffee +192 -0
  51. data/lib/html/source/javascripts/all.js +3 -0
  52. data/lib/html/source/javascripts/map.coffee +43 -0
  53. data/lib/html/source/javascripts/plot.coffee +233 -0
  54. data/lib/html/source/javascripts/querystring.coffee +12 -0
  55. data/lib/html/source/javascripts/status.coffee +17 -0
  56. data/lib/html/source/javascripts/vendor/ansi_up.js +348 -0
  57. data/lib/html/source/javascripts/vendor/highlight.pack.js +1 -0
  58. data/lib/html/source/javascripts/vendor/jquery.js +4 -0
  59. data/lib/html/source/javascripts/vendor/jquery.transit.min.js +1 -0
  60. data/lib/html/source/javascripts/vendor/l.control.geosearch.js +242 -0
  61. data/lib/html/source/javascripts/vendor/l.geosearch.provider.openstreetmap.js +43 -0
  62. data/lib/html/source/javascripts/vendor/leaflet.js +9 -0
  63. data/lib/html/source/javascripts/vendor/term.js +5973 -0
  64. data/lib/html/source/javascripts/websocket.coffee +83 -0
  65. data/lib/html/source/layouts/layout.erb +0 -0
  66. data/lib/html/source/spectrum_test.html.slim +39 -0
  67. data/lib/html/source/stylesheets/all.css +2 -0
  68. data/lib/html/source/stylesheets/buttons.styl +115 -0
  69. data/lib/html/source/stylesheets/colors.styl +18 -0
  70. data/lib/html/source/stylesheets/frontend.styl +109 -0
  71. data/lib/html/source/stylesheets/leaflet.css +478 -0
  72. data/lib/html/source/stylesheets/map.styl +0 -0
  73. data/lib/html/source/stylesheets/normalize.bak +375 -0
  74. data/lib/html/source/stylesheets/railscasts.css +184 -0
  75. data/lib/html/source/stylesheets/scrollbar.styl +18 -0
  76. data/lib/html/source/stylesheets/table.styl +28 -0
  77. data/lib/html/source/term_test.html.slim +50 -0
  78. metadata +250 -0
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2015 Zachary Capalbo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/Readme.md ADDED
@@ -0,0 +1,74 @@
1
+ # Flammarion GUI Toolkit
2
+
3
+ ## Overview
4
+
5
+ Flammarion is an easy-to-use library for displaying information that you might
6
+ normally display to the command line in a slightly easier-to-access way.
7
+
8
+ It is not intended to be a full fledged application development toolkit. It is
9
+ intended instead for small scripts where you just want to show some information
10
+ without going through too much trouble.
11
+
12
+ ## Documentation
13
+
14
+ The easiest way to use Flammarion, is similar to how you might use STDOUT:
15
+
16
+ ```ruby
17
+ require 'flammarion'
18
+ f = Flammarion::Engraving.new
19
+ f.puts "Hello World!"
20
+ ```
21
+
22
+ It can even support standard console color codes: (Thanks to [ansi_up](http://github.com/drudru/ansi_up)!)
23
+
24
+ ```ruby
25
+ require 'colorized'
26
+ f.puts "This line will be red!".red
27
+ f.puts "This #{"word".colorize(:green)} will not be blue."
28
+ ```
29
+
30
+ However, you can also do more advanced things. Say you want to show a table. Easy!
31
+
32
+ ```ruby
33
+ f.table(
34
+ [%w[Number Squared Sqrt].map{|h| h.light_magenta}] + # Make the header a different color
35
+ 10.times.collect{|x| [x, x * x, Math.sqrt(x)]})
36
+ ```
37
+
38
+ Or maybe you want to know where something is:
39
+
40
+ ```ruby
41
+ f.map("Boston, MA")
42
+ ```
43
+
44
+ Maybe you even want to see both of those things *at the same time*!
45
+
46
+ ```ruby
47
+ f.pane("numberstuff").table([%w[Number Squared Sqrt].map{|h| h.light_magenta}] +
48
+ 10.times.collect{|x| [x, x * x, Math.sqrt(x)]})
49
+ f.pane("mapstuff").map("Big Ben")
50
+ ```
51
+
52
+ If you need feedback, there's a simple callback mechanism for buttons and text
53
+ boxes:
54
+
55
+ ```ruby
56
+ f.button("Click Here!!!") {f.puts "You clicked the button!"}
57
+ f.input("Placeholder > ") {|msg| f.puts "You wrote: #{msg['text'].light_magenta}"}
58
+ ```
59
+
60
+ There's lots more, too. I'll write more documentation eventually.
61
+
62
+ ## Installation
63
+
64
+ First you need to install [Google Chrome](https://www.google.com/chrome/browser/desktop/index.html)
65
+ and make sure it's in your path. Then you can install the gem:
66
+
67
+ `gem install flammarion`
68
+
69
+ or add it to your Gemfile.
70
+
71
+ ## Behind the scenes
72
+
73
+ Flammarion uses Chrome to display a simple html page, and WebSockets to communicate
74
+ between the Javascript and ruby.
data/lib/flammarion.rb ADDED
@@ -0,0 +1,155 @@
1
+ require 'monitor'
2
+ require 'open3'
3
+ require 'ostruct'
4
+ require 'em-websocket'
5
+ require 'json'
6
+ require 'slim'
7
+ require 'coffee-script'
8
+ require 'sass'
9
+ require 'colorize'
10
+ require 'filewatcher'
11
+
12
+ require_relative 'flammarion/writeable.rb'
13
+ require_relative 'flammarion/pane.rb'
14
+ require_relative 'flammarion/server.rb'
15
+
16
+ module Flammarion
17
+ class Engraving
18
+ attr_reader :chrome
19
+ attr_accessor :callbacks, :sockets, :on_disconnect, :on_connect, :actions
20
+ include Writeable
21
+ def initialize(options = {})
22
+ @chrome = OpenStruct.new
23
+ @sockets = []
24
+ @actions = {}
25
+ @front_end = self
26
+ @pane_name = "default"
27
+ start_server
28
+ @window_id = @@server.register_window(self)
29
+ open_a_window unless options[:no_chrome]
30
+ @callbacks = {}
31
+ @exit_on_disconnect = options.fetch(:exit_on_disconnect, false)
32
+ wait_for_a_connection unless options[:no_wait]
33
+ @on_disconnect = options[:on_disconnect]
34
+ @ignore_old = options.fetch(:ignore_old, false)
35
+ @on_connect = options[:on_connect]
36
+ end
37
+
38
+ def disconnect(ws)
39
+ @sockets.delete ws
40
+ exit 0 if @exit_on_disconnect
41
+ @on_disconnect.call if @on_disconnect
42
+ end
43
+
44
+ def process_message(msg)
45
+ @last_msg = msg
46
+ m = {}
47
+ begin
48
+ m = JSON.parse(msg)
49
+ rescue JSON::ParserError
50
+ log "Invalid JSON String"
51
+ return
52
+ end
53
+
54
+ case m["action"]
55
+ when 'callback'
56
+ callback = @callbacks[m['id']]
57
+ unless callback.nil?
58
+ if callback.arity == 1
59
+ callback.call(m)
60
+ else
61
+ callback.call
62
+ end
63
+ end
64
+ end
65
+ @actions[m["action"]].call(m) if @actions.include?(m["action"])
66
+ end
67
+
68
+ def make_id
69
+ @id ||= 0
70
+ @id += 1
71
+ "i#{@id}"
72
+ end
73
+
74
+ def start_server
75
+ @@server ||= Server.new
76
+ end
77
+
78
+ def wait_for_a_connection
79
+ sleep 0.5 while @sockets.empty?
80
+ end
81
+
82
+ CHROME_PATH = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
83
+ def open_a_window_on_windows
84
+ resource = %[file\://#{File.absolute_path(File.dirname(__FILE__))}/html/build/index.html]
85
+ spawn(CHROME_PATH, %[--app=#{resource}?path=#{@window_id}&port=#{@@server.port}])
86
+ end
87
+
88
+ def open_a_window
89
+ return open_a_window_on_windows if Gem.win_platform?
90
+ developmentMode = system("lsof -i:#{4567}", out: '/dev/null')
91
+ host = "file://#{File.dirname(File.absolute_path(__FILE__))}/html/build/index.html"
92
+ host = "http://localhost:4567/" if developmentMode
93
+
94
+ # data_dir = Dir.mktmpdir("flammarion")
95
+ # File.open("#{data_dir}/First\ Run", "w") {}
96
+
97
+ @expect_title = "Flammarion-#{rand.to_s[2..-1]}"
98
+
99
+ %w[google-chrome google-chrome-stable chromium chromium-browser chrome C:\Program\ Files\ (x86)\Google\Chrome\Application\chrome.exe].each do |executable|
100
+ @chrome.in, @chrome.out, @chrome.err, @chrome.thread = Open3.popen3("#{executable} --app='#{host}?path=#{@window_id}&port=#{@@server.port}&title=#{@expect_title}'")
101
+ break if @chrome.in
102
+ end
103
+
104
+ raise StandardError.new("Cannot launch any browser") unless @chrome.in
105
+ end
106
+
107
+ def window_open?
108
+ not @sockets.empty?
109
+ end
110
+
111
+ def send_json(val)
112
+ if @sockets.empty? then
113
+ open_a_window
114
+ wait_for_a_connection
115
+ end
116
+ @sockets.each{|ws| ws.send val.to_json}
117
+ nil
118
+ end
119
+
120
+ def pane(name)
121
+ return Pane.new(self, name)
122
+ end
123
+
124
+ def alert(text)
125
+ send_json(action:'alert', text:text)
126
+ end
127
+
128
+ def orientation=(orientation)
129
+ raise ArgumentError.new("Orientation must be :horizontal or :vertical") unless [:horizontal, :vertical].include?(orientation)
130
+ send_json({action:'reorient', orientation:orientation})
131
+ end
132
+
133
+ def title(str)
134
+ send_json({action:'title', title:str})
135
+ end
136
+
137
+ def layout(file)
138
+ data = Slim::Template.new(file).render
139
+ send_json({action:'layout', data:data})
140
+ end
141
+
142
+ def live_reload_layout(file)
143
+ layout(file); yield if block_given?
144
+ FileWatcher.new(file).watch {|file| layout(file); yield if block_given? }
145
+ end
146
+
147
+ def get_save_path
148
+ if Gem.win_platform?
149
+ `powershell "Add-Type -AssemblyName System.windows.forms|Out-Null;$f=New-Object System.Windows.Forms.SaveFileDialog;$f.InitialDirectory='%cd%';$f.Filter='All Files (*.*)|*.*';$f.showHelp=$true;$f.ShowDialog()|Out-Null;$f.FileName"`.strip
150
+ else
151
+ `zenity --file-selection --save --confirm-overwrite`.strip
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'writeable.rb'
2
+
3
+ module Flammarion
4
+ class Pane
5
+ attr_reader :pane_name
6
+ include Writeable
7
+ def initialize(front_end, name)
8
+ @front_end = front_end
9
+ @pane_name = name
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,103 @@
1
+ module Flammarion
2
+ class Server
3
+ attr_reader :port
4
+ def initialize
5
+ @windows = {}
6
+ @socket_paths = {}
7
+ @started = false
8
+ Thread.new do
9
+ begin
10
+ start_server_internal
11
+ rescue SystemExit
12
+ raise
13
+ rescue Exception => e
14
+ if binding.respond_to? :pry
15
+ binding.pry
16
+ else
17
+ raise
18
+ end
19
+ end
20
+ end
21
+ sleep 0.5 while not @started
22
+ end
23
+ def start_server_internal
24
+ EM.run {
25
+ @port = 7870
26
+ @port = rand(65000 - 1024) + 1024 if Gem.win_platform?
27
+ begin
28
+ EM::WebSocket.run(:host => "0.0.0.0", :port => @port) do |ws|
29
+ ws.onopen { |handshake|
30
+ log "WebSocket connection open"
31
+ if @windows.include?(handshake.path)
32
+ @windows[handshake.path].sockets << ws
33
+ @windows[handshake.path].on_connect.call() if @windows[handshake.path].on_connect
34
+ @socket_paths[ws] = handshake.path
35
+ else
36
+ log "No such window: #{handshake.path}"
37
+ end
38
+ }
39
+
40
+ ws.onclose do
41
+ log "Connection closed";
42
+ @windows[@socket_paths[ws]].disconnect(ws) if @windows[@socket_paths[ws]]
43
+ end
44
+
45
+ ws.onmessage { |msg|
46
+ Thread.new do
47
+ begin
48
+ @windows[@socket_paths[ws]].process_message(msg)
49
+ rescue Exception => e
50
+ Kernel.puts "#{e.message.to_s.red}\n#{e.backtrace.join("\n").light_red}"
51
+ if binding.respond_to? :pry
52
+ binding.pry
53
+ else
54
+ raise
55
+ end
56
+ end
57
+ end
58
+ }
59
+
60
+ ws.onerror { |err|
61
+ if binding.respond_to? :pry
62
+ binding.pry
63
+ else
64
+ raise
65
+ end
66
+ }
67
+ end
68
+ rescue RuntimeError => e
69
+ if e.message == "no acceptor (port is in use or requires root privileges)"
70
+ @port = rand(65000 - 1024) + 1024
71
+ Kernel.puts "New port: #{port}"
72
+ retry
73
+ else
74
+ raise
75
+ end
76
+ end
77
+ @started = true
78
+ }
79
+ rescue Exception => e
80
+ unless e.is_a? SystemExit
81
+ Kernel.puts "Error in server:"
82
+ binding.pry if binding.respond_to? :pry
83
+ Kernel.puts e.message
84
+ Kernel.puts e.backtrace.inspect
85
+ end
86
+ raise
87
+ end
88
+
89
+ def stop
90
+ end
91
+
92
+ def log(str)
93
+ # Kernel.puts str
94
+ end
95
+
96
+ def register_window(window)
97
+ @new_path ||= 0
98
+ @new_path += 1
99
+ @windows["/w#{@new_path}"] = window
100
+ return "w#{@new_path}"
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,3 @@
1
+ module Flammarion
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,202 @@
1
+ module Flammarion
2
+ module Writeable
3
+ attr_reader :front_end
4
+ class DeferredValue < Delegator
5
+ def initialize
6
+ super @value
7
+ end
8
+ def __setobj__(value)
9
+ @value = value
10
+ end
11
+
12
+ def value
13
+ @value
14
+ end
15
+
16
+ def __getobj__
17
+ @value
18
+ end
19
+
20
+ def inspect
21
+ "#R#{@value.inspect}"
22
+ end
23
+
24
+ def checked?
25
+ return @value
26
+ end
27
+ end
28
+
29
+ class Spectrum
30
+ attr_reader :front_end
31
+ def initialize(id, target, front_end)
32
+ @id = id
33
+ @target = target
34
+ @front_end = front_end
35
+ end
36
+
37
+ def plot(data, options = {})
38
+ @front_end.send_json({action:'plot', id:@id, target:@target, data:data}.merge(options))
39
+ end
40
+ end
41
+
42
+ def send_json(hash)
43
+ @front_end.send_json({target: @pane_name}.merge(hash))
44
+ end
45
+
46
+ def send(str, options = {})
47
+ @front_end.send_json({action:'append', text:str, target:@pane_name}.merge(options))
48
+ end
49
+ alias_method :print, :send
50
+
51
+ def puts(str = "", options = {})
52
+ send str, options
53
+ send "\n"
54
+ return nil
55
+ end
56
+
57
+ def replace(str, options = {})
58
+ send_json({action:'replace', text:str}.merge(options))
59
+ return nil
60
+ end
61
+
62
+ def clear
63
+ send_json({action:'clear'})
64
+ return nil
65
+ end
66
+
67
+ def close
68
+ send_json({action:'closepane'})
69
+ end
70
+
71
+ def plot(values, options = {})
72
+ id = @front_end.make_id
73
+ send_json({action:'plot', data:values, id:id}.merge(options))
74
+ return Spectrum.new(id, @pane_name, @front_end)
75
+ end
76
+
77
+ def highlight(text, options = {})
78
+ output = text
79
+ output = JSON.pretty_generate(text) if text.is_a? Hash or text.is_a? Array
80
+ send_json({action:'highlight', text:output}.merge(options))
81
+ nil
82
+ end
83
+
84
+ def button(label, options = {}, &block)
85
+ id = @front_end.make_id
86
+ send_json({action:'button', label:label, id:id}.merge(options))
87
+ @front_end.callbacks[id] = block
88
+ end
89
+
90
+ def input(label, options = {}, &block)
91
+ id = @front_end.make_id
92
+ send_json({action:'input', label:label, id:id}.merge(options))
93
+ if block_given?
94
+ @front_end.callbacks[id] = block
95
+ else
96
+ d = DeferredValue.new
97
+ @front_end.callbacks[id] = Proc.new {|v| d.__setobj__ v["text"] }
98
+ return d
99
+ end
100
+ end
101
+
102
+ def checkbox(label, options = {}, &block)
103
+ id = @front_end.make_id
104
+ send_json({action:'checkbox', label:label, id:id}.merge(options))
105
+ if block_given?
106
+ @front_end.callbacks[id] = block
107
+ else
108
+ d = DeferredValue.new
109
+ d.__setobj__(options[:value] || options['value'])
110
+ @front_end.callbacks[id] = Proc.new {|v| d.__setobj__(v["checked"])}
111
+ return d
112
+ end
113
+ end
114
+
115
+ def break(options = {})
116
+ send_json({action:'break'}.merge(options))
117
+ end
118
+
119
+ def html(data)
120
+ send_json({action:'replace', text:data, raw:true})
121
+ end
122
+
123
+ def script(coffee)
124
+ data = CoffeeScript.compile(coffee)
125
+ send_json({action:'script', data:data})
126
+ end
127
+
128
+ def template(file)
129
+ data = Slim::Template.new(file).render
130
+ send_json({action:'replace', text:data, raw:true})
131
+ end
132
+
133
+ def live_reload_template(file)
134
+ FileWatcher.new(file).watch {|file| template(file) }
135
+ end
136
+
137
+ def hide
138
+ send_json({action:'hidepane'})
139
+ end
140
+
141
+ def show
142
+ send_json({action:'showpane'})
143
+ end
144
+
145
+ def subpane(name)
146
+ send_json({action:'subpane', name:name})
147
+ return Pane.new(@front_end, name)
148
+ end
149
+
150
+ def pane(name)
151
+ send_json({action:'addpane', name:name})
152
+ return Pane.new(@front_end, name)
153
+ end
154
+
155
+ def orientation=(orientation)
156
+ raise ArgumentError.new("Orientation must be :horizontal or :vertical") unless [:horizontal, :vertical].include?(orientation)
157
+ send_json({action:'reorient', orientation:orientation})
158
+ end
159
+
160
+ def button_box(name)
161
+ send_json({action:'buttonbox', name:name})
162
+ return Pane.new(@front_end, name)
163
+ end
164
+
165
+ def status(str, position = :right)
166
+ @front_end.send_json({action:'status', text: str, position:position})
167
+ end
168
+
169
+ def table(rows, options = {})
170
+ send_json({action:'table', rows: rows}.merge(options))
171
+ end
172
+
173
+ def gets(prompt = "", options = {})
174
+ str = nil
175
+ input(prompt, {once:true, focus:true}.merge(options)) {|msg| str = msg["text"]}
176
+ sleep 0.1 while str.nil?
177
+ return str
178
+ end
179
+
180
+ def map(*args)
181
+ case (args.size)
182
+ when 1
183
+ if args[0].respond_to? :keys then
184
+ options = args[0]
185
+ else
186
+ options = {address:args[0].to_s}
187
+ end
188
+ when 2
189
+ if args[1].respond_to? :keys then
190
+ options = {address:args[0]}.merge(args[1])
191
+ else
192
+ options = {latitude:args[0], longitude:args[1]}
193
+ end
194
+ when 3
195
+ options = {latitude:args[0], longitude:args[1]}.merge(args[2])
196
+ else
197
+ raise ArgumentError.new("Expected 1..3 arguments")
198
+ end
199
+ send_json({action:'map'}.merge(options))
200
+ end
201
+ end
202
+ end