everyday-cli-utils 0.0.2
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/.coveralls.yml +2 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.travis.yml +2 -0
- data/Gemfile +4 -0
- data/Gemfile.ci +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +33 -0
- data/Rakefile +8 -0
- data/everyday-cli-utils.gemspec +27 -0
- data/lib/everyday-cli-utils/ask.rb +30 -0
- data/lib/everyday-cli-utils/curses_utils.rb +38 -0
- data/lib/everyday-cli-utils/format.rb +128 -0
- data/lib/everyday-cli-utils/histogram.rb +43 -0
- data/lib/everyday-cli-utils/kmeans.rb +154 -0
- data/lib/everyday-cli-utils/maputil.rb +48 -0
- data/lib/everyday-cli-utils/mycurses.rb +199 -0
- data/lib/everyday-cli-utils/option.rb +21 -0
- data/lib/everyday-cli-utils/version.rb +3 -0
- data/lib/everyday-cli-utils.rb +5 -0
- data/spec/everyday-cli-utils/format_spec.rb +62 -0
- data/spec/everyday-cli-utils/kmeans_spec.rb +24 -0
- data/spec/everyday-cli-utils/maputil_spec.rb +62 -0
- data/spec/spec_helper.rb +2 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f620a7c1e67d6366c45323874f2346bf710ce9b6
|
4
|
+
data.tar.gz: 96c78fb64efef0e03dc1bf26b6f1d8a9e7b2d49f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5982ac1f4c14e8321e82b9b5c5d194226cd5721f08148d09d896c5d5e17edc6bd29ef450df64f3bc510988da0778a95822531829eff1fc20d3563c15f364564d
|
7
|
+
data.tar.gz: a54b86c5ca17e02dab387bfcf12550b2c32b34effa6b3d762e06ed124a891bb5980e3ca05da192445230064e41d87032d8e59573a20381cd413e9d2fd0c04f24
|
data/.coveralls.yml
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.ci
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Eric Henderson
|
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
|
+
# EverydayCliUtils
|
2
|
+
|
3
|
+
[](https://travis-ci.org/henderea/everyday-cli-utils)
|
4
|
+
[](https://codeclimate.com/github/henderea/everyday-cli-utils)
|
5
|
+
[](https://coveralls.io/r/henderea/everyday-cli-utils?branch=master)
|
6
|
+
|
7
|
+
A set of CLI and general utilities.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'everyday-cli-utils'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install everyday-cli-utils
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Contributing
|
28
|
+
|
29
|
+
1. Fork it ( http://github.com/henderea/everyday-cli-utils/fork )
|
30
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
31
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
32
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
33
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'everyday-cli-utils/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'everyday-cli-utils'
|
8
|
+
spec.version = EverydayCliUtils::VERSION
|
9
|
+
spec.authors = ['Eric Henderson']
|
10
|
+
spec.email = ['henderea@gmail.com']
|
11
|
+
spec.summary = %q{A few CLI utils}
|
12
|
+
spec.homepage = 'https://github.com/henderea/everyday-cli-utils'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
21
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
22
|
+
spec.add_development_dependency 'simplecov', '>= 0.8.2'
|
23
|
+
spec.add_development_dependency 'coveralls', '>= 0.7.0'
|
24
|
+
spec.add_development_dependency 'rspec'
|
25
|
+
|
26
|
+
spec.add_dependency 'curses'
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'readline'
|
2
|
+
|
3
|
+
module EverydayCliUtils
|
4
|
+
module Ask
|
5
|
+
def self::setup_options(options)
|
6
|
+
mapped = []
|
7
|
+
options.each_with_index { |v, k|
|
8
|
+
mapped << "#{k+1}) #{v.to_s}"
|
9
|
+
}
|
10
|
+
mapped.join("\n")
|
11
|
+
end
|
12
|
+
|
13
|
+
def self::ask(question, options, &block)
|
14
|
+
val = '-1'
|
15
|
+
while true
|
16
|
+
print "#{question}\n\n#{setup_options(options)}\n\n"
|
17
|
+
val = Readline.readline("Please enter your choice (1 - #{options.count}): ", true)
|
18
|
+
if !(val =~ /^\d+$/).nil? && (1..options.count).include?(val.to_i)
|
19
|
+
break
|
20
|
+
end
|
21
|
+
print "\n\nYou must enter an integer between 1 and #{options.count}. Please try again.\n\n"
|
22
|
+
end
|
23
|
+
block.call(options[val.to_i - 1])
|
24
|
+
end
|
25
|
+
|
26
|
+
def self::hash_to_options(hash, extra = [])
|
27
|
+
hash.keys + extra
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module CursesUtils
|
2
|
+
COLOR_TO_CURSES = {
|
3
|
+
:black => Curses::COLOR_BLACK,
|
4
|
+
:red => Curses::COLOR_RED,
|
5
|
+
:green => Curses::COLOR_GREEN,
|
6
|
+
:yellow => Curses::COLOR_YELLOW,
|
7
|
+
:blue => Curses::COLOR_BLUE,
|
8
|
+
:purple => Curses::COLOR_MAGENTA,
|
9
|
+
:cyan => Curses::COLOR_CYAN,
|
10
|
+
:white => Curses::COLOR_WHITE,
|
11
|
+
:none => -1,
|
12
|
+
}
|
13
|
+
|
14
|
+
def find_color(bgcolor, fgcolor)
|
15
|
+
@colors.find_index { |v| v[0] == (fgcolor || :none) && v[1] == (bgcolor || :none) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_color(bgcolor, fgcolor)
|
19
|
+
Curses::init_pair(@colors.count + 1, COLOR_TO_CURSES[fgcolor || :none], COLOR_TO_CURSES[bgcolor || :none])
|
20
|
+
ind = @colors.count + 1
|
21
|
+
@colors << [fgcolor || :none, bgcolor || :none]
|
22
|
+
ind
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
private
|
27
|
+
def handle_color(fgcolor, bgcolor)
|
28
|
+
return 0 if (fgcolor.nil? || fgcolor == :none) && (bgcolor.nil? || bgcolor == :none)
|
29
|
+
ind = find_color(bgcolor, fgcolor)
|
30
|
+
ind = ind.nil? ? add_color(bgcolor, fgcolor) : ind + 1
|
31
|
+
Curses::color_pair(ind)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_format(str)
|
35
|
+
bold, underline, fgcolor, bgcolor = Format::parse_format(str)
|
36
|
+
(bold ? Curses::A_BOLD : 0) | (underline ? Curses::A_UNDERLINE : 0) | handle_color(fgcolor, bgcolor)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module EverydayCliUtils
|
2
|
+
module Format
|
3
|
+
def self.build_format_hash(first_chr)
|
4
|
+
{
|
5
|
+
:black => "#{first_chr}0",
|
6
|
+
:red => "#{first_chr}1",
|
7
|
+
:green => "#{first_chr}2",
|
8
|
+
:yellow => "#{first_chr}3",
|
9
|
+
:blue => "#{first_chr}4",
|
10
|
+
:purple => "#{first_chr}5",
|
11
|
+
:cyan => "#{first_chr}6",
|
12
|
+
:white => "#{first_chr}7",
|
13
|
+
:none => nil,
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
FORMAT_TO_CODE = {
|
18
|
+
:bold => '1',
|
19
|
+
:underline => '4',
|
20
|
+
}
|
21
|
+
FG_COLOR_TO_CODE = build_format_hash('3')
|
22
|
+
BG_COLOR_TO_CODE = build_format_hash('4')
|
23
|
+
|
24
|
+
def self::format(text, format_code)
|
25
|
+
(format_code.nil? || format_code == '') ? text : "\e[#{format_code}m#{text}\e[0m"
|
26
|
+
end
|
27
|
+
|
28
|
+
def self::build_string(bold, underline, fgcolor, bgcolor)
|
29
|
+
str = ''
|
30
|
+
hit = false
|
31
|
+
hit, str = handle_bold(bold, hit, str)
|
32
|
+
hit, str = handle_underline(hit, str, underline)
|
33
|
+
hit, str = handle_fg_color(fgcolor, hit, str)
|
34
|
+
handle_bg_color(bgcolor, hit, str)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.handle_bold(bold, hit, str)
|
38
|
+
if bold
|
39
|
+
hit = true
|
40
|
+
str = FORMAT_TO_CODE[:bold]
|
41
|
+
end
|
42
|
+
return hit, str
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.handle_underline(hit, str, underline)
|
46
|
+
if underline
|
47
|
+
str += ';' if hit
|
48
|
+
hit = true
|
49
|
+
str += FORMAT_TO_CODE[:underline]
|
50
|
+
end
|
51
|
+
return hit, str
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.handle_fg_color(fgcolor, hit, str)
|
55
|
+
unless fgcolor.nil? || FG_COLOR_TO_CODE[fgcolor].nil?
|
56
|
+
str += ';' if hit
|
57
|
+
hit = true
|
58
|
+
str += FG_COLOR_TO_CODE[fgcolor]
|
59
|
+
end
|
60
|
+
return hit, str
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.handle_bg_color(bgcolor, hit, str)
|
64
|
+
unless bgcolor.nil? || BG_COLOR_TO_CODE[bgcolor].nil?
|
65
|
+
str += ';' if hit
|
66
|
+
str += BG_COLOR_TO_CODE[bgcolor]
|
67
|
+
end
|
68
|
+
str
|
69
|
+
end
|
70
|
+
|
71
|
+
def self::parse_format(str)
|
72
|
+
parts = str.split(';')
|
73
|
+
bold = false
|
74
|
+
underline = false
|
75
|
+
fgcolor = :none
|
76
|
+
bgcolor = :none
|
77
|
+
parts.each { |v|
|
78
|
+
if v == FORMAT_TO_CODE[:bold]
|
79
|
+
bold = true
|
80
|
+
elsif v == FORMAT_TO_CODE[:underline]
|
81
|
+
underline = true
|
82
|
+
elsif v[0] == '3'
|
83
|
+
fgcolor = FG_COLOR_TO_CODE.invert[v]
|
84
|
+
elsif v[0] == '4'
|
85
|
+
bgcolor = BG_COLOR_TO_CODE.invert[v]
|
86
|
+
end
|
87
|
+
}
|
88
|
+
return bold, underline, fgcolor, bgcolor
|
89
|
+
end
|
90
|
+
|
91
|
+
def self::colorize(text, fgcolor = nil, bgcolor = nil)
|
92
|
+
self::format(text, self::build_string(false, false, fgcolor, bgcolor))
|
93
|
+
end
|
94
|
+
|
95
|
+
def self::bold(text, fgcolor = nil, bgcolor = nil)
|
96
|
+
self::format(text, self::build_string(true, false, fgcolor, bgcolor))
|
97
|
+
end
|
98
|
+
|
99
|
+
def self::underline(text, fgcolor = nil, bgcolor = nil)
|
100
|
+
self::format(text, self::build_string(false, true, fgcolor, bgcolor))
|
101
|
+
end
|
102
|
+
|
103
|
+
def self::boldunderline(text, fgcolor = nil, bgcolor = nil)
|
104
|
+
self::format(text, self::build_string(true, true, fgcolor, bgcolor))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class String
|
110
|
+
alias :old_method_missing :method_missing
|
111
|
+
alias :old_respond_to? :respond_to?
|
112
|
+
|
113
|
+
def method_missing(method, *args)
|
114
|
+
name = method.to_s
|
115
|
+
colors = 'black|red|green|yellow|blue|purple|cyan|white|none'
|
116
|
+
if (name =~ /format(_bold)?(_underline)?(?:_fg_(#{colors}))?(?:_bg_(#{colors}))?/).nil?
|
117
|
+
old_method_missing(method, *args)
|
118
|
+
else
|
119
|
+
EverydayCliUtils::Format::format(self, EverydayCliUtils::Format::build_string(!$1.nil?, !$2.nil?, $3.nil? ? nil : $3.to_sym, $4.nil? ? nil : $4.to_sym))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def respond_to?(method)
|
124
|
+
name = method.to_s
|
125
|
+
colors = 'black|red|green|yellow|blue|purple|cyan|white|none'
|
126
|
+
(!(name =~ /format(_bold)?(_underline)?(?:_fg_(#{colors}))?(?:_bg_(#{colors}))?/).nil?) || old_respond_to?(method)
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative 'maputil'
|
2
|
+
|
3
|
+
module EverydayCliUtils
|
4
|
+
class Histogram
|
5
|
+
def self.setup(collection, height, width)
|
6
|
+
mi = collection.min
|
7
|
+
ma = collection.max
|
8
|
+
diff = ma - mi
|
9
|
+
step = diff.to_f / (width.to_f - 1)
|
10
|
+
counts = Array.new(width, 0)
|
11
|
+
collection.each { |v| counts[((v - mi).to_f / step.to_f).floor] += 1 }
|
12
|
+
max_y = counts.max
|
13
|
+
lines = Array.new(height) { ' ' * width }
|
14
|
+
return counts, lines, max_y, mi, step
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.add_graph(counts, height, lines, max_y, width)
|
18
|
+
(0...width).each { |i|
|
19
|
+
h = ((counts[i].to_f / max_y.to_f) * height.to_f).round
|
20
|
+
((height - h)...height).each { |j|
|
21
|
+
lines[j][i] = '#'
|
22
|
+
}
|
23
|
+
if h == 0 && counts[i] > 0
|
24
|
+
lines[height - 1][i] = '_'
|
25
|
+
end
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.add_averages(height, ks, lines, mi, step, width)
|
30
|
+
lines[height] = ' ' * width
|
31
|
+
ks.each { |v| lines[height][((v - mi) / step).to_i] = '|' }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module Enumerable
|
37
|
+
def histogram(ks = nil, width = 100, height = 50)
|
38
|
+
counts, lines, max_y, mi, step = EverydayCliUtils::Histogram.setup(self, height, width)
|
39
|
+
EverydayCliUtils::Histogram.add_graph(counts, height, lines, max_y, width)
|
40
|
+
EverydayCliUtils::Histogram.add_averages(height, ks, lines, mi, step, width) unless ks.nil?
|
41
|
+
lines
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require_relative 'maputil'
|
2
|
+
|
3
|
+
module EverydayCliUtils
|
4
|
+
class Kmeans
|
5
|
+
def self.normal(x, avg, std)
|
6
|
+
exp = -(((x - avg) / std) ** 2.0) / 2.0
|
7
|
+
((Math.exp(exp) / (std * Math.sqrt(2.0 * Math::PI))))
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.f_test(clusters, means, cnt, avg)
|
11
|
+
cnt2 = clusters.count { |i| !i.empty? }
|
12
|
+
ev = f_test_ev(avg, clusters, cnt2, means)
|
13
|
+
uv = f_test_uv(clusters, cnt, cnt2, means)
|
14
|
+
(ev / uv)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.f_test_ev(avg, clusters, cnt2, means)
|
18
|
+
ev = 0.0
|
19
|
+
(0...means.count).each { |i| ev += clusters[i].empty? ? 0.0 : clusters[i].count * ((means[i] - avg) ** 2.0) }
|
20
|
+
ev / (cnt2 - 1.0)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.f_test_uv(clusters, cnt, cnt2, means)
|
24
|
+
uv = 0.0
|
25
|
+
(0...means.count).each { |i|
|
26
|
+
unless clusters[i].empty?
|
27
|
+
(0...clusters[i].count).each { |j|
|
28
|
+
uv += (clusters[i][j] - means[i]) * (clusters[i][j] - means[i])
|
29
|
+
}
|
30
|
+
end
|
31
|
+
}
|
32
|
+
uv / (cnt - cnt2)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.f_test2(clusters, means, cnt)
|
36
|
+
uv = 0.0
|
37
|
+
cnt2 = clusters.count { |i| !i.empty? }
|
38
|
+
(0...means.count).each { |i| uv += f_test2_calc(clusters, i, means, uv) unless clusters[i].empty? }
|
39
|
+
(uv / (cnt - cnt2))
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.f_test2_calc(clusters, i, means, uv)
|
43
|
+
tmp = 0.0
|
44
|
+
(0...clusters[i].count).each { |j|
|
45
|
+
tmp += (clusters[i][j] - means[i]) ** 2.0
|
46
|
+
}
|
47
|
+
tmp /= clusters[i].count
|
48
|
+
Math.sqrt(tmp)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.nmeans_setup_1(collection)
|
52
|
+
su = collection.sum
|
53
|
+
cnt = collection.count
|
54
|
+
avg = su / cnt
|
55
|
+
ks1 = collection.kmeans(1)
|
56
|
+
return avg, cnt, ks1
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.nmeans_setup_2(collection, avg, cnt, ks1)
|
60
|
+
cso = collection.get_clusters(ks1)
|
61
|
+
ft1 = f_test2(cso, ks1, cnt)
|
62
|
+
ks = collection.kmeans(2)
|
63
|
+
cs = collection.get_clusters(ks)
|
64
|
+
ft = f_test(cs, ks, cnt, avg)
|
65
|
+
ft2 = f_test2(cs, ks, cnt)
|
66
|
+
return ft, ft1, ft2, ks
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.run_nmean(collection, avg, cnt, ft, ft2, k, ks)
|
70
|
+
kso = ks
|
71
|
+
fto = ft
|
72
|
+
fto2 = ft2
|
73
|
+
ks = collection.kmeans(k)
|
74
|
+
cs = collection.get_clusters(ks)
|
75
|
+
ft = f_test(cs, ks, cnt, avg)
|
76
|
+
ft2 = f_test2(cs, ks, cnt)
|
77
|
+
return ft, ft2, fto, fto2, ks, kso
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.run_nmeans(avg, cnt, collection, ft, ft1, ft2, ks, ks1, max_k, threshold)
|
81
|
+
(3..[max_k, cnt].min).each { |k|
|
82
|
+
ft, ft2, fto, fto2, ks, kso = run_nmean(collection, avg, cnt, ft, ft2, k, ks)
|
83
|
+
return kso if ((ft - fto) / fto) < threshold && fto2 < ft1
|
84
|
+
}
|
85
|
+
ft2 >= ft1 ? ks1 : ks
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.run_kmean(collection, ks)
|
89
|
+
kso = ks
|
90
|
+
clusters = collection.get_clusters(kso)
|
91
|
+
ks = []
|
92
|
+
clusters.each_with_index { |val, key| ks[key] = (val.count <= 0) ? kso[key] : (val.sum / val.count) }
|
93
|
+
ks.sort
|
94
|
+
return kso, ks
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
module Enumerable
|
100
|
+
def outliers(sensitivity = 0.5, k = nil)
|
101
|
+
ks = k.nil? ? nmeans : kmeans(k)
|
102
|
+
cs = get_clusters(ks)
|
103
|
+
|
104
|
+
outliers = []
|
105
|
+
|
106
|
+
ks.each_with_index { |avg, i| outliers += find_outliers(avg, cs, i, sensitivity) }
|
107
|
+
outliers
|
108
|
+
end
|
109
|
+
|
110
|
+
def find_outliers(avg, cs, i, sensitivity)
|
111
|
+
csi = cs[i]
|
112
|
+
std = csi.std_dev
|
113
|
+
cnt = csi.count
|
114
|
+
csi.select { |c| (EverydayCliUtils::Kmeans.normal(c, avg, std) * cnt) < sensitivity }
|
115
|
+
end
|
116
|
+
|
117
|
+
def nmeans(max_k = 10, threshold = 0.05)
|
118
|
+
collection = self.floats
|
119
|
+
avg, cnt, ks1 = EverydayCliUtils::Kmeans.nmeans_setup_1(collection)
|
120
|
+
return ks1 if cnt == 1
|
121
|
+
ft, ft1, ft2, ks = EverydayCliUtils::Kmeans.nmeans_setup_2(collection, avg, cnt, ks1)
|
122
|
+
EverydayCliUtils::Kmeans.run_nmeans(avg, cnt, collection, ft, ft1, ft2, ks, ks1, max_k, threshold)
|
123
|
+
end
|
124
|
+
|
125
|
+
def kmeans(k)
|
126
|
+
mi = min
|
127
|
+
ma = max
|
128
|
+
diff = ma - mi
|
129
|
+
ks = []
|
130
|
+
(1..k).each { |i| ks[i - 1] = mi + (i * (diff / (k + 1.0))) }
|
131
|
+
kso = false
|
132
|
+
while ks != kso
|
133
|
+
kso, ks = EverydayCliUtils::Kmeans.run_kmean(self, ks)
|
134
|
+
end
|
135
|
+
ks
|
136
|
+
end
|
137
|
+
|
138
|
+
def get_clusters(means)
|
139
|
+
clusters = Array.new(means.count) { Array.new }
|
140
|
+
each { |item|
|
141
|
+
cluster = false
|
142
|
+
distance = false
|
143
|
+
(0...means.count).each { |i|
|
144
|
+
diff = (means[i] - item).abs
|
145
|
+
if distance == false || diff < distance
|
146
|
+
cluster = i
|
147
|
+
distance = diff
|
148
|
+
end
|
149
|
+
}
|
150
|
+
clusters[cluster][clusters[cluster].count] = item
|
151
|
+
}
|
152
|
+
clusters
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Enumerable
|
2
|
+
def removefalse
|
3
|
+
select { |i| i }
|
4
|
+
end
|
5
|
+
|
6
|
+
def filtermap(&block)
|
7
|
+
map(&block).removefalse
|
8
|
+
end
|
9
|
+
|
10
|
+
def sum
|
11
|
+
reduce(:+)
|
12
|
+
end
|
13
|
+
|
14
|
+
def prod
|
15
|
+
reduce(:*)
|
16
|
+
end
|
17
|
+
|
18
|
+
def average
|
19
|
+
sum.to_f / count.to_f
|
20
|
+
end
|
21
|
+
|
22
|
+
def std_dev
|
23
|
+
avg = average
|
24
|
+
cnt = count.to_f
|
25
|
+
su = summap { |v| (v.to_f - avg.to_f) ** 2 }
|
26
|
+
Math.sqrt(su / cnt)
|
27
|
+
end
|
28
|
+
|
29
|
+
def floats
|
30
|
+
map(&:to_f)
|
31
|
+
end
|
32
|
+
|
33
|
+
def summap(&block)
|
34
|
+
map(&block).sum
|
35
|
+
end
|
36
|
+
|
37
|
+
def productmap(&block)
|
38
|
+
map(&block).prod
|
39
|
+
end
|
40
|
+
|
41
|
+
def chompall
|
42
|
+
map(&:chomp)
|
43
|
+
end
|
44
|
+
|
45
|
+
def join(join_str)
|
46
|
+
map(&:to_s).reduce { |a, b| a << join_str << b }
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'curses'
|
2
|
+
require_relative 'format'
|
3
|
+
require_relative 'curses_utils'
|
4
|
+
|
5
|
+
module EverydayCliUtils
|
6
|
+
class MyCurses
|
7
|
+
include CursesUtils
|
8
|
+
#region External
|
9
|
+
def initialize(use_curses, linesh, linesf)
|
10
|
+
@use_curses = use_curses
|
11
|
+
@linesh = linesh
|
12
|
+
@linesf = linesf
|
13
|
+
@colors = []
|
14
|
+
@headers = []
|
15
|
+
@bodies = []
|
16
|
+
@footers = []
|
17
|
+
@cur_l = 0
|
18
|
+
@max_l = 0
|
19
|
+
@ch = nil
|
20
|
+
setup_curses(linesf, linesh) if @use_curses
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup_curses(linesf, linesh)
|
24
|
+
Curses::noecho
|
25
|
+
Curses::init_screen
|
26
|
+
@subpad_start = linesh
|
27
|
+
update_subpad_size
|
28
|
+
@padh = Curses::Pad.new(linesh, Curses::cols)
|
29
|
+
@padb = Curses::Pad.new(Curses::lines - linesh - linesf, Curses::cols)
|
30
|
+
@padf = Curses::Pad.new(linesf, Curses::cols)
|
31
|
+
configure_curses
|
32
|
+
end
|
33
|
+
|
34
|
+
def configure_curses
|
35
|
+
@padh.keypad(true)
|
36
|
+
@padh.clear
|
37
|
+
@padh.nodelay = true
|
38
|
+
@padb.keypad(true)
|
39
|
+
@padb.clear
|
40
|
+
@padb.nodelay = true
|
41
|
+
@padf.keypad(true)
|
42
|
+
@padf.clear
|
43
|
+
@padf.nodelay = true
|
44
|
+
Curses::cbreak
|
45
|
+
Curses::start_color
|
46
|
+
Curses::use_default_colors
|
47
|
+
end
|
48
|
+
|
49
|
+
def clear
|
50
|
+
@headers = []
|
51
|
+
@bodies = []
|
52
|
+
@footers = []
|
53
|
+
end
|
54
|
+
|
55
|
+
def myprints
|
56
|
+
@use_curses ? print_curses : print_normal
|
57
|
+
end
|
58
|
+
|
59
|
+
def print_normal
|
60
|
+
@headers.each { |v| puts v }
|
61
|
+
@bodies.each { |v| puts v }
|
62
|
+
@footers.each { |v| puts v }
|
63
|
+
end
|
64
|
+
|
65
|
+
def print_curses
|
66
|
+
resize_curses
|
67
|
+
myprint(@headers.join("\n"), @padh)
|
68
|
+
myprint(@bodies.join("\n"), @padb)
|
69
|
+
myprint(@footers.join("\n"), @padf)
|
70
|
+
update_max_l
|
71
|
+
@cur_l = [@cur_l, @max_l].min
|
72
|
+
padh_refresh
|
73
|
+
padb_refresh
|
74
|
+
padf_refresh
|
75
|
+
end
|
76
|
+
|
77
|
+
def resize_curses
|
78
|
+
@padh.resize(@headers.count, Curses::cols)
|
79
|
+
@padb.resize(@bodies.count, Curses::cols)
|
80
|
+
@padf.resize(@footers.count, Curses::cols)
|
81
|
+
@padh.clear
|
82
|
+
@padb.clear
|
83
|
+
@padf.clear
|
84
|
+
@padh.setpos(0, 0)
|
85
|
+
@padb.setpos(0, 0)
|
86
|
+
@padf.setpos(0, 0)
|
87
|
+
end
|
88
|
+
|
89
|
+
def read_ch
|
90
|
+
@ch = @padf.getch
|
91
|
+
end
|
92
|
+
|
93
|
+
def clear_ch
|
94
|
+
read_ch
|
95
|
+
while @ch == 10 || @ch == Curses::Key::ENTER || @ch == Curses::Key::UP || @ch == Curses::Key::DOWN
|
96
|
+
read_ch
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def scroll_iteration
|
101
|
+
old_subpad_size = @subpad_size
|
102
|
+
update_subpad_size
|
103
|
+
update_max_l
|
104
|
+
update_scroll(@subpad_size != old_subpad_size)
|
105
|
+
sleep(0.05)
|
106
|
+
read_ch
|
107
|
+
end
|
108
|
+
|
109
|
+
def header_live_append(str)
|
110
|
+
@padh << str
|
111
|
+
padh_refresh
|
112
|
+
end
|
113
|
+
|
114
|
+
def body_live_append(str)
|
115
|
+
@padb << str
|
116
|
+
padb_refresh
|
117
|
+
end
|
118
|
+
|
119
|
+
def footer_live_append(str)
|
120
|
+
@padf << str
|
121
|
+
padf_refresh
|
122
|
+
end
|
123
|
+
|
124
|
+
def dispose
|
125
|
+
Curses::close_screen if @use_curses
|
126
|
+
end
|
127
|
+
|
128
|
+
#endregion
|
129
|
+
|
130
|
+
#region Internal
|
131
|
+
def myputs(text, pad)
|
132
|
+
myprint("#{text}\n", pad)
|
133
|
+
end
|
134
|
+
|
135
|
+
def myprint(text, pad)
|
136
|
+
if @use_curses
|
137
|
+
if text.include?("\e")
|
138
|
+
pieces = text.scan(/#{"\e"}\[(.+?)m([^#{"\e"}]+?)#{"\e"}\[0m|([^#{"\e"}]+)/)
|
139
|
+
pieces.each { |v|
|
140
|
+
if v[2].nil?
|
141
|
+
pad.attron(get_format(v[0])) {
|
142
|
+
pad << v[1]
|
143
|
+
}
|
144
|
+
else
|
145
|
+
pad << v[2]
|
146
|
+
end
|
147
|
+
}
|
148
|
+
else
|
149
|
+
pad << text
|
150
|
+
end
|
151
|
+
else
|
152
|
+
print text
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def update_max_l
|
157
|
+
@max_l = [0, @bodies.count - @subpad_size].max
|
158
|
+
end
|
159
|
+
|
160
|
+
def update_subpad_size
|
161
|
+
Curses::refresh
|
162
|
+
@subpad_size = Curses::lines - @linesh - @linesf
|
163
|
+
end
|
164
|
+
|
165
|
+
def padh_refresh
|
166
|
+
@padh.refresh(0, 0, 0, 0, @subpad_start - 1, Curses::cols - 1)
|
167
|
+
end
|
168
|
+
|
169
|
+
def padb_refresh
|
170
|
+
@padb.refresh(@cur_l, 0, @subpad_start, 0, @subpad_start + @subpad_size - 1, Curses::cols - 1)
|
171
|
+
end
|
172
|
+
|
173
|
+
def padf_refresh
|
174
|
+
@padf.refresh(0, 0, @subpad_start + [@subpad_size, @bodies.count].min, 0, @subpad_start + [@subpad_size, @bodies.count].min + @footers.count, Curses::cols - 1)
|
175
|
+
end
|
176
|
+
|
177
|
+
def update_scroll(force_refresh = false)
|
178
|
+
if @ch == Curses::Key::UP
|
179
|
+
@cur_l = [0, @cur_l - 1].max
|
180
|
+
elsif @ch == Curses::Key::DOWN
|
181
|
+
@cur_l = [@max_l, @cur_l + 1].min
|
182
|
+
end
|
183
|
+
@cur_l = [@cur_l, @max_l].min
|
184
|
+
if @ch == Curses::Key::UP || @ch == Curses::Key::DOWN || force_refresh
|
185
|
+
Curses::refresh
|
186
|
+
padh_refresh
|
187
|
+
padb_refresh
|
188
|
+
padf_refresh
|
189
|
+
end
|
190
|
+
@cur_l
|
191
|
+
end
|
192
|
+
|
193
|
+
#endregion
|
194
|
+
|
195
|
+
attr_reader :ch
|
196
|
+
attr_accessor :bodies, :headers, :footers
|
197
|
+
private :myputs, :myprint, :update_max_l, :update_subpad_size, :padh_refresh, :padb_refresh, :padf_refresh, :update_scroll
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module EverydayCliUtils
|
2
|
+
class Option
|
3
|
+
def self.add_option(options, opts, names, opt_name, settings = {})
|
4
|
+
opts.on(*names) {
|
5
|
+
options[opt_name] = !settings[:toggle] || !options[opt_name]
|
6
|
+
yield if block_given?
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.add_option_with_param(options, opts, names, opt_name, settings = {})
|
11
|
+
opts.on(*names, settings[:type] || String) { |param|
|
12
|
+
if settings[:append]
|
13
|
+
options[opt_name] << param
|
14
|
+
else
|
15
|
+
options[opt_name] = param
|
16
|
+
end
|
17
|
+
yield if block_given?
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative '../../lib/everyday-cli-utils/format'
|
2
|
+
|
3
|
+
def extract_format(text)
|
4
|
+
(text.scan(/#{"\e"}\[(.+?)m([^#{"\e"}]+?)#{"\e"}\[0m|([^#{"\e"}]+)/))[0][0]
|
5
|
+
end
|
6
|
+
|
7
|
+
describe EverydayCliUtils::Format do
|
8
|
+
it 'adds formatting methods to String' do
|
9
|
+
str = 'hi'
|
10
|
+
str.respond_to?(:format_bold_underline_fg_yellow_bg_green).should be_true
|
11
|
+
str.respond_to?(:format_underline_bg_green).should be_true
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'does the same formatting in the String methods and the Format methods' do
|
15
|
+
str = 'hi'
|
16
|
+
format_str = EverydayCliUtils::Format.boldunderline(str, :yellow, :green)
|
17
|
+
string_str = str.format_bold_underline_fg_yellow_bg_green
|
18
|
+
format_str.should eq string_str
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'can parse a format it creates' do
|
22
|
+
str = 'hi'
|
23
|
+
format_str = EverydayCliUtils::Format.bold(str, :yellow, :green)
|
24
|
+
piece = extract_format(format_str)
|
25
|
+
bold, underline, fgcolor, bgcolor = EverydayCliUtils::Format.parse_format(piece)
|
26
|
+
bold.should be_true
|
27
|
+
underline.should be_false
|
28
|
+
fgcolor.should eq :yellow
|
29
|
+
bgcolor.should eq :green
|
30
|
+
|
31
|
+
format_str = EverydayCliUtils::Format.underline(str, :yellow, :green)
|
32
|
+
piece = extract_format(format_str)
|
33
|
+
bold, underline, fgcolor, bgcolor = EverydayCliUtils::Format.parse_format(piece)
|
34
|
+
bold.should be_false
|
35
|
+
underline.should be_true
|
36
|
+
fgcolor.should eq :yellow
|
37
|
+
bgcolor.should eq :green
|
38
|
+
|
39
|
+
format_str = EverydayCliUtils::Format.colorize(str, :yellow, :green)
|
40
|
+
piece = extract_format(format_str)
|
41
|
+
bold, underline, fgcolor, bgcolor = EverydayCliUtils::Format.parse_format(piece)
|
42
|
+
bold.should be_false
|
43
|
+
underline.should be_false
|
44
|
+
fgcolor.should eq :yellow
|
45
|
+
bgcolor.should eq :green
|
46
|
+
|
47
|
+
format_str = EverydayCliUtils::Format.boldunderline(str, :yellow, :green)
|
48
|
+
piece = extract_format(format_str)
|
49
|
+
bold, underline, fgcolor, bgcolor = EverydayCliUtils::Format.parse_format(piece)
|
50
|
+
bold.should be_true
|
51
|
+
underline.should be_true
|
52
|
+
fgcolor.should eq :yellow
|
53
|
+
bgcolor.should eq :green
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'still works with the default String method_missing and respond_to?' do
|
57
|
+
str = 'hi'
|
58
|
+
str.respond_to?(:split).should be_true
|
59
|
+
str.respond_to?(:hi).should be_false
|
60
|
+
expect { str.hi }.to raise_error(NameError)
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative '../../lib/everyday-cli-utils/kmeans'
|
2
|
+
|
3
|
+
describe EverydayCliUtils::Kmeans do
|
4
|
+
it 'finds the right clusters in simple data' do
|
5
|
+
arr = [100, 200, 99, 400, 98, 201, 101, 405, 102]
|
6
|
+
nmeans = arr.nmeans
|
7
|
+
nmeans.count.should eq 3
|
8
|
+
nmeans.should eq [100, 200.5, 402.5]
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'limits number of clusters found' do
|
12
|
+
arr = (1..50).to_a.map { |v| (v * (101-v)) ** 2 }
|
13
|
+
nmeans = arr.nmeans(5)
|
14
|
+
nmeans.count.should eq 5
|
15
|
+
nmeans = arr.nmeans(3)
|
16
|
+
nmeans.count.should eq 3
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'finds outliers in simple data' do
|
20
|
+
arr = [100, 200, 99, 400, 98, 201, 101, 405, 102]
|
21
|
+
outliers = arr.outliers
|
22
|
+
outliers.should eq [400.0, 405.0]
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative '../../lib/everyday-cli-utils/maputil'
|
2
|
+
|
3
|
+
describe 'maputil' do
|
4
|
+
it 'provides a shortcut for removing false values from a list' do
|
5
|
+
arr = [0, false, false, 3, 4, false, 6, false, 8, 9]
|
6
|
+
arr.removefalse.should eq [0, 3, 4, 6, 8, 9]
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'allows a filtered mapping' do
|
10
|
+
arr = (0..9).to_a
|
11
|
+
arr2 = arr.filtermap { |v| v % 3 == 0 ? false : v ** 2 }
|
12
|
+
arr2.should eq [1, 4, 16, 25, 49, 64]
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'provides a shortcut for summing the values of an list' do
|
16
|
+
arr = (0..9).to_a
|
17
|
+
sum = arr.sum
|
18
|
+
sum.should eq 45
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'provides a shortcut for calculating the product of the values of an list' do
|
22
|
+
arr = (1..4).to_a
|
23
|
+
prod = arr.prod
|
24
|
+
prod.should eq 24
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'provides a shortcut for calculating the average of the values of an list' do
|
28
|
+
arr = (0..9).to_a
|
29
|
+
avg = arr.floats.average
|
30
|
+
avg.should eq 4.5
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'provides a shortcut for calculating the standard deviation of the values of an list' do
|
34
|
+
arr = (0..9).to_a.floats
|
35
|
+
std_dev = arr.std_dev
|
36
|
+
std_dev.should eq Math.sqrt(8.25)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'provides a shortcut for calculating the sum of a mapped list' do
|
40
|
+
arr = (0..9).to_a
|
41
|
+
summap = arr.summap { |v| v ** 2 }
|
42
|
+
summap.should eq 285
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'provides a shortcut for calculating the product of a mapped list' do
|
46
|
+
arr = (1..4).to_a
|
47
|
+
prodmap = arr.productmap { |v| v + 1 }
|
48
|
+
prodmap.should eq 120
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'provides a shortcut to remove newlines from all strings in a list' do
|
52
|
+
arr = (0..9).to_a.map { |v| "#{v}\n" }
|
53
|
+
arr.should eq %W(0\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n)
|
54
|
+
arr.chompall.should eq %w(0 1 2 3 4 5 6 7 8 9)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'provides a shortcut to join elements in a list' do
|
58
|
+
list = ('a'..'f')
|
59
|
+
joined = list.join('-')
|
60
|
+
joined.should eq 'a-b-c-d-e-f'
|
61
|
+
end
|
62
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: everyday-cli-utils
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Henderson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.8.2
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.8.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: coveralls
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.7.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.7.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: curses
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- henderea@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".coveralls.yml"
|
105
|
+
- ".gitignore"
|
106
|
+
- ".rspec"
|
107
|
+
- ".travis.yml"
|
108
|
+
- Gemfile
|
109
|
+
- Gemfile.ci
|
110
|
+
- LICENSE.txt
|
111
|
+
- README.md
|
112
|
+
- Rakefile
|
113
|
+
- everyday-cli-utils.gemspec
|
114
|
+
- lib/everyday-cli-utils.rb
|
115
|
+
- lib/everyday-cli-utils/ask.rb
|
116
|
+
- lib/everyday-cli-utils/curses_utils.rb
|
117
|
+
- lib/everyday-cli-utils/format.rb
|
118
|
+
- lib/everyday-cli-utils/histogram.rb
|
119
|
+
- lib/everyday-cli-utils/kmeans.rb
|
120
|
+
- lib/everyday-cli-utils/maputil.rb
|
121
|
+
- lib/everyday-cli-utils/mycurses.rb
|
122
|
+
- lib/everyday-cli-utils/option.rb
|
123
|
+
- lib/everyday-cli-utils/version.rb
|
124
|
+
- spec/everyday-cli-utils/format_spec.rb
|
125
|
+
- spec/everyday-cli-utils/kmeans_spec.rb
|
126
|
+
- spec/everyday-cli-utils/maputil_spec.rb
|
127
|
+
- spec/spec_helper.rb
|
128
|
+
homepage: https://github.com/henderea/everyday-cli-utils
|
129
|
+
licenses:
|
130
|
+
- MIT
|
131
|
+
metadata: {}
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options: []
|
134
|
+
require_paths:
|
135
|
+
- lib
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
requirements: []
|
147
|
+
rubyforge_project:
|
148
|
+
rubygems_version: 2.2.0
|
149
|
+
signing_key:
|
150
|
+
specification_version: 4
|
151
|
+
summary: A few CLI utils
|
152
|
+
test_files:
|
153
|
+
- spec/everyday-cli-utils/format_spec.rb
|
154
|
+
- spec/everyday-cli-utils/kmeans_spec.rb
|
155
|
+
- spec/everyday-cli-utils/maputil_spec.rb
|
156
|
+
- spec/spec_helper.rb
|
157
|
+
has_rdoc:
|