flammarion 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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