mfd 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,314 @@
1
+ require 'mfd/asset'
2
+ require 'optparse'
3
+
4
+ module Mfd
5
+ class Cli
6
+
7
+ def initialize
8
+ @predicates = ['(true)']
9
+ @print_command = false
10
+ @ascii_null = false
11
+ @live = false
12
+ @count = false
13
+
14
+ @opt = OptionParser.new
15
+ declare_options!
16
+ end
17
+
18
+ def parse_options!(args)
19
+ if args.empty?
20
+ puts @opt
21
+ exit
22
+ end
23
+
24
+ @opt.parse!(args)
25
+
26
+ if args.size == 1
27
+ dir = args.first
28
+ dir = File.expand_path dir unless dir =~ /^\//
29
+ scope = "-onlyin '#{escape dir}'"
30
+ else
31
+ scope = ''
32
+ end
33
+
34
+ query_string = @predicates.join(' && ')
35
+
36
+ cmd = "mdfind #{scope} \
37
+ #{@ascii_null ? '-0' : ''} \
38
+ #{@live ? '-live' : ''} \
39
+ #{@count ? '-count' : ''} \
40
+ '#{query_string}'"
41
+
42
+ puts cmd if @print_command
43
+
44
+ system cmd
45
+ end
46
+
47
+ def declare_options!
48
+ @opt.banner = <<-BANNER
49
+ mdfind4 - a better mdfind built upon mdfind3
50
+
51
+ Spotlight 是 OS X 上非常好用的一个桌面搜索工具,mdfind 是Spotlight 的 CLI frontend,
52
+ 虽然 mdfind 的功能足够强大,但是界面非常不友好,例如,要搜索电脑中2013年的文件大小大于 1MB
53
+ 的jpg图像文件,需要一个非常复杂的命令。mdfind4 对mdfind进行封装,提供比较易用的界面
54
+
55
+ mdfind3: https://gist.github.com/4086771
56
+
57
+ usage: mdfind4 [options] [search dir]
58
+
59
+ 通过指定若干选项来搜索文件系统中被 Spotlight 索引的文件,每个选项之间是逻辑与的关系. 默认将
60
+ 搜索文件系统中所有的被索引文件,可以通过最后一个参数来约束其只在某个目录中进行搜索.
61
+
62
+ % mdfind4 --content-type com.omnigroup.omnigraffle.graffle --from 2011/11/1 -t '2013-01-01 14:30:24'
63
+ % mdfind4 --query '关键字' --content-type 'com.adobe.pdf'
64
+ % mdfind4 -e mp3 -b10M
65
+
66
+ options:
67
+
68
+ 以下选项均支持以一个 "@" 符号开头, 后跟一个文件名,表示此选项的值将从给定的文件中提取:
69
+
70
+ • -f, --from
71
+ • -t, --to
72
+ • -F, --from-create
73
+ • -T, --to-create
74
+ • -c, --content-type
75
+ • -k, --kind
76
+ • -b, --biggerthan
77
+ • -s, --smallerthan
78
+
79
+ BANNER
80
+
81
+ @opt.on('-f from', '--from from', "e.g. --from 2010/1/1
82
+ #{' ' * 35}搜索\"文件最后修改时间\"等于或晚于指定时间的文件,时间格式为\"YYYY-mm-dd HH:MM:SS\"
83
+ #{' ' * 35}其中可以使用任意非数字字符作为分隔符,也可以只指定日期部分。
84
+ #{' ' * 35}另外, -f 选项还支持指定一个数字后跟一个字母的形式来指定时间,
85
+ #{' ' * 35}支持的字符包括: S, M, H, d, m, Y,分别表示: 秒, 分, 时,
86
+ #{' ' * 35}天, 月, 年, 其中一个月等于 30 天, 一年等于 365 天.
87
+ #{' ' * 35}例如, 搜索最近 10 天的文件: -f 10d
88
+ ") do |from|
89
+ @predicates << 'kMDItemFSContentChangeDate >= %i' % (from.start_with?('@') ?
90
+ prop_value_from_file(from, 'kMDItemFSContentChangeDate') : calculate_time(from))
91
+ end
92
+
93
+ @opt.on('-t to', '--to to', "搜索\"文件最后修改时间\"等于或早于指定时间的文件, 格式同\"-f\"选项
94
+ ") do |to|
95
+ @predicates << 'kMDItemFSContentChangeDate <= %i' % (to.start_with?('@') ?
96
+ prop_value_from_file(to, 'kMDItemFSContentChangeDate') : calculate_time(to))
97
+ end
98
+
99
+ @opt.on('-F from', '--from-create from', "搜索\"文件创建时间\"等于或晚于指定时间的文件, 格式同\"-f\"选项
100
+ ") do |from|
101
+ @predicates << 'kMDItemFSCreationDate >= %i' % (from.start_with?('@') ?
102
+ prop_value_from_file(from, 'kMDItemFSCreationDate') : calculate_time(from))
103
+ end
104
+
105
+ @opt.on('-T to', '--to-create to', "搜索\"文件创建时间\"等于或早于指定时间的文件, 格式同\"-f\"选项
106
+ ") do |to|
107
+ @predicates << 'kMDItemFSCreationDate <= %i' % (to.start_with?('@') ?
108
+ prop_value_from_file(to, 'kMDItemFSCreationDate') : calculate_time(to))
109
+ end
110
+
111
+ @opt.on('-q query', '--query query', 'e.g. --query "关键字"
112
+ ') do |query|
113
+ @predicates << 'kMDItemTextContent == "%s"cdw' % escape(query)
114
+ end
115
+
116
+ @opt.on('-c contenttype', '--content-type contenttype', "e.g. --contenttype com.omnigroup.omnigraffle.graffle
117
+ #{' ' * 35}搜索 \"kMDItemContentType\" 属性等于指定值的文件,可以不
118
+ #{' ' * 35}指定完整的 Content Type,例如 Ruby 脚本的 Content Type
119
+ #{' ' * 35}为 \"public.ruby-script\",但是可以使用下面的命令所有所有
120
+ #{' ' * 35}Ruby 脚本文件:mdfind4 -c ruby
121
+ ") do |contenttype|
122
+ @predicates << 'kMDItemContentType == "%s"cdw' % (contenttype.start_with?('@') ?
123
+ prop_value_from_file(contenttype, 'kMDItemContentType') : escape(contenttype))
124
+ end
125
+
126
+ @opt.on('-e file-ext-name', '--type file-ext-name', ".e.g -e 'mp3'
127
+ #{' ' * 35}ContentType 字符串不便记忆,为了方便使用,本程序将常用的
128
+ #{' ' * 35}文件类型的后缀名和 kMDItemKind 字符串建立关联,可以使用本选项
129
+ #{' ' * 35}来指定要搜索的文件的后缀名, 可以使用\"-l\"选项查看所有支持的后
130
+ #{' ' * 35}缀名以及关联
131
+ ") do |type|
132
+ unless Mfd::Asset::KIND_MAP[type]
133
+ STDERR.puts "Not supported file ext name: #{type}, use `mfd -l' to see all supported file ext names`"
134
+ exit(1)
135
+ end
136
+ @predicates << 'kMDItemKind == "*%s*"cdw' % escape(Mfd::Asset::KIND_MAP[type])
137
+ end
138
+
139
+ @opt.on('-l', '--list-types', "kMDItemKind 字符串不便记忆,为了方便使用,本程序将常用的
140
+ #{' ' * 35}文件类型的后缀名和 kMDItemKind 字符串建立关联,使用本选项
141
+ #{' ' * 35}看所有支持的后缀名以及关联
142
+ ") do
143
+ puts
144
+ Mfd::Asset::KIND_MAP.keys.sort.each do |e|
145
+ printf "\t%-10s : %s\n", e, Mfd::Asset::KIND_MAP[e]
146
+ end
147
+ puts
148
+ exit
149
+ end
150
+
151
+ @opt.on('-k kind', '--kind kind', "e.g. --kind \"HTML Document\"
152
+ #{' ' * 35}\"-k\"选项是另外一个用来指定文件类型的选项,它使用
153
+ #{' ' * 35}\"kMDItemKind\"属性来搜索文件. 例如, 搜索邮件: \"-k 邮件信息\"
154
+ #{' ' * 35}搜索 Safari 历史记录: \"-k 'Safari 历史记录项目'\"
155
+ ") do |kind|
156
+ @predicates << 'kMDItemKind == "*%s*"cdw' % (kind.start_with?('@') ?
157
+ prop_value_from_file(kind, 'kMDItemKind') : escape(kind))
158
+ end
159
+
160
+ @opt.on('-b size', '--bigger-than size', "e.g. --bigger-than 100000
161
+ #{' ' * 35}搜索文件大小大于或等于给定值的文件,可以使用 t, g, m, k 等单位,
162
+ #{' ' * 35}例如, 搜索大于或等于 1MB 字节的文件:\"-b 1m\"
163
+ ") do |size|
164
+ @predicates << 'kMDItemFSSize >= %s' % (size.start_with?('@') ?
165
+ prop_value_from_file(size, 'kMDItemFSSize') : calculate_size(size).to_s)
166
+ end
167
+
168
+ @opt.on('-s sizes', '--smaller-than size', "e.g. --smaller-than 100000
169
+ #{' ' * 35}搜索文件大小小于或等于给定值的文件,可以使用 t, g, m, k 等单位,
170
+ #{' ' * 35}例如, 搜索小于或等于 1GB 字节的文件:\"-b 1G\"
171
+ ") do |size|
172
+ @predicates << 'kMDItemFSSize <= %s' % (size.start_with?('@') ?
173
+ prop_value_from_file(size, 'kMDItemFSSize') : calculate_size(size).to_s)
174
+ end
175
+
176
+ @opt.on('', '--prop-eq arg_value', "e.g. --prop-eq FSSize@diary.txt
177
+ #{' ' * 35}不同类型的文件各自都有多种多样的属性,本程序支持的属性比较有限, 若需要
178
+ #{' ' * 35}按照其他属性比较,则使用本选项以及下面三个以 \"--prop\"
179
+ #{' ' * 35}打头的选项。可以通过 OS X 自带的 \"mdls\" 命令来查看文件的各种属性,
180
+ #{' ' * 35}属性名称一般以 \"kMDItem\" 打头,使用这四个选项时可以只指定 \"kMDItem\"
181
+ #{' ' * 35}后面的部分, 本选项的值的格式为 \"property@filename\", 例如,
182
+ #{' ' * 35}搜索文件大小等于 a.txt 的所有文件:--prop-eq FSSize@./a.txt
183
+ ") do |arg_value|
184
+ prop_key, filename = arg_value.split('@')
185
+ prop_key = 'kMDItem' + prop_key unless prop_key.start_with?('kMDItem')
186
+ filename = File.expand_path filename
187
+ @predicates << "#{prop_key} == %s" % prop_value_from_file(filename, prop_key)
188
+ end
189
+
190
+ @opt.on('', '--prop-ne arg_value', 'e.g. --prop-ne FSSize@diary.txt
191
+ ') do |arg_value|
192
+ prop_key, filename = arg_value.split('@')
193
+ prop_key = 'kMDItem' + prop_key unless prop_key.start_with?('kMDItem')
194
+ filename = File.expand_path filename
195
+ @predicates << "#{prop_key} != %s" % prop_value_from_file(filename, prop_key)
196
+ end
197
+
198
+ @opt.on('', '--prop-le arg_value', 'e.g. --prop-le FSSize@diary.txt
199
+ ') do |arg_value|
200
+ prop_key, filename = arg_value.split('@')
201
+ prop_key = 'kMDItem' + prop_key unless prop_key.start_with?('kMDItem')
202
+ filename = File.expand_path filename
203
+ @predicates << "#{prop_key} <= %s" % prop_value_from_file(filename, prop_key)
204
+ end
205
+
206
+ @opt.on('', '--prop-ge arg_value', 'e.g. --prop-ge FSSize@diary.txt
207
+ ') do |arg_value|
208
+ prop_key, filename = arg_value.split('@')
209
+ prop_key = 'kMDItem' + prop_key unless prop_key.start_with?('kMDItem')
210
+ filename = File.expand_path filename
211
+ @predicates << "#{prop_key} >= %s" % prop_value_from_file(filename, prop_key)
212
+ end
213
+
214
+ @opt.on('-d url', '--downloadfrom url', '搜索从URL下载的文件') do |url|
215
+ @predicates << 'kMDItemWhereFroms == "%s"' % "*#{escape(url)}*" # 曖昧指定
216
+ end
217
+
218
+ @opt.on('-n name', '--name name', '按文件名称搜索, 自动支持模糊匹配, 无需指定通配符') do |name|
219
+ @predicates << 'kMDItemFSName == "%s"cdw' % escape(name)
220
+ end
221
+
222
+ @opt.on('-0', '--null', "Prints an ASCII NUL character after each result path.
223
+ #{' ' * 35}This is useful when used in conjunction with
224
+ #{' ' * 35}xargs -0.") do
225
+ @ascii_null = true
226
+ end
227
+
228
+ @opt.on('', '--live', "Causes the mdfind command to provide live-updates to the
229
+ #{' ' * 35}number of files matching the query. When an
230
+ #{' ' * 35}update causes the query results to change the
231
+ #{' ' * 35} number of matches is updated. The find can
232
+ #{' ' * 35}be cancelled by typing ctrl-C.
233
+ ") do
234
+ @live = true
235
+ end
236
+
237
+ @opt.on('', '--count', "Causes the mdfind command to output the total number of
238
+ #{' ' * 35}matches, instead of the path to the matching
239
+ #{' ' * 35}items.
240
+ ") do
241
+ @count = true
242
+ end
243
+
244
+ @opt.on('', '--debug', "mdfind4 通过调用 OS X 系统自带的 mdfind 命令来完成文件搜索,指定
245
+ #{' ' * 35}本选项之后, 可以看到 mdfind4 实际调用的命令的参数
246
+ ") do
247
+ @print_command = true
248
+ end
249
+ end
250
+
251
+ def escape(str)
252
+ str.gsub(/"/, '\\"')
253
+ end
254
+
255
+ def calculate_size(size_str)
256
+ case size_str
257
+ when /(\d+)t$/i
258
+ Regexp.last_match(1).to_i * 1024**4
259
+ when /(\d+)g$/i
260
+ Regexp.last_match(1).to_i * 1024**3
261
+ when /(\d+)m$/i
262
+ Regexp.last_match(1).to_i * 1024**2
263
+ when /(\d+)k$/i
264
+ Regexp.last_match(1).to_i * 1024
265
+ else
266
+ Regexp.last_match(1).to_i
267
+ end
268
+ end
269
+
270
+ def calculate_time(time_str)
271
+ datum = Time.gm 2001
272
+
273
+ case time_str
274
+ when /^(\d+)S$/
275
+ NOW - datum - Regexp.last_match(1).to_i
276
+ when /^(\d+)M$/
277
+ NOW - datum - Regexp.last_match(1).to_i * 60
278
+ when /^(\d+)H$/
279
+ NOW - datum - Regexp.last_match(1).to_i * 3600
280
+ when /^(\d+)d$/
281
+ NOW - datum - Regexp.last_match(1).to_i * 86_400
282
+ when /^(\d+)m$/
283
+ NOW - datum - Regexp.last_match(1).to_i * 2_592_000
284
+ when /^(\d+)Y$/
285
+ NOW - datum - Regexp.last_match(1).to_i * 946_080_000
286
+ else
287
+ ymd = time_str.split(/\D+/)
288
+ Time.local(*ymd) - datum
289
+ end
290
+ end
291
+
292
+ def prop_value_from_file(filename, key)
293
+ filename = File.expand_path filename.sub(/^@/) { '' }
294
+ IO.popen("mdls '#{filename}'", 'r') do |io|
295
+ while line = io.gets
296
+ next unless line =~ /#{key}\s*=\s*(.+)/
297
+ value_str = Regexp.last_match(1)
298
+ case value_str
299
+ when /^"([^"]*)"$/
300
+ return Regexp.last_match(1)
301
+ when /^(\d+)$/
302
+ return Regexp.last_match(1)
303
+ when /^(.*) \+0000$/
304
+ ymd = Regexp.last_match(1).split(/\D+/)
305
+ return Time.gm(*ymd) - Time.gm(2001)
306
+ else
307
+ next
308
+ end
309
+ end
310
+ return nil
311
+ end
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,3 @@
1
+ module Mfd
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,31 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "mfd/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mfd"
8
+ spec.version = Mfd::VERSION
9
+ spec.authors = ["Liu Xiang"]
10
+ spec.email = ["liuxiang921@gmail.com"]
11
+
12
+ spec.summary = %q{}
13
+ spec.description = %q{}
14
+ spec.homepage = "https://github.com/lululau/mfd"
15
+ spec.license = "MIT"
16
+
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.17"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "rspec", "~> 3.0"
30
+ spec.add_development_dependency "pry-byebug", ">= 3.6.0"
31
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mfd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Liu Xiang
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-04-29 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.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
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: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 3.6.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 3.6.0
69
+ description: ''
70
+ email:
71
+ - liuxiang921@gmail.com
72
+ executables:
73
+ - mfd
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - CODE_OF_CONDUCT.md
81
+ - Gemfile
82
+ - Gemfile.lock
83
+ - LICENSE.txt
84
+ - README.md
85
+ - Rakefile
86
+ - bin/console
87
+ - bin/setup
88
+ - exe/mfd
89
+ - lib/mfd.rb
90
+ - lib/mfd/asset.rb
91
+ - lib/mfd/cli.rb
92
+ - lib/mfd/version.rb
93
+ - mfd.gemspec
94
+ homepage: https://github.com/lululau/mfd
95
+ licenses:
96
+ - MIT
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubygems_version: 3.0.3
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: ''
117
+ test_files: []