everyday-cli-utils 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ service_name: travis-ci
2
+ repo_token: dLtsGeDKaRp2Jj9aynRpJ3Qx23AoJ3qaj
data/.gitignore ADDED
@@ -0,0 +1,19 @@
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
+
19
+ /.idea/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format progress
3
+ -rspec_helper
data/.travis.yml ADDED
@@ -0,0 +1,2 @@
1
+ language: ruby
2
+ gemfile: Gemfile.ci
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in everyday-cli-utils.gemspec
4
+ gemspec
data/Gemfile.ci ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'bundler', '~> 1.5'
4
+ gem 'rake', '~> 10.0'
5
+ gem 'simplecov', '>= 0.8.2'
6
+ gem 'coveralls', '>= 0.7.0'
7
+ gem 'rspec'
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
+ [![Build Status](https://travis-ci.org/henderea/everyday-cli-utils.png?branch=master)](https://travis-ci.org/henderea/everyday-cli-utils)
4
+ [![Code Climate](https://codeclimate.com/github/henderea/everyday-cli-utils.png)](https://codeclimate.com/github/henderea/everyday-cli-utils)
5
+ [![Coverage Status](https://coveralls.io/repos/henderea/everyday-cli-utils/badge.png?branch=master)](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,8 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new('spec')
7
+
8
+ task :default => :spec
@@ -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,3 @@
1
+ module EverydayCliUtils
2
+ VERSION = '0.0.2'
3
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'everyday-cli-utils/version'
2
+
3
+ module EverydayCliUtils
4
+ # Your code goes here...
5
+ 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
@@ -0,0 +1,2 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
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: