TokiCLI 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.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +13 -0
- data/Rakefile +2 -0
- data/TokiCLI.gemspec +30 -0
- data/bin/toki +10 -0
- data/lib/TokiCLI.rb +7 -0
- data/lib/TokiCLI/app.rb +114 -0
- data/lib/TokiCLI/get_channels.rb +22 -0
- data/lib/TokiCLI/get_messages.rb +32 -0
- data/lib/TokiCLI/module.rb +142 -0
- data/lib/TokiCLI/search_messages.rb +23 -0
- data/lib/TokiCLI/status.rb +32 -0
- data/lib/TokiCLI/version.rb +3 -0
- data/lib/TokiCLI/view.rb +40 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 24126edaca561eab0623a449445132dddf6697a0
|
4
|
+
data.tar.gz: bec8ef8903fd53c79b8ef96696dc04fde69e9e7b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 985a22eda651b23467d18db8e7198edd24c5454275a09958e31b4d38a74f9a4d44031e30033302dd7cba00a3485b0b7301470464f605ce94170d2e1f7a8ab2f3
|
7
|
+
data.tar.gz: 8168f8fa325678e6e4d332476ee0d889a9819bbcf9a917e0cf89d7442b29c5295350f049a0f1ad5f19168f69193fb723df3ff0354767d00dab3247970d71dbac
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
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
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
token
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Eric Dejonckheere
|
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
data/Rakefile
ADDED
data/TokiCLI.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'TokiCLI/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "TokiCLI"
|
8
|
+
spec.version = TokiCLI::VERSION
|
9
|
+
spec.authors = ["Eric Dejonckheere"]
|
10
|
+
spec.email = ["eric@aya.io"]
|
11
|
+
spec.summary = %q{Toki command-line client}
|
12
|
+
spec.description = %q{Toki command-line client: read your Toki data from the local database or from the App.net backup channel}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.required_ruby_version = '>= 1.9.3'
|
22
|
+
|
23
|
+
spec.add_dependency "thor", ">= 0.18"
|
24
|
+
spec.add_dependency "rest-client", ">= 1.6"
|
25
|
+
spec.add_dependency "amalgalite", ">= 1.3"
|
26
|
+
spec.add_dependency "terminal-table", ">= 1.4"
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
29
|
+
spec.add_development_dependency "rake"
|
30
|
+
end
|
data/bin/toki
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
# Toki command-line client
|
4
|
+
# Author: Eric Dejonckheere (http://app.net/ericd)
|
5
|
+
# Toki by: Keita Kobayashi (http://app.net/keita)
|
6
|
+
|
7
|
+
$PROGRAM_NAME = 'toki'
|
8
|
+
require_relative '../lib/TokiCLI'
|
9
|
+
|
10
|
+
TokiCLI::App.start ARGV
|
data/lib/TokiCLI.rb
ADDED
data/lib/TokiCLI/app.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TokiCLI
|
3
|
+
class App < Thor
|
4
|
+
package_name "TokiCLI"
|
5
|
+
%w{get_messages search_messages get_channels module status view}.each {|r| require_relative "#{r}"}
|
6
|
+
|
7
|
+
desc "total", "Shows the total usage of all apps"
|
8
|
+
option :adn, aliases: '-a', type: :boolean, desc: 'Select ADN channel as source (toki total -a)'
|
9
|
+
def total
|
10
|
+
if options[:adn]
|
11
|
+
toki = total_adn
|
12
|
+
else
|
13
|
+
toki = total_db
|
14
|
+
end
|
15
|
+
get_total(toki, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "auth", "Input your App.net token for authorization"
|
19
|
+
def auth
|
20
|
+
puts Status.paste_token
|
21
|
+
token = STDIN.gets.chomp!
|
22
|
+
unless token.nil?
|
23
|
+
save_token(token)
|
24
|
+
else
|
25
|
+
abort(Status.no_token)
|
26
|
+
end
|
27
|
+
puts Status.done
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "log APP", "Shows the complete log for an app"
|
31
|
+
option :adn, aliases: '-a', type: :boolean, desc: 'Select ADN channel as source (toki total -a)'
|
32
|
+
def log(*args)
|
33
|
+
abort(Status.specify_name) if args.empty?
|
34
|
+
if options[:adn]
|
35
|
+
toki = total_adn
|
36
|
+
else
|
37
|
+
toki = total_db
|
38
|
+
end
|
39
|
+
asked = args[0]
|
40
|
+
synced = toki.get_content(options)
|
41
|
+
app_data = toki.get_app(asked, synced)
|
42
|
+
v = TokiCLI::View.new
|
43
|
+
puts v.app_table(asked, app_data)
|
44
|
+
puts "\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def total_adn
|
50
|
+
clear
|
51
|
+
puts Status.get_all
|
52
|
+
init_toki_adn
|
53
|
+
end
|
54
|
+
|
55
|
+
def total_db
|
56
|
+
clear
|
57
|
+
puts "getting data from db\n"
|
58
|
+
init_toki_db
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_total(toki, options)
|
62
|
+
begin
|
63
|
+
apps = get_all(toki, options)
|
64
|
+
rescue Interrupt
|
65
|
+
puts Status.canceled
|
66
|
+
exit
|
67
|
+
rescue => e
|
68
|
+
puts Status.error(e)
|
69
|
+
exit
|
70
|
+
end
|
71
|
+
clear
|
72
|
+
puts TokiCLI::View.new.total_table(apps)
|
73
|
+
puts "\n"
|
74
|
+
end
|
75
|
+
|
76
|
+
def init_toki_db
|
77
|
+
TokiCLI::Toki.new(nil, nil)
|
78
|
+
end
|
79
|
+
|
80
|
+
def init_toki_adn(channel_id = get_channel_id)
|
81
|
+
TokiCLI::Toki.new("#{get_token}", channel_id)
|
82
|
+
end
|
83
|
+
|
84
|
+
def save_token(token)
|
85
|
+
File.write(Dir.home + '/.toki_token', token)
|
86
|
+
end
|
87
|
+
def get_token
|
88
|
+
if File.exist?(Dir.home + '/.toki_token')
|
89
|
+
File.read(Dir.home + '/.toki_token').chomp
|
90
|
+
else
|
91
|
+
abort Status.run_auth
|
92
|
+
end
|
93
|
+
end
|
94
|
+
def get_channel_id #TODO return ch['counts']['messages'] too
|
95
|
+
channels = ADNChannels::GetChannels.new(get_token).get_channels
|
96
|
+
channel_id = ''
|
97
|
+
channels.each do |ch|
|
98
|
+
if ch['type'] == 'us.kkob.toki.sync-b'
|
99
|
+
channel_id = ch['id']
|
100
|
+
break
|
101
|
+
end
|
102
|
+
end
|
103
|
+
return channel_id.to_i unless channel_id == ''
|
104
|
+
puts Status.no_channel
|
105
|
+
exit
|
106
|
+
end
|
107
|
+
def clear
|
108
|
+
puts "\e[H\e[2J"
|
109
|
+
end
|
110
|
+
def get_all(t, options)
|
111
|
+
t.all_data(t.get_content(options))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rest_client'
|
3
|
+
module ADNChannels
|
4
|
+
class GetChannels
|
5
|
+
def initialize(token)
|
6
|
+
@base_url = 'http://api.app.net'
|
7
|
+
@token = token
|
8
|
+
end
|
9
|
+
def get_channels
|
10
|
+
args = {:count => 200, :before_id => nil}
|
11
|
+
channels = []
|
12
|
+
loop do
|
13
|
+
@url = "#{@base_url}/users/me/channels?access_token=#{@token}&include_machine=1&include_message_annotations=1&include_deleted=0&include_html=0&count=#{args[:count]}&before_id=#{args[:before_id]}"
|
14
|
+
resp = JSON.parse(RestClient.get(@url))
|
15
|
+
resp['data'].each { |m| channels << m }
|
16
|
+
break unless resp['meta']['more']
|
17
|
+
args = {:count => 200, :before_id => resp['meta']['min_id']}
|
18
|
+
end
|
19
|
+
channels
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rest_client'
|
3
|
+
module ADNChannels
|
4
|
+
class GetMessages
|
5
|
+
def initialize(token)
|
6
|
+
@base_url = 'http://api.app.net'
|
7
|
+
@token = token
|
8
|
+
end
|
9
|
+
def get_messages(channel_id)
|
10
|
+
args = {:count => 200, :before_id => nil}
|
11
|
+
messages = []
|
12
|
+
@index = 1
|
13
|
+
puts "Downloading synced objects (~1MB/page):\n\n"
|
14
|
+
loop do
|
15
|
+
puts "--Downloading from page #{@index}...\n"
|
16
|
+
@url = "#{@base_url}/channels/#{channel_id}/messages?access_token=#{@token}&include_machine=1&include_message_annotations=1&include_deleted=0&include_html=0&count=#{args[:count]}&before_id=#{args[:before_id]}"
|
17
|
+
begin
|
18
|
+
data = RestClient.get(@url)
|
19
|
+
rescue Interrupt
|
20
|
+
puts TokiCLI::Status.canceled
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
resp = JSON.parse(data)
|
24
|
+
resp['data'].each { |m| messages << m }
|
25
|
+
break unless resp['meta']['more']
|
26
|
+
@index += 1
|
27
|
+
args = {:count => 200, :before_id => resp['meta']['min_id']}
|
28
|
+
end
|
29
|
+
messages
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TokiCLI
|
3
|
+
class Toki
|
4
|
+
def initialize(token, channel_id)
|
5
|
+
@token = token
|
6
|
+
@channel_id = channel_id
|
7
|
+
end
|
8
|
+
def get_content(options)
|
9
|
+
synced = []
|
10
|
+
if options[:adn]
|
11
|
+
messages = get_adn_data
|
12
|
+
messages.reverse.each do |obj|
|
13
|
+
synced << make_obj_from_adn(obj)
|
14
|
+
end
|
15
|
+
else
|
16
|
+
content = get_db_data
|
17
|
+
content.each do |obj|
|
18
|
+
synced << make_obj_from_db(obj)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
synced
|
22
|
+
end
|
23
|
+
def get_app(app, synced)
|
24
|
+
name_regex = /#{app.downcase}/
|
25
|
+
app_data = {}
|
26
|
+
totals = {}
|
27
|
+
synced.each do |obj|
|
28
|
+
next unless obj.app[:name].downcase =~ name_regex
|
29
|
+
unless totals[:app].nil?
|
30
|
+
totals[:app] = totals[:app] + obj.app[:total]
|
31
|
+
else
|
32
|
+
totals[:app] = obj.app[:total]
|
33
|
+
end
|
34
|
+
human = epoch_to_human(obj.app[:total])
|
35
|
+
app_data[obj.msg[:id]] = {
|
36
|
+
from: Time.at(obj.app[:active_from]).strftime("%Y/%m/%d %Hh:%Mm:%Ss"),
|
37
|
+
to: Time.at(obj.app[:active_to]).strftime("%Y/%m/%d %Hh:%Mm:%Ss"),
|
38
|
+
duration: "#{human[:hours]}h #{human[:minutes]}m #{human[:seconds]}s"
|
39
|
+
}
|
40
|
+
end
|
41
|
+
human = epoch_to_human(totals[:app])
|
42
|
+
app_data['total'] = "#{human[:hours]}h #{human[:minutes]}m #{human[:seconds]}s"
|
43
|
+
app_data
|
44
|
+
end
|
45
|
+
def all_data(synced)
|
46
|
+
totals = {}
|
47
|
+
synced.each do |obj|
|
48
|
+
unless totals[obj.app[:name]].nil?
|
49
|
+
totals[obj.app[:name]] = totals[obj.app[:name]] + obj.app[:total]
|
50
|
+
else
|
51
|
+
totals[obj.app[:name]] = obj.app[:total]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
bundles = {}
|
55
|
+
sorted = totals.sort_by{ |k,v| v }
|
56
|
+
sorted.each do |name, total|
|
57
|
+
human = epoch_to_human(total)
|
58
|
+
if human[:hours] < 1
|
59
|
+
bundles[name] = "#{human[:minutes]}m #{human[:seconds]}s"
|
60
|
+
else
|
61
|
+
bundles[name] = "#{human[:hours]}h #{human[:minutes]}m #{human[:seconds]}s"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
bundles
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def epoch_to_human(epoch)
|
70
|
+
hours = epoch / 3600
|
71
|
+
minutes = (epoch / 60 - hours * 60)
|
72
|
+
seconds = (epoch - (minutes * 60 + hours * 3600))
|
73
|
+
{hours: hours, minutes: minutes, seconds: seconds}
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_adn_data
|
77
|
+
channels = ADNChannels::GetMessages.new(@token) #ericd
|
78
|
+
channels.get_messages(@channel_id)
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_db_data
|
82
|
+
db = open_db
|
83
|
+
data = db.execute("SELECT * FROM KKAppActivity")
|
84
|
+
data
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
def open_db
|
90
|
+
tmp_path = "#{Dir.home}/temp/toki/"
|
91
|
+
db_path = "#{Dir.home}/Library/Containers/us.kkob.Toki/Data/Documents/toki_data.sqlite3"
|
92
|
+
FileUtils.mkdir_p(tmp_path)
|
93
|
+
FileUtils.copy(db_path, "#{tmp_path}/toki_data.bak")
|
94
|
+
Amalgalite::Database.new(db_path)
|
95
|
+
end
|
96
|
+
|
97
|
+
def make_obj_from_adn(obj)
|
98
|
+
msg = {}
|
99
|
+
usr = {}
|
100
|
+
app = {}
|
101
|
+
d = obj['annotations'][0]['value']['d']
|
102
|
+
msg[:id] = obj['id']
|
103
|
+
msg[:thread] = obj['thread_id']
|
104
|
+
msg[:date] = parsed_date obj['created_at']
|
105
|
+
usr[:username] = obj['user']['username']
|
106
|
+
usr[:name] = obj['user']['name']
|
107
|
+
usr[:id] = obj['user']['id']
|
108
|
+
app[:id] = d['id']
|
109
|
+
app[:uuid] = d['UUID']
|
110
|
+
app[:name] = d['bundleIdentifier']
|
111
|
+
app[:active_to] = d['activeTo']
|
112
|
+
app[:active_from] = d['activeFrom']
|
113
|
+
app[:total] = d['totalSeconds']
|
114
|
+
OpenStruct.new(app: app, msg: msg, usr: usr)
|
115
|
+
end
|
116
|
+
|
117
|
+
def make_obj_from_db(obj)
|
118
|
+
# content is an array:
|
119
|
+
# id (INTEGER) 0, bundleIdentifier (VARCHAR) 1, activeFrom (INTEGER) 2, activeTo (INTEGER) 3, totalSeconds (INTEGER) 4, UUID (VARCHAR) 5, synced (INTEGER) 6, availableToSync (INTEGER) 7
|
120
|
+
msg = {}
|
121
|
+
usr = {}
|
122
|
+
app = {}
|
123
|
+
msg[:id] = rand(100000000)
|
124
|
+
msg[:thread] = msg[:id]
|
125
|
+
msg[:date] = Time.now
|
126
|
+
usr[:username] = ENV['USERNAME']
|
127
|
+
usr[:name] = usr[:username]
|
128
|
+
usr[:id] = obj[5]
|
129
|
+
app[:id] = obj[0]
|
130
|
+
app[:uuid] = obj[5]
|
131
|
+
app[:name] = obj[1]
|
132
|
+
app[:active_to] = obj[3]
|
133
|
+
app[:active_from] = obj[2]
|
134
|
+
app[:total] = obj[4]
|
135
|
+
OpenStruct.new(app: app, msg: msg, usr: usr)
|
136
|
+
end
|
137
|
+
|
138
|
+
def parsed_date(string)
|
139
|
+
"#{string[0...10]} #{string[11...19]}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rest_client'
|
3
|
+
module ADNChannels
|
4
|
+
class SearchMessages
|
5
|
+
def initialize(token)
|
6
|
+
@base_url = 'http://api.app.net'
|
7
|
+
@token = token
|
8
|
+
end
|
9
|
+
def search_annotation(channel_id, query)
|
10
|
+
args = {:count => 200, :before_id => nil}
|
11
|
+
messages = []
|
12
|
+
loop do
|
13
|
+
@url = "#{@base_url}/channels/messages/search?query=#{query}&channel_ids=#{channel_id}&access_token=#{@token}&include_machine=1&include_message_annotations=1&include_deleted=0&count=#{args[:count]}&before_id=#{args[:before_id]}"
|
14
|
+
resp = JSON.parse(RestClient.get(@url))
|
15
|
+
resp['data'].each { |m| messages << m }
|
16
|
+
break unless resp['meta']['more']
|
17
|
+
args = {:count => 200, :before_id => resp['meta']['min_id']}
|
18
|
+
end
|
19
|
+
messages
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TokiCLI
|
3
|
+
class Status
|
4
|
+
def self.get_all
|
5
|
+
"Toki_CLI is crawling your Toki App.net channel to get your data.\nIt could take a while, please be patient.\n\n"
|
6
|
+
end
|
7
|
+
def self.canceled
|
8
|
+
"\n\nCanceled.\n\n"
|
9
|
+
end
|
10
|
+
def self.error(e)
|
11
|
+
"\n\nStopped. Error: \n#{e}\n\n"
|
12
|
+
end
|
13
|
+
def self.no_channel
|
14
|
+
"\n\nCan't find your Toki App.net channel, sorry.\n\n"
|
15
|
+
end
|
16
|
+
def self.paste_token
|
17
|
+
"\nPlease paste your App.net token:\n\n"
|
18
|
+
end
|
19
|
+
def self.no_token
|
20
|
+
"\nOops, I didn't get the token. Please try again.\n"
|
21
|
+
end
|
22
|
+
def self.done
|
23
|
+
"\nDone.\n"
|
24
|
+
end
|
25
|
+
def self.specify_name
|
26
|
+
"\nPlease specify the name of the app (or part of it).\n\n"
|
27
|
+
end
|
28
|
+
def self.run_auth
|
29
|
+
"\nPlease run `toki auth` first.\n\n"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/TokiCLI/view.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TokiCLI
|
3
|
+
class View
|
4
|
+
def total_table(list)
|
5
|
+
table = init_table
|
6
|
+
table.title = "Your apps monitored by Toki"
|
7
|
+
list.each do |k,v|
|
8
|
+
table << ["#{k}", "#{v}"]
|
9
|
+
end
|
10
|
+
table
|
11
|
+
end
|
12
|
+
def app_table(asked, app_data)
|
13
|
+
table = init_table
|
14
|
+
table.title = "Toki time tracking for '#{asked}'"
|
15
|
+
table.headings = ['From', 'To', 'Duration']
|
16
|
+
@index = 0
|
17
|
+
@length = app_data.length - 1
|
18
|
+
app_data.each do |id, log|
|
19
|
+
if id == 'total'
|
20
|
+
@total = log
|
21
|
+
next
|
22
|
+
end
|
23
|
+
@index += 1
|
24
|
+
table << ["#{log[:from]}", "#{log[:to]}", "#{log[:duration]}"]
|
25
|
+
table << :separator unless @index == @length
|
26
|
+
end
|
27
|
+
table << :separator
|
28
|
+
table << [{ :value => "Total: #{@total}", :colspan => 3, :alignment => :center }]
|
29
|
+
table
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def init_table
|
35
|
+
Terminal::Table.new do |t|
|
36
|
+
t.style = { :width => 75 }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: TokiCLI
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Dejonckheere
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.18'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.18'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rest-client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: amalgalite
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: terminal-table
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.4'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.6'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.6'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: 'Toki command-line client: read your Toki data from the local database
|
98
|
+
or from the App.net backup channel'
|
99
|
+
email:
|
100
|
+
- eric@aya.io
|
101
|
+
executables:
|
102
|
+
- toki
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- ".gitignore"
|
107
|
+
- Gemfile
|
108
|
+
- LICENSE.txt
|
109
|
+
- README.md
|
110
|
+
- Rakefile
|
111
|
+
- TokiCLI.gemspec
|
112
|
+
- bin/toki
|
113
|
+
- lib/TokiCLI.rb
|
114
|
+
- lib/TokiCLI/app.rb
|
115
|
+
- lib/TokiCLI/get_channels.rb
|
116
|
+
- lib/TokiCLI/get_messages.rb
|
117
|
+
- lib/TokiCLI/module.rb
|
118
|
+
- lib/TokiCLI/search_messages.rb
|
119
|
+
- lib/TokiCLI/status.rb
|
120
|
+
- lib/TokiCLI/version.rb
|
121
|
+
- lib/TokiCLI/view.rb
|
122
|
+
homepage: ''
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
metadata: {}
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 1.9.3
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 2.2.2
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: Toki command-line client
|
146
|
+
test_files: []
|
147
|
+
has_rdoc:
|