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 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: