xively-cli 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © Heroku 2008 - 2012
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 NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # Xively CLI
2
+
3
+ A Xively client tool
4
+
5
+ [![Build Status](https://secure.travis-ci.org/levent/xively-cli.png)](http://travis-ci.org/levent/xively-cli)
6
+ [![Dependency Status](https://gemnasium.com/levent/xively-cli.png)](https://gemnasium.com/levent/xively-cli)
7
+ [![Coverage Status](https://coveralls.io/repos/levent/xively-cli/badge.png)](https://coveralls.io/r/levent/xively-cli)
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ gem install xively
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Connect to the socket server
18
+
19
+ You can connect to a feed or datastream and receive realtime json updates in your terminal
20
+
21
+ ```bash
22
+ xively subscribe -k YOUR_API_KEY -f FEED_ID -d DATASTREAM_ID
23
+ ```
24
+
data/bin/xively ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ begin
5
+ # resolve bin path, ignoring symlinks
6
+ require "pathname"
7
+ bin_file = Pathname.new(__FILE__).realpath
8
+
9
+ # add self to libpath
10
+ $:.unshift File.expand_path("../../lib", bin_file)
11
+
12
+ # start up the CLI
13
+ require "xively-cli"
14
+ Xively::CLI.start(*ARGV)
15
+ rescue Interrupt
16
+ `stty icanon echo`
17
+ puts("\n ! Command cancelled.")
18
+ end
data/lib/xively-cli.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "xively-cli/command"
2
+
3
+ module Xively
4
+ class CLI
5
+
6
+ def self.start(*args)
7
+ command = args.shift.strip rescue "help"
8
+ Xively::Command.load
9
+ Xively::Command.run(command, args)
10
+ end
11
+
12
+ end
13
+ end
File without changes
@@ -0,0 +1,168 @@
1
+ require 'xively-cli/version'
2
+ require "optparse"
3
+
4
+ module Xively
5
+ module Command
6
+ class CommandFailed < RuntimeError; end
7
+
8
+ def self.load
9
+ Dir[File.join(File.dirname(__FILE__), "command", "*.rb")].each do |file|
10
+ require file
11
+ end
12
+ end
13
+
14
+ def self.commands
15
+ @@commands ||= {}
16
+ end
17
+
18
+ def self.command_aliases
19
+ @@command_aliases ||= {}
20
+ end
21
+
22
+ def self.files
23
+ @@files ||= Hash.new {|hash,key| hash[key] = File.readlines(key).map {|line| line.strip}}
24
+ end
25
+
26
+ def self.namespaces
27
+ @@namespaces ||= {}
28
+ end
29
+
30
+ def self.register_command(command)
31
+ commands[command[:command]] = command
32
+ end
33
+
34
+ def self.register_namespace(namespace)
35
+ namespaces[namespace[:name]] = namespace
36
+ end
37
+
38
+ def self.current_command
39
+ @current_command
40
+ end
41
+
42
+ def self.current_command=(new_current_command)
43
+ @current_command = new_current_command
44
+ end
45
+
46
+ def self.global_options
47
+ @global_options ||= []
48
+ end
49
+
50
+ def self.invalid_arguments
51
+ @invalid_arguments
52
+ end
53
+
54
+ def self.shift_argument
55
+ @invalid_arguments.shift rescue nil
56
+ end
57
+
58
+ def self.validate_arguments!
59
+ unless invalid_arguments.empty?
60
+ arguments = invalid_arguments.map {|arg| "\"#{arg}\""}
61
+ if arguments.length == 1
62
+ message = "Invalid argument: #{arguments.first}"
63
+ elsif arguments.length > 1
64
+ message = "Invalid arguments: "
65
+ message << arguments[0...-1].join(", ")
66
+ message << " and "
67
+ message << arguments[-1]
68
+ end
69
+ $stderr.puts(message)
70
+ run(current_command, ["--help"])
71
+ exit(1)
72
+ end
73
+ end
74
+
75
+ def self.warnings
76
+ @warnings ||= []
77
+ end
78
+
79
+ def self.display_warnings
80
+ unless warnings.empty?
81
+ $stderr.puts(warnings.map {|warning| " ! #{warning}"}.join("\n"))
82
+ end
83
+ end
84
+
85
+ def self.global_option(name, *args, &blk)
86
+ global_options << { :name => name, :args => args, :proc => blk }
87
+ end
88
+
89
+ global_option :key, "--key API_KEY", "-k"
90
+ global_option :help, "--help", "-h"
91
+
92
+ def self.prepare_run(cmd, args=[])
93
+ command = parse(cmd)
94
+
95
+ unless command
96
+ if %w( -v --version ).include?(cmd)
97
+ command = parse('version')
98
+ else
99
+ $stderr.puts(["`#{cmd}` is not a xively command.", "See `xively help` for a list of available commands."].join("\n"))
100
+ exit(1)
101
+ end
102
+ end
103
+
104
+ @current_command = cmd
105
+
106
+ opts = {}
107
+ invalid_options = []
108
+
109
+ parser = OptionParser.new do |parser|
110
+ # overwrite OptionParsers Officious['version'] to avoid conflicts
111
+ # see: https://github.com/ruby/ruby/blob/trunk/lib/optparse.rb#L814
112
+ parser.on("--version") do |value|
113
+ invalid_options << "--version"
114
+ end
115
+ global_options.each do |global_option|
116
+ parser.on(*global_option[:args]) do |value|
117
+ global_option[:proc].call(value) if global_option[:proc]
118
+ opts[global_option[:name]] = value
119
+ end
120
+ end
121
+ command[:options].each do |name, option|
122
+ parser.on("-#{option[:short]}", "--#{option[:long]}", option[:desc]) do |value|
123
+ opts[name.gsub("-", "_").to_sym] = value
124
+ end
125
+ end
126
+ end
127
+
128
+ begin
129
+ parser.order!(args) do |nonopt|
130
+ invalid_options << nonopt
131
+ end
132
+ rescue OptionParser::InvalidOption => ex
133
+ invalid_options << ex.args.first
134
+ retry
135
+ end
136
+
137
+ if opts[:help]
138
+ args.unshift cmd unless cmd =~ /^-.*/
139
+ cmd = "help"
140
+ command = parse(cmd)
141
+ end
142
+
143
+ args.concat(invalid_options)
144
+
145
+ @current_args = args
146
+ @current_options = opts
147
+ @invalid_arguments = invalid_options
148
+
149
+ [ command[:klass].new(args.dup, opts.dup), command[:method] ]
150
+ end
151
+
152
+ def self.run(cmd, arguments=[])
153
+ object, method = prepare_run(cmd, arguments.dup)
154
+ object.send(method)
155
+ rescue CommandFailed => e
156
+ $stderr.puts e.message
157
+ rescue OptionParser::ParseError
158
+ commands[cmd] ? run("help", [cmd]) : run("help")
159
+ ensure
160
+ display_warnings
161
+ end
162
+
163
+ def self.parse(cmd)
164
+ commands[cmd] || commands[command_aliases[cmd]]
165
+ end
166
+
167
+ end
168
+ end
@@ -0,0 +1,148 @@
1
+ require "fileutils"
2
+ require "xively-cli/command"
3
+
4
+ class Xively::Command::Base
5
+
6
+ def self.namespace
7
+ self.to_s.split("::").last.downcase
8
+ end
9
+
10
+ attr_reader :args
11
+ attr_reader :options
12
+
13
+ def initialize(args=[], options={})
14
+ @args = args
15
+ @options = options
16
+ end
17
+
18
+ protected
19
+
20
+ def self.inherited(klass)
21
+ unless klass == Xively::Command::Base
22
+ help = extract_help_from_caller(caller.first)
23
+
24
+ Xively::Command.register_namespace(
25
+ :name => klass.namespace,
26
+ :description => help.first
27
+ )
28
+ end
29
+ end
30
+
31
+ def self.method_added(method)
32
+ return if self == Xively::Command::Base
33
+ return if private_method_defined?(method)
34
+ return if protected_method_defined?(method)
35
+
36
+ help = extract_help_from_caller(caller.first)
37
+ resolved_method = (method.to_s == "index") ? nil : method.to_s
38
+ command = [ self.namespace, resolved_method ].compact.join(":")
39
+ banner = extract_banner(help) || command
40
+
41
+ Xively::Command.register_command(
42
+ :klass => self,
43
+ :method => method,
44
+ :namespace => self.namespace,
45
+ :command => command,
46
+ :banner => banner.strip,
47
+ :help => help.join("\n"),
48
+ :summary => extract_summary(help),
49
+ :description => extract_description(help),
50
+ :options => extract_options(help)
51
+ )
52
+ end
53
+
54
+ def self.alias_command(new, old)
55
+ raise "no such command: #{old}" unless Xively::Command.commands[old]
56
+ Xively::Command.command_aliases[new] = old
57
+ end
58
+
59
+ #
60
+ # Parse the caller format and identify the file and line number as identified
61
+ # in : http://www.ruby-doc.org/core/classes/Kernel.html#M001397. This will
62
+ # look for a colon followed by a digit as the delimiter. The biggest
63
+ # complication is windows paths, which have a color after the drive letter.
64
+ # This regex will match paths as anything from the beginning to a colon
65
+ # directly followed by a number (the line number).
66
+ #
67
+ def self.extract_help_from_caller(line)
68
+ # pull out of the caller the information for the file path and line number
69
+ if line =~ /^(.+?):(\d+)/
70
+ extract_help($1, $2)
71
+ else
72
+ raise("unable to extract help from caller: #{line}")
73
+ end
74
+ end
75
+
76
+ def self.extract_help(file, line_number)
77
+ buffer = []
78
+ lines = Xively::Command.files[file]
79
+
80
+ (line_number.to_i-2).downto(0) do |i|
81
+ line = lines[i]
82
+ case line[0..0]
83
+ when ""
84
+ when "#"
85
+ buffer.unshift(line[1..-1])
86
+ else
87
+ break
88
+ end
89
+ end
90
+
91
+ buffer
92
+ end
93
+
94
+ def self.extract_banner(help)
95
+ help.first
96
+ end
97
+
98
+ def self.extract_summary(help)
99
+ extract_description(help).split("\n")[2].to_s.split("\n").first
100
+ end
101
+
102
+ def self.extract_description(help)
103
+ help.reject do |line|
104
+ line =~ /^\s+-(.+)#(.+)/
105
+ end.join("\n")
106
+ end
107
+
108
+ def self.extract_options(help)
109
+ help.select do |line|
110
+ line =~ /^\s+-(.+)#(.+)/
111
+ end.inject({}) do |hash, line|
112
+ description = line.split("#", 2).last
113
+ long = line.match(/--([A-Za-z\- ]+)/)[1].strip
114
+ short = line.match(/-([A-Za-z ])[ ,]/) && $1 && $1.strip
115
+ hash.update(long.split(" ").first => { :desc => description, :short => short, :long => long })
116
+ end
117
+ end
118
+
119
+ def current_command
120
+ Xively::Command.current_command
121
+ end
122
+
123
+ def extract_option(key)
124
+ options[key.dup.gsub('-','').to_sym]
125
+ end
126
+
127
+ def invalid_arguments
128
+ Xively::Command.invalid_arguments
129
+ end
130
+
131
+ def shift_argument
132
+ Xively::Command.shift_argument
133
+ end
134
+
135
+ def validate_arguments!
136
+ Xively::Command.validate_arguments!
137
+ end
138
+
139
+ def escape(value)
140
+ xively.escape(value)
141
+ end
142
+ end
143
+
144
+ module Xively::Command
145
+ unless const_defined?(:BaseWithApp)
146
+ BaseWithApp = Base
147
+ end
148
+ end
@@ -0,0 +1,46 @@
1
+ require 'xively-cli/command/base'
2
+ require 'xively-rb'
3
+
4
+ # Create a Xively feed
5
+ #
6
+ class Xively::Command::Feeds < Xively::Command::Base
7
+
8
+ # feeds:create [TITLE]
9
+ #
10
+ # create a Xively feed
11
+ #
12
+ # -k, --key API_KEY # your api key
13
+ #
14
+ #Examples:
15
+ #
16
+ # $ xively feeds:create "Home energy monitor" -k ABCD1234
17
+ #
18
+ def create
19
+ title = shift_argument
20
+ api_key = options[:key]
21
+ validate_arguments!
22
+
23
+ if title.nil?
24
+ $stderr.puts "Please provide a title"
25
+ exit(1)
26
+ end
27
+
28
+ if api_key.nil?
29
+ $stderr.puts Xively::Command::Help.usage_for_command("feeds:create")
30
+ exit(1)
31
+ end
32
+
33
+ puts "Creating feed \"#{title}\"..."
34
+
35
+ feed = Xively::Feed.new
36
+ feed.title = title
37
+
38
+ response = Xively::Client.post('/v2/feeds.json', :headers => {'X-ApiKey' => api_key}, :body => feed.to_json)
39
+ if response.code == 201
40
+ puts "Your feed has been created at:"
41
+ puts response.headers['location']
42
+ end
43
+ end
44
+
45
+ alias_command "create", "feeds:create"
46
+ end
@@ -0,0 +1,121 @@
1
+ # list commands and display help
2
+ #
3
+ class Xively::Command::Help < Xively::Command::Base
4
+
5
+ PRIMARY_NAMESPACES = %w( subscribe )
6
+
7
+ # help [COMMAND]
8
+ #
9
+ # list available commands or display help for a specific command
10
+ #
11
+ #Examples:
12
+ #
13
+ # $ xively help
14
+ # Usage: xively COMMAND [--key API_KEY] [command-specific-options]
15
+ #
16
+ # Primary help topics, type "xively help TOPIC" for more details:
17
+ #
18
+ # subscribe # subscribe to a datastream
19
+ # ...
20
+ #
21
+ def index
22
+ if command = args.shift
23
+ help_for_command(command)
24
+ else
25
+ help_for_root
26
+ end
27
+ end
28
+
29
+ alias_command "-h", "help"
30
+ alias_command "--help", "help"
31
+
32
+ def self.usage_for_command(command)
33
+ command = new.send(:commands)[command]
34
+ "Usage: xively #{command[:help]}" if command
35
+ end
36
+
37
+ private
38
+
39
+ def commands_for_namespace(name)
40
+ Xively::Command.commands.values.select do |command|
41
+ command[:namespace] == name && command[:command] != name
42
+ end
43
+ end
44
+
45
+ def namespaces
46
+ namespaces = Xively::Command.namespaces
47
+ namespaces.delete("app")
48
+ namespaces
49
+ end
50
+
51
+ def commands
52
+ Xively::Command.commands
53
+ end
54
+
55
+ def primary_namespaces
56
+ PRIMARY_NAMESPACES.map { |name| namespaces[name] }.compact
57
+ end
58
+
59
+ def additional_namespaces
60
+ (namespaces.values - primary_namespaces)
61
+ end
62
+
63
+ def summary_for_namespaces(namespaces)
64
+ size = (namespaces.map { |n| n[:name] }).map { |i| i.to_s.length }.sort.last
65
+ namespaces.sort_by {|namespace| namespace[:name]}.each do |namespace|
66
+ name = namespace[:name]
67
+ namespace[:description]
68
+ puts " %-#{size}s # %s" % [ name, namespace[:description] ]
69
+ end
70
+ end
71
+
72
+ def help_for_root
73
+ puts "Usage: xively COMMAND [--key API_KEY] [command-specific-options]"
74
+ puts
75
+ puts "Primary help topics, type \"xively help TOPIC\" for more details:"
76
+ puts
77
+ summary_for_namespaces(primary_namespaces)
78
+ puts
79
+ puts "Additional topics:"
80
+ puts
81
+ summary_for_namespaces(additional_namespaces)
82
+ puts
83
+ end
84
+
85
+ def help_for_namespace(name)
86
+ namespace_commands = commands_for_namespace(name)
87
+
88
+ unless namespace_commands.empty?
89
+ size = (namespace_commands.map { |c| c[:banner] }).map { |i| i.to_s.length }.sort.last
90
+ namespace_commands.sort_by { |c| c[:banner].to_s }.each do |command|
91
+ next if command[:help] =~ /DEPRECATED/
92
+ command[:summary]
93
+ puts " %-#{size}s # %s" % [ command[:banner], command[:summary] ]
94
+ end
95
+ end
96
+ end
97
+
98
+ def help_for_command(name)
99
+ if command_alias = Xively::Command.command_aliases[name]
100
+ puts("Alias: #{name} is short for #{command_alias}")
101
+ name = command_alias
102
+ end
103
+ if command = commands[name]
104
+ puts "Usage: xively #{command[:banner]}"
105
+
106
+ if command[:help].strip.length > 0
107
+ puts command[:help].split("\n")[1..-1].join("\n")
108
+ end
109
+ puts
110
+ end
111
+
112
+ if commands_for_namespace(name).size > 0
113
+ puts "Additional commands, type \"xively help COMMAND\" for more details:"
114
+ puts
115
+ help_for_namespace(name)
116
+ puts
117
+ elsif command.nil?
118
+ puts "#{name} is not a xively command. See `xively help`."
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,70 @@
1
+ require 'xively-cli/command/base'
2
+ require 'socket'
3
+
4
+ # Subscribe to a feed or datastream via the Xively Socket Server
5
+ #
6
+ class Xively::Command::Subscribe < Xively::Command::Base
7
+
8
+ # subscribe
9
+ #
10
+ # connect to a tcp socket for a feed or datastream
11
+ #
12
+ # -k, --key API_KEY # your api key
13
+ # -f, --feed FEED # the feed id
14
+ # -d, --datastream DATASTREAM # the datastream id (optional)
15
+ #
16
+ #Example:
17
+ #
18
+ # $ xively subscribe -k ABCD1234 -f 504 -d 0 # subscribe to datastream
19
+ # $ xively subscribe -k ABCD1234 -f 504 # subscribe to feed
20
+ #
21
+ def index
22
+ api_key = options[:key]
23
+ feed_id = options[:feed]
24
+ datastream_id = options[:datastream]
25
+
26
+ validate_arguments!
27
+
28
+ unless api_key && feed_id
29
+ $stderr.puts Xively::Command::Help.usage_for_command("subscribe")
30
+ exit(1)
31
+ end
32
+
33
+ resource = "/feeds/#{feed_id}"
34
+ resource += "/datastreams/#{datastream_id}" if datastream_id
35
+
36
+ puts "Subscribing to updates for #{resource}"
37
+
38
+ subscribe = "{\"method\":\"subscribe\", \"resource\":\"#{resource}\", \"headers\":{\"X-ApiKey\":\"#{api_key}\"}}"
39
+ s = TCPSocket.new 'api.xively.com', 8081
40
+ s.puts subscribe
41
+ while line = s.gets
42
+ parse_data(line, s)
43
+ end
44
+ s.close
45
+
46
+ puts "Connection closed"
47
+
48
+ # EventMachine.run {
49
+ # EventMachine::connect 'api.xively.com', 8081, XivelySocket, options
50
+ # }
51
+ rescue Interrupt => e
52
+ puts "Closing connection"
53
+ s.close
54
+ raise e
55
+ end
56
+
57
+ alias_command "sub", "subscribe"
58
+
59
+ protected
60
+
61
+ def parse_data(string, socket)
62
+ puts(string)
63
+ if string =~ /"status":40/
64
+ socket.close
65
+ exit(1)
66
+ end
67
+ end
68
+
69
+ end
70
+
@@ -0,0 +1,23 @@
1
+ require "xively-cli/command/base"
2
+ require "xively-cli/version"
3
+
4
+ # display version
5
+ #
6
+ class Xively::Command::Version < Xively::Command::Base
7
+
8
+ # version
9
+ #
10
+ # show xively client version
11
+ #
12
+ #Example:
13
+ #
14
+ # $ xively version
15
+ # v0.0.1
16
+ #
17
+ def index
18
+ validate_arguments!
19
+
20
+ puts(Xively::VERSION)
21
+ end
22
+
23
+ end
@@ -0,0 +1,5 @@
1
+ module Xively
2
+ class CLI
3
+ VERSION = "0.0.9"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xively-cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.9
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Levent Ali
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: xively-rb
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0.2'
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.2'
30
+ description: Xively CLI
31
+ email: lebreeze@gmail.com
32
+ executables:
33
+ - xively
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - lib/xively-cli/cli.rb
38
+ - lib/xively-cli/command/base.rb
39
+ - lib/xively-cli/command/feeds.rb
40
+ - lib/xively-cli/command/help.rb
41
+ - lib/xively-cli/command/subscribe.rb
42
+ - lib/xively-cli/command/version.rb
43
+ - lib/xively-cli/command.rb
44
+ - lib/xively-cli/version.rb
45
+ - lib/xively-cli.rb
46
+ - LICENSE
47
+ - README.md
48
+ - bin/xively
49
+ homepage: https://github.com/levent/xively-cli
50
+ licenses:
51
+ - MIT
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ segments:
63
+ - 0
64
+ hash: -2041254343982566204
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ segments:
72
+ - 0
73
+ hash: -2041254343982566204
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 1.8.23
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Provides a client to Xively
80
+ test_files: []
81
+ has_rdoc: