hackmac 1.2.1 → 1.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a23e0590d9f48c2f7b18432140531bc240f91e67bb5e459c6786d451920db43
4
- data.tar.gz: efe3f22eb263b39b20722cb98bd249d3ef26f832ce4e77252a15a7bfcc3c8718
3
+ metadata.gz: 7b280a3a7695d3ca2acc44f274d9602bcfccb8f24ea43a6e49b490830efed1e0
4
+ data.tar.gz: a8fdf6d9ccaf9cf736218a10e462c4522139e938ef0677234fe48af3ea278179
5
5
  SHA512:
6
- metadata.gz: 81dc68295902e4a986c3356fc13645549df3c9fa1e2c49b465ce32911f99ceda5c8abd028f9aab41b5c4d87c2afaf9d6af98fc9fcb1c02dd415bd2c54ef35011
7
- data.tar.gz: fe12bed6b4bbabdfb002b16bfbc3c6d1bdc1c363da20b9d4d960c72d11fb9f8622ae3dd8dc8416de0388400167b3988c53ccc0d2b0f1fb5b98c2a63d91e5630a
6
+ metadata.gz: 6cb72a600ddd93ac37f453b952b49e748a40626f1405d89803a64401c8aee7331c800d5b07d699ec45132ce1a26aa0288f2969798a1dcb6b1ee7598e1a090584
7
+ data.tar.gz: 32d42271e8814c01c80ac2ecb63d15080042abc9a2f265fccd82536680dbcfd17e595ba2cee78c05ee73a6595c41e0212a6ed834ca9c52929cb3ab40f595c633
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.1
1
+ 1.4.0
data/bin/gfxmon CHANGED
@@ -13,7 +13,7 @@ require 'amatch'
13
13
  require 'search_ui'
14
14
  include SearchUI
15
15
 
16
- $opts = go 'c:g:n:jlh', defaults: { ?s => true, ?n => 10 }
16
+ $opts = go 'c:m:n:jlh', defaults: { ?s => true, ?n => 5 }
17
17
 
18
18
  def usage
19
19
  puts <<~end
@@ -25,7 +25,7 @@ def usage
25
25
  -l output GPU performance data as list
26
26
  -j output GPU performance data as json
27
27
  -n SECONDS measure every SECONDS
28
- -g METRIC output graph for performance METRIC
28
+ -m METRIC output graph for performance METRIC
29
29
  -c COLOR output graph in this terminal COLOR (between 0 - 255)
30
30
 
31
31
  end
@@ -45,23 +45,16 @@ def ps
45
45
  }
46
46
  end
47
47
 
48
- def format_bytes(n, v)
49
- if n =~ /bytes/i
50
- Tins::Unit.format(v, format: '%.3f%U', unit: '')
51
- else
52
- v.to_s
53
- end
54
- end
55
-
56
48
  def list(ps)
57
49
  max = ps.keys.max_by(&:size)&.size&.to_i
50
+ include Hackmac::Graph::Formatters
58
51
  puts ps.map { |n, v|
59
- ("%-#{max}s" % n) + " " + ("%s" % format_bytes(n, v)).bold
52
+ ("%-#{max}s" % n) + " " + ("%s" % send(derive_formatter(n), v)).bold
60
53
  }
61
54
  end
62
55
 
63
56
  def choose_metric(ps)
64
- case metric = $opts[?g]
57
+ case metric = $opts[?m]
65
58
  when nil
66
59
  ps_names = ps.keys
67
60
  metric = Search.new(
@@ -87,75 +80,36 @@ def choose_metric(ps)
87
80
  fail "Metric #{metric} not known"
88
81
  end
89
82
 
83
+ def derive_formatter(metric)
84
+ case metric
85
+ when /bytes/i
86
+ :as_bytes
87
+ when /Hz/
88
+ :as_hertz
89
+ when /\(C\)/
90
+ :as_celsius
91
+ when /\(%\)/
92
+ :as_percent
93
+ else
94
+ :as_default
95
+ end
96
+ end
97
+
90
98
  def display_graph
91
99
  if metric = choose_metric(ps)
92
100
  sleep_duration = [ 1, ($opts[?n] || 10).to_i ].max
93
- data = []
94
- print hide_cursor
95
- at_exit do
96
- print reset, clear_screen, move_home, show_cursor
97
- end
98
- trap(:SIGWINCH) {
99
- lines, cols = Tins::Terminal.winsize
100
- print clear_screen,
101
- move_to(lines / 2, (cols - "Zzz…".size) / 2) { "Zzz…".italic }
102
- }
103
- loop do
104
- print clear_screen
105
-
106
- lines, cols = Tins::Terminal.winsize
107
-
108
- as_title = -> t {
109
- move_to(lines, (cols - t.size) / 2) { t.bold }
110
- }
111
-
112
- case value = ps[metric]
113
- when Float
114
- data << value
115
- else
116
- data << value.to_i
117
- end
118
- data = data.last(cols)
119
-
120
- y_width = (data.max - data.min).to_f
121
- if y_width == 0
122
- print(
123
- as_title.("#{metric} / #{sleep_duration}s"),
124
- move_to(lines / 2, (cols - "no data".size) / 2) { "no data".italic }
125
- )
126
- sleep sleep_duration
127
- print clear_screen
128
- next
129
- end
130
-
131
- unless c = $opts[?c]
132
- cs = (21..226).select { |d|
133
- Term::ANSIColor::Attribute[d].to_rgb_triple.to_hsl_triple.
134
- lightness < 40
135
- }
136
- c = cs[ metric.bytes.reduce(0, :+) % cs.size ]
137
- end
138
- color = Term::ANSIColor::Attribute[c]
139
- color_light = color.to_rgb_triple.to_hsl_triple.lighten(15) rescue color
140
-
141
- data.each_with_index do |value, x|
142
- x = x + cols - data.size + 1
143
- y = lines - (((value - data.min) * lines / y_width)).round + 1
144
- y.upto(lines) do |iy|
145
- print move_to(iy, x) { ' '.on_color(y == iy ? color : color_light) }
146
- end
147
- end
148
-
149
- print(
150
- as_title.("#{metric} #{format_bytes(metric, data.last)} / #{sleep_duration}s"),
151
- move_home { format_bytes(metric, data.max).bold },
152
- move_to_line(lines) { format_bytes(metric, data.min).bold }
153
- )
154
-
155
- sleep sleep_duration
156
- end
101
+ format_value = derive_formatter(metric)
102
+ graph = Hackmac::Graph.new(
103
+ title: metric,
104
+ sleep: sleep_duration,
105
+ value: -> _ { ps[metric] },
106
+ color: $opts[?c],
107
+ format_value:
108
+ )
109
+ graph.start
157
110
  end
158
- rescue Interrupt
111
+ ensure
112
+ graph&.stop
159
113
  end
160
114
 
161
115
  case
data/hackmac.gemspec CHANGED
@@ -1,19 +1,19 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: hackmac 1.2.1 ruby lib
2
+ # stub: hackmac 1.4.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "hackmac".freeze
6
- s.version = "1.2.1"
6
+ s.version = "1.4.0"
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
10
10
  s.authors = ["Florian Frank".freeze]
11
- s.date = "2023-05-14"
11
+ s.date = "2023-05-28"
12
12
  s.description = "This ruby gem provides some useful tools for working with a Hackintosh.".freeze
13
13
  s.email = "flori@ping.de".freeze
14
14
  s.executables = ["efi".freeze, "gfxmon".freeze, "usb".freeze]
15
- s.extra_rdoc_files = ["README.md".freeze, "lib/hackmac.rb".freeze, "lib/hackmac/asset_tools.rb".freeze, "lib/hackmac/config.rb".freeze, "lib/hackmac/disks.rb".freeze, "lib/hackmac/github_source.rb".freeze, "lib/hackmac/kext.rb".freeze, "lib/hackmac/kext_upgrader.rb".freeze, "lib/hackmac/oc.rb".freeze, "lib/hackmac/oc_upgrader.rb".freeze, "lib/hackmac/oc_validator.rb".freeze, "lib/hackmac/plist.rb".freeze, "lib/hackmac/url_download.rb".freeze, "lib/hackmac/utils.rb".freeze, "lib/hackmac/version.rb".freeze]
16
- s.files = [".gitignore".freeze, "Gemfile".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/efi".freeze, "bin/gfxmon".freeze, "bin/usb".freeze, "hackmac.gemspec".freeze, "lib/hackmac.rb".freeze, "lib/hackmac/asset_tools.rb".freeze, "lib/hackmac/config.rb".freeze, "lib/hackmac/disks.rb".freeze, "lib/hackmac/github_source.rb".freeze, "lib/hackmac/hackmac.yml".freeze, "lib/hackmac/kext.rb".freeze, "lib/hackmac/kext_upgrader.rb".freeze, "lib/hackmac/oc.rb".freeze, "lib/hackmac/oc_upgrader.rb".freeze, "lib/hackmac/oc_validator.rb".freeze, "lib/hackmac/plist.rb".freeze, "lib/hackmac/url_download.rb".freeze, "lib/hackmac/utils.rb".freeze, "lib/hackmac/version.rb".freeze]
15
+ s.extra_rdoc_files = ["README.md".freeze, "lib/hackmac.rb".freeze, "lib/hackmac/asset_tools.rb".freeze, "lib/hackmac/config.rb".freeze, "lib/hackmac/disks.rb".freeze, "lib/hackmac/github_source.rb".freeze, "lib/hackmac/graph.rb".freeze, "lib/hackmac/graph/display.rb".freeze, "lib/hackmac/kext.rb".freeze, "lib/hackmac/kext_upgrader.rb".freeze, "lib/hackmac/oc.rb".freeze, "lib/hackmac/oc_upgrader.rb".freeze, "lib/hackmac/oc_validator.rb".freeze, "lib/hackmac/plist.rb".freeze, "lib/hackmac/url_download.rb".freeze, "lib/hackmac/utils.rb".freeze, "lib/hackmac/version.rb".freeze]
16
+ s.files = [".gitignore".freeze, "Gemfile".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/efi".freeze, "bin/gfxmon".freeze, "bin/usb".freeze, "hackmac.gemspec".freeze, "lib/hackmac.rb".freeze, "lib/hackmac/asset_tools.rb".freeze, "lib/hackmac/config.rb".freeze, "lib/hackmac/disks.rb".freeze, "lib/hackmac/github_source.rb".freeze, "lib/hackmac/graph.rb".freeze, "lib/hackmac/graph/display.rb".freeze, "lib/hackmac/hackmac.yml".freeze, "lib/hackmac/kext.rb".freeze, "lib/hackmac/kext_upgrader.rb".freeze, "lib/hackmac/oc.rb".freeze, "lib/hackmac/oc_upgrader.rb".freeze, "lib/hackmac/oc_validator.rb".freeze, "lib/hackmac/plist.rb".freeze, "lib/hackmac/url_download.rb".freeze, "lib/hackmac/utils.rb".freeze, "lib/hackmac/version.rb".freeze]
17
17
  s.homepage = "http://github.com/flori/hackmac".freeze
18
18
  s.rdoc_options = ["--title".freeze, "Hackmac - Some useful tools for working with a Hackintosh".freeze, "--main".freeze, "README.md".freeze]
19
19
  s.rubygems_version = "3.3.26".freeze
@@ -0,0 +1,200 @@
1
+ class Hackmac::Graph
2
+ class Display
3
+ ANSI = Term::ANSIColor
4
+
5
+ class Cell < Struct.new(:char, :color, :on_color, :styles)
6
+ def ==(other)
7
+ to_a == other.to_a
8
+ end
9
+
10
+ def to_s
11
+ ANSI.color(color) {
12
+ ANSI.on_color(on_color) {
13
+ styles.inject(char) { |c, s|
14
+ ANSI.send(s) { c }
15
+ }
16
+ }
17
+ }
18
+ end
19
+ end
20
+
21
+ def initialize(lines, columns)
22
+ @lines_range = 1..lines
23
+ @columns_range = 1..columns
24
+ clear
25
+ end
26
+
27
+ def clear
28
+ @x = 1
29
+ @y = 1
30
+ @color = 15
31
+ @on_color = 0
32
+ @styles = []
33
+ @cells =
34
+ Array.new(lines) {
35
+ Array.new(columns) {
36
+ Cell.new(' ', @color, @on_color, @styles)
37
+ }
38
+ }
39
+ reset
40
+ end
41
+
42
+ def reset
43
+ @color = 15
44
+ @on_color = 0
45
+ @styles = []
46
+ self
47
+ end
48
+
49
+ def each(&block)
50
+ Enumerator.new do |enum|
51
+ @lines_range.each do |y|
52
+ @columns_range.each do |x|
53
+ enum.yield y, x, @cells[y - 1][x - 1]
54
+ end
55
+ end
56
+ end.each(&block)
57
+ end
58
+
59
+ def -(old)
60
+ dimensions != old.dimensions and raise ArgumentError,
61
+ "old dimensions #{old.dimensions.inspect} don't match #{dimensions.inspect}"
62
+ result = ''
63
+ each.zip(old.each) do |(my, mx, me), (_, _, old)|
64
+ if me != old
65
+ result << ANSI.move_to(my, mx) << me.to_s
66
+ end
67
+ end
68
+ result
69
+ end
70
+
71
+ def to_s
72
+ each.inject(ANSI.clear_screen) do |s, (y, x, c)|
73
+ s << ANSI.move_to(y, x) << c.to_s
74
+ end
75
+ end
76
+
77
+ def inspect
78
+ "#<#{self.class}: #{columns}×#{lines}>"
79
+ end
80
+
81
+ attr_reader :x
82
+
83
+ attr_reader :y
84
+
85
+ def lines
86
+ @lines_range.end
87
+ end
88
+
89
+ def columns
90
+ @columns_range.end
91
+ end
92
+
93
+ def dimensions
94
+ [ lines, columns ]
95
+ end
96
+
97
+ def styled(*s)
98
+ if nope = s.find { !style?(_1) }
99
+ raise ArgumentError, "#{nope} is not a style"
100
+ end
101
+ @styles = s
102
+ self
103
+ end
104
+
105
+ def at(y, x)
106
+ @lines_range.include?(y) or
107
+ raise ArgumentError, "y #{y} out of lines range #@lines_range"
108
+ @columns_range.include?(x) or
109
+ raise ArgumentError, "x #{x} out of columns range #@columns_range"
110
+ @y, @x = y, x
111
+ self
112
+ end
113
+
114
+ def put(char)
115
+ char.size == 1 or
116
+ raise ArgumentError, "#{char} is not single character"
117
+ @cells[@y - 1][@x - 1] = Cell.new(char, @color, @on_color, @styles)
118
+ self
119
+ end
120
+
121
+ def write(string)
122
+ string.each_char do |char|
123
+ put(char)
124
+ if x < columns - 1
125
+ at(y, x + 1)
126
+ else
127
+ break
128
+ end
129
+ end
130
+ self
131
+ end
132
+
133
+ def top
134
+ at(1, x)
135
+ end
136
+
137
+ def bottom
138
+ at(lines, x)
139
+ end
140
+
141
+ def left
142
+ at(y, 1)
143
+ end
144
+
145
+ def right
146
+ at(y, columns)
147
+ end
148
+
149
+ def centered
150
+ at(lines / 2, columns / 2)
151
+ end
152
+
153
+ def write_centered(string)
154
+ at(@y, (columns - string.size) / 2).write(string)
155
+ end
156
+
157
+ def get
158
+ @cells[@y - 1][@x - 1]
159
+ end
160
+
161
+ def color(color)
162
+ @color = attribute!(color)
163
+ self
164
+ end
165
+
166
+ def on_color(on_color)
167
+ @on_color = attribute!(on_color)
168
+ self
169
+ end
170
+
171
+ private
172
+
173
+ def style?(s)
174
+ [
175
+ :bold,
176
+ :dark,
177
+ :faint,
178
+ :italic,
179
+ :underline,
180
+ :underscore,
181
+ :blink,
182
+ :rapid_blink,
183
+ :reverse,
184
+ :negative,
185
+ :concealed,
186
+ :conceal,
187
+ :strikethrough,
188
+ ].member?(s)
189
+ end
190
+
191
+ def attribute?(a)
192
+ Term::ANSIColor::Attribute[a]
193
+ end
194
+
195
+ def attribute!(a)
196
+ attribute?(a) or
197
+ raise ArgumentError, "#{a.inspect} is not a color attribute"
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,201 @@
1
+ require 'term/ansicolor'
2
+ require 'tins'
3
+
4
+ class Hackmac::Graph
5
+ include Term::ANSIColor
6
+
7
+ include\
8
+ module Formatters
9
+ def as_bytes(value)
10
+ Tins::Unit.format(value, prefix: :uc, format: '%.3f%U', unit: 'B')
11
+ end
12
+
13
+ def as_hertz(value)
14
+ Tins::Unit.format(value, prefix: :uc, format: '%.3f%U', unit: 'Hz')
15
+ end
16
+
17
+ def as_celsius(value)
18
+ "#{value}°"
19
+ end
20
+
21
+ def as_percent(value)
22
+ "#{value}%"
23
+ end
24
+
25
+ def as_default(value)
26
+ value.to_s
27
+ end
28
+
29
+ self
30
+ end
31
+
32
+ def initialize(
33
+ title:,
34
+ value: -> i { 0 },
35
+ format_value: nil,
36
+ sleep: nil,
37
+ color: nil
38
+ )
39
+ sleep >= 0 or raise ArgumentError, 'sleep has to be >= 0'
40
+ @title = title
41
+ @value = value
42
+ @format_value = format_value
43
+ @sleep = sleep
44
+ @continue = false
45
+ @data = []
46
+ @color = color
47
+ @mutex = Mutex.new
48
+ end
49
+
50
+ def start
51
+ install_handlers
52
+ full_reset
53
+ start_loop
54
+ end
55
+
56
+ def stop
57
+ full_reset
58
+ @continue = false
59
+ end
60
+
61
+ private
62
+
63
+ def start_loop
64
+ full_reset
65
+ color = pick_color
66
+ color_light = color.to_rgb_triple.to_hsl_triple.lighten(15) rescue color
67
+ i = 0
68
+ @continue = true
69
+ while @continue
70
+ @full_reset and full_reset
71
+ perform hide_cursor
72
+
73
+ @data << @value.(i)
74
+ @data = data.last(columns)
75
+
76
+ y_width = (data.max - data.min).to_f
77
+ if y_width == 0
78
+ @display.reset.bottom.styled(:bold).
79
+ write_centered("#@title / #{sleep_duration}").
80
+ reset.centered.styled(:italic).write_centered("no data")
81
+ perform_display_diff
82
+ sleep_now
83
+ next
84
+ end
85
+
86
+ @display.reset
87
+ data.each_with_index do |value, x|
88
+ x = x + columns - data.size + 1
89
+ y = lines - (((value - data.min) * lines / y_width)).round + 1
90
+ y.upto(lines) do |iy|
91
+ @display.at(iy, x).on_color(
92
+ y == iy ? color : color_light
93
+ ).write(' ')
94
+ end
95
+ end
96
+
97
+ @display.reset.bottom.styled(:bold).
98
+ write_centered("#@title #{format_value(data.last)} / #{sleep_duration}")
99
+ @display.reset.styled(:bold).
100
+ left.top.write(format_value(data.max)).
101
+ left.bottom.write(format_value(data.min))
102
+
103
+ perform_display_diff
104
+ sleep_now
105
+ end
106
+ ensure
107
+ stop
108
+ end
109
+
110
+ def perform(*a)
111
+ print *a
112
+ end
113
+
114
+ def columns
115
+ @display.columns
116
+ end
117
+
118
+ def lines
119
+ @display.lines
120
+ end
121
+
122
+ attr_reader :data
123
+
124
+ def sleep_duration
125
+ "#{@sleep}s"
126
+ end
127
+
128
+ def format_value(value)
129
+ case @format_value
130
+ when Proc
131
+ @format_value.(value)
132
+ when Symbol
133
+ send(@format_value, value)
134
+ else
135
+ send(:as_default, value)
136
+ end
137
+ end
138
+
139
+ def pick_color
140
+ Term::ANSIColor::Attribute[
141
+ case @color
142
+ when Proc
143
+ @color.(@title)
144
+ when nil
145
+ cs = (21..226).select { |d|
146
+ Term::ANSIColor::Attribute[d].to_rgb_triple.to_hsl_triple.
147
+ lightness < 40
148
+ }
149
+ cs[ @title.bytes.reduce(0, :+) % cs.size ]
150
+ else
151
+ @color
152
+ end
153
+ ]
154
+ end
155
+
156
+ def sleep_now
157
+ sleep @sleep
158
+ end
159
+
160
+ def perform_display_diff
161
+ @mutex.synchronize do
162
+ unless @old_display && @old_display.dimensions == @display.dimensions
163
+ @old_display = @display.dup.clear
164
+ end
165
+ perform @display - @old_display
166
+ @display, @old_display = @old_display.clear, @display
167
+ perform move_to(lines, columns)
168
+ end
169
+ end
170
+
171
+
172
+ def normalize_value(value)
173
+ case value
174
+ when Float
175
+ value
176
+ else
177
+ value.to_i
178
+ end
179
+ end
180
+
181
+ def full_reset
182
+ @mutex.synchronize do
183
+ perform reset, clear_screen, move_home, show_cursor
184
+ winsize = Tins::Terminal.winsize
185
+ @display = Hackmac::Graph::Display.new(*winsize)
186
+ @old_display = Hackmac::Graph::Display.new(*winsize)
187
+ perform @display
188
+ @full_reset = false
189
+ end
190
+ end
191
+
192
+ def install_handlers
193
+ at_exit { full_reset }
194
+ trap(:SIGWINCH) do
195
+ @full_reset = true
196
+ perform reset, clear_screen, move_home, 'Zzz…'
197
+ end
198
+ end
199
+ end
200
+
201
+ require 'hackmac/graph/display'
@@ -1,6 +1,6 @@
1
1
  module Hackmac
2
2
  # Hackmac version
3
- VERSION = '1.2.1'
3
+ VERSION = '1.4.0'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
data/lib/hackmac.rb CHANGED
@@ -17,3 +17,4 @@ require 'hackmac/oc_upgrader'
17
17
  require 'hackmac/oc_validator'
18
18
  require 'hackmac/config'
19
19
  require 'hackmac/utils'
20
+ require 'hackmac/graph'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hackmac
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-14 00:00:00.000000000 Z
11
+ date: 2023-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gem_hadar
@@ -150,6 +150,8 @@ extra_rdoc_files:
150
150
  - lib/hackmac/config.rb
151
151
  - lib/hackmac/disks.rb
152
152
  - lib/hackmac/github_source.rb
153
+ - lib/hackmac/graph.rb
154
+ - lib/hackmac/graph/display.rb
153
155
  - lib/hackmac/kext.rb
154
156
  - lib/hackmac/kext_upgrader.rb
155
157
  - lib/hackmac/oc.rb
@@ -174,6 +176,8 @@ files:
174
176
  - lib/hackmac/config.rb
175
177
  - lib/hackmac/disks.rb
176
178
  - lib/hackmac/github_source.rb
179
+ - lib/hackmac/graph.rb
180
+ - lib/hackmac/graph/display.rb
177
181
  - lib/hackmac/hackmac.yml
178
182
  - lib/hackmac/kext.rb
179
183
  - lib/hackmac/kext_upgrader.rb