maid 0.1.2 → 0.1.3.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,89 +1,127 @@
1
- # From https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/numeric/time.rb, with some modifications since active_support ruins Logger by overriding its functionality.
2
1
  module Maid::NumericExtensions
3
- # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years.
4
- #
5
- # These methods use Time#advance for precise date calculations when using from_now, ago, etc.
6
- # as well as adding or subtracting their results from a Time object. For example:
7
- #
8
- # # equivalent to Time.now.advance(:months => 1)
9
- # 1.month.from_now
10
- #
11
- # # equivalent to Time.now.advance(:years => 2)
12
- # 2.years.from_now
13
- #
14
- # # equivalent to Time.now.advance(:months => 4, :years => 5)
15
- # (4.months + 5.years).from_now
16
- #
17
- # While these methods provide precise calculation when used as in the examples above, care
18
- # should be taken to note that this is not true if the result of `months', `years', etc is
19
- # converted before use:
20
- #
21
- # # equivalent to 30.days.to_i.from_now
22
- # 1.month.to_i.from_now
23
- #
24
- # # equivalent to 365.25.days.to_f.from_now
25
- # 1.year.to_f.from_now
26
- #
27
- # In such cases, Ruby's core
28
- # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and
29
- # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision
30
- # date and time arithmetic
31
- def seconds
32
- self
33
- end
34
- alias :second :seconds
2
+ # From https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/numeric/time.rb, with some modifications since active_support ruins Logger by overriding its functionality.
3
+ module Time
4
+ # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years.
5
+ #
6
+ # These methods use Time#advance for precise date calculations when using from_now, ago, etc.
7
+ # as well as adding or subtracting their results from a Time object. For example:
8
+ #
9
+ # # equivalent to Time.now.advance(:months => 1)
10
+ # 1.month.from_now
11
+ #
12
+ # # equivalent to Time.now.advance(:years => 2)
13
+ # 2.years.from_now
14
+ #
15
+ # # equivalent to Time.now.advance(:months => 4, :years => 5)
16
+ # (4.months + 5.years).from_now
17
+ #
18
+ # While these methods provide precise calculation when used as in the examples above, care
19
+ # should be taken to note that this is not true if the result of `months', `years', etc is
20
+ # converted before use:
21
+ #
22
+ # # equivalent to 30.days.to_i.from_now
23
+ # 1.month.to_i.from_now
24
+ #
25
+ # # equivalent to 365.25.days.to_f.from_now
26
+ # 1.year.to_f.from_now
27
+ #
28
+ # In such cases, Ruby's core
29
+ # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and
30
+ # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision
31
+ # date and time arithmetic
32
+ def seconds
33
+ self
34
+ end
35
+ alias :second :seconds
35
36
 
36
- def minutes
37
- self * 60
38
- end
39
- alias :minute :minutes
37
+ def minutes
38
+ self * 60
39
+ end
40
+ alias :minute :minutes
40
41
 
41
- def hours
42
- self * 3600
43
- end
44
- alias :hour :hours
42
+ def hours
43
+ self * 3600
44
+ end
45
+ alias :hour :hours
45
46
 
46
- def days
47
- self * 24.hours
48
- end
49
- alias :day :days
47
+ def days
48
+ self * 24.hours
49
+ end
50
+ alias :day :days
50
51
 
51
- def weeks
52
- self * 7.days
53
- end
54
- alias :week :weeks
52
+ def weeks
53
+ self * 7.days
54
+ end
55
+ alias :week :weeks
55
56
 
56
- def fortnights
57
- self * 2.weeks
58
- end
59
- alias :fortnight :fortnights
57
+ def fortnights
58
+ self * 2.weeks
59
+ end
60
+ alias :fortnight :fortnights
60
61
 
61
- # Reads best without arguments: 10.minutes.ago
62
- def ago(time = ::Time.now)
63
- time - self
64
- end
62
+ # Reads best without arguments: 10.minutes.ago
63
+ def ago(time = ::Time.now)
64
+ time - self
65
+ end
66
+
67
+ # Reads best with argument: 10.minutes.until(time)
68
+ alias :until :ago
69
+
70
+ # Reads best with argument: 10.minutes.since(time)
71
+ def since(time = ::Time.now)
72
+ time + self
73
+ end
74
+
75
+ # Reads best without arguments: 10.minutes.from_now
76
+ alias :from_now :since
77
+
78
+ ######################
79
+ ### Maid additions ###
80
+ ######################
65
81
 
66
- # Reads best with argument: 10.minutes.until(time)
67
- alias :until :ago
82
+ # TODO find a better place for these to live?
68
83
 
69
- # Reads best with argument: 10.minutes.since(time)
70
- def since(time = ::Time.now)
71
- time + self
84
+ # Reads well in a case like:
85
+ #
86
+ # 1.week.since? accessed_at('filename')
87
+ def since?(other_time)
88
+ other_time < self.ago
89
+ end
72
90
  end
91
+ module SizeToKb
92
+ # Enables Computer disk size conversion into kilobytes.
93
+ #
94
+ # Can convert megabytes, gigabytes and terabytes.
95
+ # Handles full name (megabyte), plural (megabytes) and symobl (mb/mB).
96
+ #
97
+ # 1.megabyte = 1024 kilobytes
73
98
 
74
- # Reads best without arguments: 10.minutes.from_now
75
- alias :from_now :since
99
+ def kb
100
+ self
101
+ end
102
+ alias :kilobytes :kb
103
+ alias :kilobyte :kb
104
+ alias :kB :kb
76
105
 
77
- ######################
78
- ### Maid additions ###
79
- ######################
106
+ def mb
107
+ self * 1024 ** 1
108
+ end
109
+ alias :megabytes :mb
110
+ alias :megabyte :mb
111
+ alias :mB :mb
80
112
 
81
- # TODO find a better place for these to live?
113
+ def gb
114
+ self * 1024 ** 2
115
+ end
116
+ alias :gigabytes :gb
117
+ alias :gigabyte :gb
118
+ alias :gB :gb
82
119
 
83
- # Reads well in a case like:
84
- #
85
- # 1.week.since? last_accessed('filename')
86
- def since?(other_time)
87
- other_time < self.ago
120
+ def tb
121
+ self * 1024 ** 3
122
+ end
123
+ alias :terabytes :tb
124
+ alias :terabyte :tb
125
+ alias :tB :tb
88
126
  end
89
127
  end
@@ -0,0 +1,17 @@
1
+ require 'rbconfig'
2
+
3
+ module Maid::Platform
4
+ class << self
5
+ def host_os
6
+ RbConfig::CONFIG['host_os']
7
+ end
8
+
9
+ def linux?
10
+ !!(host_os =~ /linux/i)
11
+ end
12
+
13
+ def osx?
14
+ !!(host_os =~ /darwin/i)
15
+ end
16
+ end
17
+ end
@@ -1,62 +1,71 @@
1
- # Sample Maid rules file -- a sampling to get you started.
1
+ # Sample Maid rules file -- some ideas to get you started.
2
2
  #
3
- # To use, remove ".sample" from the filename. Test using:
3
+ # To use, remove ".sample" from the filename, and modify as desired. Test using:
4
4
  #
5
- # maid -n
5
+ # maid clean -n
6
6
  #
7
- # For more help on Maid:
7
+ # **NOTE:** It's recommended you just use this as a template; if you run these rules on your machine without knowing what they do, you might run into unwanted results!
8
+ #
9
+ # Don't forget, it's just Ruby! You can define custom methods and use them below:
10
+ #
11
+ # def magic(*)
12
+ # # ...
13
+ # end
14
+ #
15
+ # If you come up with some cool tools of your own, please send me a pull request on GitHub!
8
16
  #
9
- # * Run `maid help`
10
- # * Read the README at http://github.com/benjaminoakes/maid
11
- # * For more DSL helper methods, please see the documentation of Maid::Tools at http://rubydoc.info/gems/maid/0.1.0/Maid/Tools
12
- # * Come up with some cool tools of your own? Fork, make your changes, and send me a pull request on GitHub!
13
- # * Ask me a question over email (hello@benjaminoakes.com) or Twitter (@benjaminoakes)
17
+ # For more help on Maid:
14
18
  #
19
+ # * Run `maid help`
20
+ # * Read the README, tutorial, and documentation at https://github.com/benjaminoakes/maid#maid
21
+ # * Ask me a question over email (hello@benjaminoakes.com) or Twitter (@benjaminoakes)
22
+
15
23
  Maid.rules do
16
- # NOTE: Currently, only Mac OS X supports `duration_s`.
17
- rule 'MP3s likely to be music' do
18
- dir('~/Downloads/*.mp3').each do |path|
19
- if duration_s(path) > 30.0
20
- move(path, '~/Music/iTunes/iTunes Media/Automatically Add to iTunes/')
21
- end
22
- end
23
- end
24
-
25
- # NOTE: Currently, only Mac OS X supports `downloaded_from`.
26
- rule 'Old files downloaded while developing/testing' do
27
- dir('~/Downloads/*').each do |path|
28
- if downloaded_from(path).any? {|u| u.match 'http://localhost' || u.match('http://staging.yourcompany.com') } && 1.week.since?(last_accessed(path))
29
- trash(path)
30
- end
31
- end
32
- end
24
+ # **NOTE:** It's recommended you just use this as a template; if you run these rules on your machine without knowing what they do, you might run into unwanted results!
33
25
 
34
26
  rule 'Linux ISOs, etc' do
35
- dir('~/Downloads/*.iso').each { |p| trash p }
27
+ trash(dir('~/Downloads/*.iso'))
36
28
  end
37
29
 
38
30
  rule 'Linux applications in Debian packages' do
39
- dir('~/Downloads/*.deb').each { |p| trash p }
31
+ trash(dir('~/Downloads/*.deb'))
40
32
  end
41
33
 
42
34
  rule 'Mac OS X applications in disk images' do
43
- dir('~/Downloads/*.dmg').each { |p| trash p }
35
+ trash(dir('~/Downloads/*.dmg'))
44
36
  end
45
37
 
46
38
  rule 'Mac OS X applications in zip files' do
47
- dir('~/Downloads/*.zip').select do |path|
48
- candidates = zipfile_contents(path)
49
- candidates.any? { |c| c.match(/\.app$/) }
50
- end.each { |p| trash p }
39
+ found = dir('~/Downloads/*.zip').select { |path|
40
+ zipfile_contents(path).any? { |c| c.match(/\.app$/) }
41
+ }
42
+
43
+ trash(found)
51
44
  end
52
45
 
53
46
  rule 'Misc Screenshots' do
54
47
  dir('~/Desktop/Screen shot *').each do |path|
55
- if 1.week.since?(last_accessed(path))
48
+ if 1.week.since?(accessed_at(path))
56
49
  move(path, '~/Documents/Misc Screenshots/')
57
50
  end
58
51
  end
59
52
  end
60
53
 
61
- # Add your own rules here.
54
+ # NOTE: Currently, only Mac OS X supports `duration_s`.
55
+ rule 'MP3s likely to be music' do
56
+ dir('~/Downloads/*.mp3').each do |path|
57
+ if duration_s(path) > 30.0
58
+ move(path, '~/Music/iTunes/iTunes Media/Automatically Add to iTunes/')
59
+ end
60
+ end
61
+ end
62
+
63
+ # NOTE: Currently, only Mac OS X supports `downloaded_from`.
64
+ rule 'Old files downloaded while developing/testing' do
65
+ dir('~/Downloads/*').each do |path|
66
+ if downloaded_from(path).any? { |u| u.match('http://localhost') || u.match('http://staging.yourcompany.com') } && 1.week.since?(accessed_at(path))
67
+ trash(path)
68
+ end
69
+ end
70
+ end
62
71
  end
@@ -8,6 +8,8 @@ require 'time'
8
8
  #
9
9
  # Some methods are not available on all platforms. An <tt>ArgumentError</tt> is raised when a command is not available. See tags: [Mac OS X]
10
10
  module Maid::Tools
11
+ include Deprecated
12
+
11
13
  # Move from <tt>from</tt> to <tt>to</tt>.
12
14
  #
13
15
  # The path is not moved if a file already exists at the destination with the same name. A warning is logged instead.
@@ -15,53 +17,139 @@ module Maid::Tools
15
17
  # This method delegates to FileUtils. The instance-level <tt>file_options</tt> hash is passed to control the <tt>:noop</tt> option.
16
18
  #
17
19
  # move('~/Downloads/foo.zip', '~/Archive/Software/Mac OS X/')
18
- def move(from, to)
19
- from = File.expand_path(from)
20
- to = File.expand_path(to)
21
- target = File.join(to, File.basename(from))
22
-
23
- unless File.exist?(target)
24
- @logger.info "mv #{from.inspect} #{to.inspect}"
25
- FileUtils.mv(from, to, @file_options)
26
- else
27
- @logger.warn "skipping #{from.inspect} because #{target.inspect} already exists"
20
+ #
21
+ # This method can handle multiple from paths.
22
+ #
23
+ # move(['~/Downloads/foo.zip', '~/Downloads/bar.zip'], '~/Archive/Software/Mac OS X/')
24
+ # move(dir('~/Downloads/*.zip'), '~/Archive/Software/Mac OS X/')
25
+ def move(froms, to)
26
+ Array(froms).each do |from|
27
+ from = File.expand_path(from)
28
+ to = File.expand_path(to)
29
+ target = File.join(to, File.basename(from))
30
+
31
+ unless File.exist?(target)
32
+ @logger.info "mv #{from.inspect} #{to.inspect}"
33
+ FileUtils.mv(from, to, @file_options)
34
+ else
35
+ @logger.warn "skipping #{from.inspect} because #{target.inspect} already exists"
36
+ end
28
37
  end
29
38
  end
30
39
 
31
40
  # Move the given path to the trash (as set by <tt>trash_path</tt>).
32
41
  #
33
42
  # The path is moved if a file already exists in the trash with the same name. However, the current date and time is appended to the filename.
43
+ #
44
+ # Note: the OS native "restore" or "put back" functionality for trashed files is not currently supported. However, they can be restored manually, and the Maid log can help assist with this.
45
+ #
46
+ # Options:
47
+ #
48
+ # - :remove_over => Fixnum (e.g. 1.gigabyte, 1024.megabytes)
49
+ # Remove files over the given size rather than moving to the trash.
50
+ # See also Maid::NumericExtensions::SizeToKb
34
51
  #
35
52
  # trash('~/Downloads/foo.zip')
36
- def trash(path)
37
- target = File.join(@trash_path, File.basename(path))
38
- safe_trash_path = File.join(@trash_path, "#{File.basename(path)} #{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}")
53
+ #
54
+ # This method can also handle multiple paths.
55
+ #
56
+ # trash(['~/Downloads/foo.zip', '~/Downloads/bar.zip'])
57
+ # trash(dir('~/Downloads/*.zip'))
58
+ def trash(paths, options = {})
59
+ # ## Implementation Notes
60
+ #
61
+ # Trashing files correctly is surprisingly hard. What Maid ends up doing is one the easiest, most foolproof solutions: moving the file.
62
+ #
63
+ # Unfortunately, that means it's not possile to restore files automatically in OSX or Ubuntu. The previous location of the file is lost.
64
+ #
65
+ # OSX support depends on AppleScript or would require a not-yet-written C extension to interface with the OS. The AppleScript solution is less than ideal: the user has to be logged in, Finder has to be running, and it makes the "trash can sound" every time a file is moved.
66
+ #
67
+ # Ubuntu makes it easy to implement, and there's a Python library for doing so (see `trash-cli`). However, there's not a Ruby equivalent yet.
39
68
 
40
- if File.exist?(target)
41
- move(path, safe_trash_path)
42
- else
43
- move(path, @trash_path)
69
+ Array(paths).each do |path|
70
+ target = File.join(@trash_path, File.basename(path))
71
+ safe_trash_path = File.join(@trash_path, "#{File.basename(path)} #{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}")
72
+
73
+ if options[:remove_over] &&
74
+ File.exist?(path) &&
75
+ disk_usage(path) > options[:remove_over]
76
+ remove(path)
77
+ end
78
+
79
+ if File.exist?(path)
80
+ if File.exist?(target)
81
+ move(path, safe_trash_path)
82
+ else
83
+ move(path, @trash_path)
84
+ end
85
+ end
44
86
  end
45
87
  end
46
88
 
47
- # Give all files matching the given glob.
89
+ # Remove the given path recursively.
90
+ #
91
+ # Options:
48
92
  #
49
- # Delgates to Dir.
93
+ # - :force => boolean
94
+ # - :secure => boolean (See FileUtils.remove_entry_secure for further details)
95
+ #
96
+ # remove('~/Downloads/foo.zip')
97
+ #
98
+ # This method can handle multiple remove paths.
99
+ #
100
+ # remove(['~/Downloads/foo.zip', '~/Downloads/bar.zip'])
101
+ # remove(dir('~/Downloads/*.zip'))
102
+ def remove(paths, options = {})
103
+ Array(paths).each do |path|
104
+ path = File.expand_path(path)
105
+ options = @file_options.merge(options)
106
+
107
+ @logger.info "Removing #{path.inspect}"
108
+ FileUtils.rm_r(path,options)
109
+ end
110
+ end
111
+
112
+ # Give all files matching the given glob.
50
113
  #
51
114
  # dir('~/Downloads/*.zip')
52
115
  def dir(glob)
53
116
  Dir[File.expand_path(glob)]
54
117
  end
55
118
 
119
+ # Creates a directory and all its parent directories.
120
+ #
121
+ # Options:
122
+ #
123
+ # - :mode, the symbolic and absolute mode both can be used.
124
+ # eg. 0700, 'u=wr,go=rr'
125
+ #
126
+ # mkdir('~/Downloads/Music/Pink.Floyd/', :mode => 0644)
127
+ def mkdir(path, options = {})
128
+ FileUtils.mkdir_p(File.expand_path(path), options)
129
+ end
130
+
56
131
  # Find matching files, akin to the Unix utility <tt>find</tt>.
57
132
  #
58
- # Delegates to Find.find.
133
+ # If no block is given, it will return an array.
134
+ #
135
+ # find '~/Downloads/' # => [...]
136
+ #
137
+ # or delegates to Find.find.
59
138
  #
60
139
  # find '~/Downloads/' do |path|
61
140
  # # ...
62
141
  # end
142
+ #
63
143
  def find(path, &block)
64
- Find.find(File.expand_path(path), &block)
144
+ expanded_path = File.expand_path(path)
145
+
146
+ if block.nil?
147
+ files = []
148
+ Find.find(expanded_path) { |file_path| files << file_path }
149
+ files
150
+ else
151
+ Find.find(expanded_path, &block)
152
+ end
65
153
  end
66
154
 
67
155
  # [Mac OS X] Use Spotlight to locate all files matching the given filename.
@@ -99,27 +187,93 @@ module Maid::Tools
99
187
 
100
188
  # Calculate disk usage of a given path.
101
189
  #
190
+ # FIXME: This reports in kilobytes, but should probably report in bytes.
191
+ #
102
192
  # disk_usage('foo.zip') # => 136
103
193
  def disk_usage(path)
104
194
  raw = cmd("du -s #{path.inspect}")
105
- raw.split(/\s+/).first.to_i
195
+ usage_kb = raw.split(/\s+/).first.to_i
196
+
197
+ if usage_kb.zero?
198
+ raise "Stopping pessimistically because of unexpected value from du (#{raw.inspect})"
199
+ else
200
+ usage_kb
201
+ end
202
+ end
203
+
204
+ # In Unix speak, "ctime".
205
+ #
206
+ # created_at('foo.zip') # => Sat Apr 09 10:50:01 -0400 2011
207
+ def created_at(path)
208
+ File.ctime(File.expand_path(path))
106
209
  end
107
210
 
108
211
  # In Unix speak, "atime".
109
212
  #
110
- # last_accessed('foo.zip') # => Sat Apr 09 10:50:01 -0400 2011
111
- def last_accessed(path)
213
+ # accessed_at('foo.zip') # => Sat Apr 09 10:50:01 -0400 2011
214
+ def accessed_at(path)
112
215
  File.atime(File.expand_path(path))
113
216
  end
114
217
 
218
+ alias :last_accessed :accessed_at
219
+ deprecated :last_accessed, :accessed_at
220
+
221
+ # In Unix speak, "mtime".
222
+ #
223
+ # modified_at('foo.zip') # => Sat Apr 09 10:50:01 -0400 2011
224
+ def modified_at(path)
225
+ File.mtime(File.expand_path(path))
226
+ end
227
+
115
228
  # Pulls and pushes the given git repository.
116
229
  #
117
- # You might also be interested in SparkleShare (http://sparkleshare.org/), a great git-based file syncronization project.
230
+ # Since this is deprecated, you might also be interested in SparkleShare (http://sparkleshare.org/), a great git-based file syncronization project.
118
231
  #
119
232
  # git_piston('~/code/projectname')
233
+ #
234
+ # @deprecated
120
235
  def git_piston(path)
121
236
  full_path = File.expand_path(path)
122
237
  stdout = cmd("cd #{full_path.inspect} && git pull && git push 2>&1")
123
238
  @logger.info "Fired git piston on #{full_path.inspect}. STDOUT:\n\n#{stdout}"
124
239
  end
240
+
241
+ deprecated :git_piston, 'SparkleShare (http://sparkleshare.org/)'
242
+
243
+ # [Rsync] Simple sync of two files/folders using rsync.
244
+ #
245
+ # See rsync man page for a detailed description.
246
+ #
247
+ # Options:
248
+ #
249
+ # - :delete => boolean
250
+ # - :verbose => boolean
251
+ # - :archive => boolean (default true)
252
+ # - :update => boolean (default true)
253
+ # - :exclude => string EXE :exclude => ".git" or :exclude => [".git", ".rvmrc"]
254
+ # - :prune_empty => boolean
255
+ #
256
+ # sync('~/music', '/backup/music')
257
+ def sync(from, to, options = {})
258
+ # expand path removes trailing slash
259
+ # cannot use str[-1] due to ruby 1.8.7 restriction
260
+ from = File.expand_path(from) + (from.end_with?('/') ? '/' : '')
261
+ to = File.expand_path(to) + (to.end_with?('/') ? '/' : '')
262
+ # default options
263
+ options = { :archive => true, :update => true }.merge(options)
264
+ ops = []
265
+ ops << '-a' if options[:archive]
266
+ ops << '-v' if options[:verbose]
267
+ ops << '-u' if options[:update]
268
+ ops << '-m' if options[:prune_empty]
269
+ ops << '-n' if @file_options[:noop]
270
+
271
+ Array(options[:exclude]).each do |path|
272
+ ops << "--exclude=#{path.inspect}"
273
+ end
274
+
275
+ ops << '--delete' if options[:delete]
276
+ stdout = cmd("rsync #{ops.join(' ')} #{from.inspect} #{to.inspect} 2>&1")
277
+ @logger.info "Fired sync from #{from.inspect} to #{to.inspect}. STDOUT:\n\n#{stdout}"
278
+ end
125
279
  end