mfd 0.1.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.
@@ -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: []