mez 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/bin/mez +33 -0
  3. data/lib/mez/VERSION.rb +3 -0
  4. data/lib/mez.rb +116 -0
  5. metadata +89 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9229e10affa135c3ce6542006affa29d1cf148da
4
+ data.tar.gz: e0559af63b3b33677d6b907f70e4e8f971d42cc2
5
+ SHA512:
6
+ metadata.gz: 98b194786f55dfed6770da15fcb8d2c01fcf974538503aea4799a8a34ed7ea34b9f9bb33d4edfc72898a7c5573f43b3dc445daec8839775cc78c3d1f0b8aee06
7
+ data.tar.gz: b00f64269b83b2a72202fac60890946dec3719d1d2786f370672bf999f05458bb5006ed153f269204fb02ac8a08126580a21aeb34e22a93b975bf6057ad448dc
data/bin/mez ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ require 'mez'
3
+
4
+ unless File.exist?(File.join(Mez::APP_DIR, 'folders.json'))
5
+ os_prefix = OS.windows? ? 'c:/files/' : '~/'
6
+ puts 'You are missing your configuration file!'
7
+ puts "It should be at #{Mez::CONFIG}, and should look like this:
8
+
9
+ {
10
+ \"projects\": [
11
+ \"#{os_prefix}work projects\",
12
+ \"#{os_prefix}personal projects\" ],
13
+ \"books\": [
14
+ \"#{os_prefix}fiction\",
15
+ \"#{os_prefix}nonfiction\" ]
16
+ }"
17
+ exit
18
+ end
19
+
20
+ total_size = 0
21
+ total_difference = 0
22
+
23
+ Mez.intro_line
24
+ JSON.parse(File.read(Mez::CONFIG)).each do |name, folderset|
25
+ size = Mez.setsize(folderset)
26
+ Mez.update(name, size)
27
+ total_size += size
28
+ yesterday = Mez.yesterday_size(name)
29
+ difference = yesterday ? size - yesterday : 0
30
+ total_difference += difference
31
+ Mez.report(name, size, difference)
32
+ end
33
+ Mez.closing_line(total_size, total_difference)
@@ -0,0 +1,3 @@
1
+ module Mez
2
+ VERSION = '0.8.1'.freeze
3
+ end
data/lib/mez.rb ADDED
@@ -0,0 +1,116 @@
1
+ # Configuration and the SQLite database are stored in ~/.mez/ on macOS
2
+ # and Linux, and in %localappdata%/mez on Windows.
3
+ require 'sqlite3'
4
+ require 'date'
5
+ require 'os'
6
+ require 'json'
7
+ require 'fileutils'
8
+ require 'win32ole' if OS.windows?
9
+
10
+ module Mez
11
+ APP_DIR = if OS.windows?
12
+ File.join(File.expand_path('~'), 'appdata', 'local', 'mez')
13
+ else
14
+ File.join(File.expand_path('~'), '.mez')
15
+ end
16
+ CONFIG = File.join(APP_DIR, 'folders.json')
17
+ DB = if File.exist?(File.join(APP_DIR, 'folders.db'))
18
+ SQLite3::Database.new(File.join(APP_DIR, 'folders.db'))
19
+ else
20
+ FileUtils.mkdir_p(APP_DIR) unless File.exist?(APP_DIR)
21
+ db = SQLite3::Database.new(File.join(APP_DIR, 'folders.db'))
22
+ db.execute('CREATE TABLE folders (name string, date string, size int,
23
+ PRIMARY KEY ("name", "date"))')
24
+ db
25
+ end
26
+
27
+ # Calculates the size of a folder and its contents as an integer.
28
+ # On Windows, uses the Win32 API, which caches folder information and makes
29
+ # repeated calls to this function very fast. On other systems, sums individual
30
+ # file sizes.
31
+ #
32
+ # @param folder [String] a path to a folder, eg '~/books' or 'c:\books'
33
+ # @return [Integer] the folder's size in bytes
34
+ def self.folder_size(folder)
35
+ if OS.windows?
36
+ # Much faster for repeated calls than calculating sizes in Ruby.
37
+ WIN32OLE.new('Scripting.FileSystemObject').getFolder(folder).size.to_i
38
+ else
39
+ size = 0
40
+ Dir.glob(File.join(folder, '**', '*')) { |file| size += File.size(file) }
41
+ size
42
+ end
43
+ end
44
+
45
+ # Uses commas as separators to make large numbers human-readable.
46
+ #
47
+ # @param num [Integer] any integer, eg -48151
48
+ # @return [String] a human readable number, eg "-48,151"
49
+ def self.humanise(num)
50
+ (prefix(num) == '-' ? '-' : '') + num
51
+ .abs
52
+ .to_s
53
+ .split('')
54
+ .reverse
55
+ .each_slice(3)
56
+ .map(&:join)
57
+ .join(',')
58
+ .reverse
59
+ end
60
+
61
+ # Provide a "+" or "-" prefix based on whether `n` is positive or negative.
62
+ #
63
+ # @param n [Number]
64
+ # @return [String] "+" or "-"
65
+ def self.prefix(n)
66
+ n >= 0 ? '+' : '-'
67
+ end
68
+
69
+ # Format a number for the 'change' column, meaning a + or - prefix and a
70
+ # number in bytes converted to a comma-separated number in IEC megabytes.
71
+ #
72
+ # @param n [Integer] size differential in bytes
73
+ # @return [String] "+48,151"
74
+ def self.difference_report(n)
75
+ n.zero? ? '' : prefix(n) + humanise(n.abs / 1_000_000)
76
+ end
77
+
78
+ def self.yesterday_size(name)
79
+ DB.get_first_value('SELECT size FROM folders
80
+ WHERE name = ? AND date <> ?
81
+ ORDER BY date DESC
82
+ LIMIT 1', name, Date.today.to_s)
83
+ end
84
+
85
+ def self.report(name, size, difference)
86
+ puts name.ljust(54) +
87
+ humanise(size / 1_000_000).rjust(10) +
88
+ difference_report(difference).rjust(16)
89
+ end
90
+
91
+ def self.intro_line
92
+ puts "\nFOLDERSET".ljust(56) + 'SIZE (MB)' + 'CHANGE (MB)'.rjust(16)
93
+ puts('-' * 80)
94
+ end
95
+
96
+ # Take an array of folder names, and return their total size in bytes.
97
+ #
98
+ # @param [Array<String>] array of folder names
99
+ # @return [Integer] total size of all folders and their contents
100
+ def self.setsize(folderset)
101
+ folderset.reduce(0) { |acc, elem| acc + Mez.folder_size(elem) }
102
+ end
103
+
104
+ def self.closing_line(total_size, total_difference)
105
+ puts('-' * 80)
106
+ puts 'TOTAL SIZE: ' +
107
+ humanise(total_size / 1_000_000) +
108
+ ('TOTAL CHANGE: ' + humanise(total_difference / 1_000_000)).rjust(59)
109
+ end
110
+
111
+ def self.update(name, size)
112
+ DB.execute('INSERT OR REPLACE INTO folders
113
+ (name, size, date)
114
+ VALUES (?,?,?)', name, size, Date.today.to_s)
115
+ end
116
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mez
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.1
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Plant
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: os
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.6
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.6
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.5'
55
+ description:
56
+ email: ryan@ryanplant.net
57
+ executables:
58
+ - mez
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - bin/mez
63
+ - lib/mez.rb
64
+ - lib/mez/VERSION.rb
65
+ homepage: https://github.com/ryantriangles/mez
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message: Please configure your folders.json!
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.5.1
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: A CLI utility to track directory sizes over time
89
+ test_files: []