jtag 0.1.19 → 0.1.21
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 +4 -4
- data/.irbrc +6 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +3 -0
- data/Jekyll/plugins/autotag_gen.rb +58 -0
- data/Jekyll/source/_layouts/tags_json.html +11 -0
- data/README.md +159 -0
- data/Rakefile +113 -0
- data/bin/jtag +374 -252
- data/jtag.completion.bash +9 -0
- data/jtag.gemspec +25 -0
- data/lib/jtag/array.rb +48 -0
- data/lib/jtag/config_files/config.yml +3 -2
- data/lib/jtag/errors.rb +31 -0
- data/lib/jtag/hash.rb +103 -0
- data/lib/jtag/jekylltag.rb +267 -225
- data/lib/jtag/string.rb +238 -42
- data/lib/jtag/stupid_json.rb +319 -0
- data/lib/jtag/util.rb +237 -0
- data/lib/jtag/version.rb +1 -1
- data/lib/jtag.rb +19 -13
- data/mise.toml +2 -0
- metadata +34 -9
data/bin/jtag
CHANGED
@@ -10,106 +10,85 @@ program_desc "Autotagging for Jekyll"
|
|
10
10
|
|
11
11
|
version Jtag::VERSION
|
12
12
|
|
13
|
-
@config_files = %w{blacklist.txt config.yml stopwords.txt synonyms.yml}
|
14
|
-
@config_target = File.expand_path("~/.jtag")
|
15
13
|
@piped_content = nil
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
##
|
16
|
+
## Define global flags and switches
|
17
|
+
##
|
18
|
+
desc "Expect input from STDIN to be null (0)-delimited: Useful for piping file lists from `find` or `grep`"
|
19
|
+
default_value false
|
20
|
+
switch %i[0 null], negatable: false
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
return false unless File.exist?(line)
|
28
|
-
end
|
29
|
-
true
|
30
|
-
end
|
31
|
-
end
|
22
|
+
desc "Path to alternate configuration directory"
|
23
|
+
arg_name "config_dir"
|
24
|
+
flag %i[c config_dir], :type => String
|
32
25
|
|
33
|
-
desc "Debug level"
|
34
|
-
default_value "0"
|
26
|
+
desc "Debug level: Set the debug level for the application. 0 is silent, 1 is errors only, 2 is errors and warnings, 3 is errors, warnings and info"
|
35
27
|
arg_name "debug_level"
|
36
|
-
flag %i[d debug], :must_match => /\d+/, :type => Integer, :default_value =>
|
28
|
+
flag %i[d debug], :must_match => /\d+/, :type => Integer, :default_value => 3
|
37
29
|
|
38
|
-
desc "
|
39
|
-
switch %i[
|
30
|
+
desc "Perform case-insensitive matches and searches: Useful for case-insensitive filesystems or when you're not sure of the case of your tags"
|
31
|
+
switch %i[i case_insensitive], negatable: false
|
40
32
|
|
41
|
-
desc "
|
42
|
-
switch %i[
|
33
|
+
desc "Run silently: Suppress all output to STDOUT, same as debug level 0"
|
34
|
+
switch %i[s silent], negatable: false
|
43
35
|
|
44
|
-
desc "Test (dry run, don't update files)"
|
45
|
-
long_desc "Run all commands and show results on the command line, but don't overwrite/update any files"
|
36
|
+
desc "Test (dry run, don't update files): Run all commands and show results on the command line, but don't overwrite/update any files"
|
46
37
|
default_value false
|
47
|
-
switch %i[t test]
|
48
|
-
|
49
|
-
def console_log(msg = "", options = {})
|
50
|
-
err = options[:err] || false
|
51
|
-
options[:log] ||= false
|
52
|
-
|
53
|
-
if options[:log]
|
54
|
-
if err
|
55
|
-
@log.warn(msg)
|
56
|
-
else
|
57
|
-
@log.info(msg)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
return if @silent
|
62
|
-
|
63
|
-
unless err
|
64
|
-
$stdout.puts msg
|
65
|
-
else
|
66
|
-
warn msg
|
67
|
-
end
|
68
|
-
end
|
38
|
+
switch %i[t test], negatable: false
|
69
39
|
|
40
|
+
# Add a command to update and notify the user of the configuration files location
|
70
41
|
desc "Update and notify user of configuration files location"
|
71
42
|
command :config do |c|
|
43
|
+
# Add a switch to reset all configuration files to default values
|
72
44
|
c.desc "Reset all configuration files to default values"
|
73
|
-
c.switch [
|
45
|
+
c.switch %i[r reset]
|
46
|
+
|
47
|
+
c.desc "Ignore prompts and answer yes to all questions."
|
48
|
+
c.switch %i[y yes], negatable: false
|
49
|
+
|
50
|
+
c.desc "Set a specific configuration value"
|
51
|
+
c.arg_name "key"
|
52
|
+
c.flag %i[s set]
|
53
|
+
|
54
|
+
c.desc "The value to set for key"
|
55
|
+
c.arg_name "value"
|
56
|
+
c.flag %i[v value]
|
74
57
|
|
58
|
+
# Skip the pre block for this command
|
75
59
|
c.skips_pre
|
60
|
+
|
61
|
+
# Define the action to be taken when the command is executed
|
76
62
|
c.action do |global_options, options, args|
|
77
|
-
if options[:
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
console_log "Config files reset"
|
63
|
+
if options[:set]
|
64
|
+
config = YAML.load_file(File.join(@util.config_target, "config.yml"))
|
65
|
+
config[options[:set]] = options[:value]
|
66
|
+
File.open(File.join(@util.config_target, "config.yml"), "w") do |f|
|
67
|
+
f.write(config.to_yaml)
|
83
68
|
end
|
84
|
-
|
85
|
-
write_config(false)
|
69
|
+
return
|
86
70
|
end
|
87
|
-
end
|
88
|
-
end
|
89
71
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
console_log "Configuration files written to #{@config_target}"
|
100
|
-
end
|
72
|
+
if options[:reset]
|
73
|
+
# Prompt the user for confirmation before resetting config files
|
74
|
+
if options[:yes]
|
75
|
+
res = true
|
76
|
+
else
|
77
|
+
print "Are you sure you want to reset all config files? y/N: "
|
78
|
+
response = STDIN.gets.strip
|
79
|
+
res = response =~ /^y/i
|
80
|
+
end
|
101
81
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
82
|
+
if res
|
83
|
+
# Reset config files to default values
|
84
|
+
@util.write_config(true)
|
85
|
+
@util.console_log "Config files reset"
|
86
|
+
end
|
87
|
+
else
|
88
|
+
# Update config files without overwriting existing files
|
89
|
+
@util.write_config(false)
|
108
90
|
end
|
109
91
|
end
|
110
|
-
console_log
|
111
|
-
console_log "Configuration files are located in the folder: " + @config_target
|
112
|
-
console_log %Q{Make sure that "tags_location" in the config.yml file is set to your tags json file.}
|
113
92
|
end
|
114
93
|
|
115
94
|
desc "List tags, optionally filter for keywords/regular expressions (OR)"
|
@@ -126,32 +105,125 @@ command :search do |c|
|
|
126
105
|
c.default_value false
|
127
106
|
c.switch %i[c counts]
|
128
107
|
|
108
|
+
c.desc "Boolean operator for multiple tags (AND/OR/NOT)"
|
109
|
+
c.arg_name "bool"
|
110
|
+
c.default_value "OR"
|
111
|
+
c.flag %i[b bool], :must_match => /^([aon])(?:.*)$/i, :type => String
|
112
|
+
|
113
|
+
c.desc "Match type: exact, fuzzy, starts_with, contains"
|
114
|
+
c.arg_name "match_type"
|
115
|
+
c.default_value "contains"
|
116
|
+
c.flag %i[m match], :must_match => /^([exfsc])(?:.*)$/i, :type => String
|
117
|
+
|
118
|
+
c.desc "Case-sensitive matching"
|
119
|
+
c.switch %i[I s case_sensitive], negatable: true
|
120
|
+
|
121
|
+
c.desc "Fuzzy match distance"
|
122
|
+
c.arg_name "distance"
|
123
|
+
c.default_value 2
|
124
|
+
c.flag %i[d distance], :must_match => /^\d+$/, :type => Integer
|
125
|
+
|
129
126
|
c.action do |global_options, options, args|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
127
|
+
# Initialize arrays to store files and keywords
|
128
|
+
files = []
|
129
|
+
keywords = []
|
130
|
+
bool_op = options[:bool].to_bool
|
131
|
+
|
132
|
+
# Iterate over the arguments and separate files from keywords
|
133
|
+
args.length.times do
|
134
|
+
arg = args.pop
|
135
|
+
if File.file?(File.expand_path(arg))
|
136
|
+
files.push(File.expand_path(arg)) # Add existing files to the files array
|
137
|
+
else
|
138
|
+
keywords.push(arg) # Add non-file arguments to the keywords array
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# If piped content is provided and it is a list of files, add them to the files array
|
143
|
+
files.concat(@piped_content.split(global_options[:null] ? "\x00" : "\n")) if @piped_content && @piped_content.file_list?(global_options[:null])
|
144
|
+
|
145
|
+
# If no files are provided, use the default post location
|
146
|
+
if files.empty?
|
147
|
+
if File.exist?(File.expand_path(@jt.tags_loc))
|
148
|
+
files = nil
|
149
|
+
else
|
150
|
+
if @jt.default_post_location && File.exist?(File.expand_path(@jt.default_post_location))
|
151
|
+
# Retrieves a list of files from the default post location with the specified post extension.
|
152
|
+
# The files are located using a glob pattern that matches all files with the given extension.
|
153
|
+
#
|
154
|
+
# @return [Array<String>] an array of file paths matching the specified pattern
|
155
|
+
files = Dir.glob(File.join(File.expand_path(@jt.default_post_location), "**", "*.#{@jt.post_extension}"))
|
138
156
|
end
|
139
|
-
|
140
|
-
|
141
|
-
|
157
|
+
end
|
158
|
+
else
|
159
|
+
tags = {}
|
160
|
+
end
|
161
|
+
|
162
|
+
# Get tags with counts for the specified files
|
163
|
+
if @piped_content && !@piped_content.file_list?(global_options[:null])
|
164
|
+
if @piped_content.yaml?
|
165
|
+
tags = YAML.load(@piped_content).to_counts
|
166
|
+
elsif @piped_content.json?
|
167
|
+
tags = JSON.parse(@piped_content).to_counts
|
168
|
+
elsif @piped_content.tag_list?
|
169
|
+
tags = @piped_content.split("\n").map(&:strip).to_counts
|
142
170
|
else
|
143
|
-
tags.
|
171
|
+
tags = @jt.tags({ :counts => true, :files => files })
|
144
172
|
end
|
145
|
-
output_tags(tags, { :format => options[:format] })
|
146
173
|
else
|
147
|
-
tags.
|
148
|
-
|
149
|
-
|
174
|
+
tags = @jt.tags({ :counts => true, :files => files })
|
175
|
+
end
|
176
|
+
|
177
|
+
exit_now! "No source tag list found" if !tags || tags.empty?
|
178
|
+
|
179
|
+
match_options = {
|
180
|
+
exact: false,
|
181
|
+
contains: false,
|
182
|
+
starts_with: false,
|
183
|
+
fuzzy: false,
|
184
|
+
case_sensitive: options[:case_sensitive],
|
185
|
+
distance: options[:distance],
|
186
|
+
}
|
187
|
+
|
188
|
+
if options[:match] =~ /^[ex]/i
|
189
|
+
match_options[:exact] = true
|
190
|
+
elsif options[:match] =~ /^s/i
|
191
|
+
match_options[:starts_with] = true
|
192
|
+
elsif options[:match] =~ /^f/i
|
193
|
+
match_options[:fuzzy] = true
|
194
|
+
else
|
195
|
+
match_options[:contains] = true
|
196
|
+
end
|
197
|
+
|
198
|
+
# If keywords are provided, filter tags based on the keywords
|
199
|
+
if keywords.length > 0
|
200
|
+
if bool_op == :and
|
201
|
+
tags.delete_if { |tag| tag && !keywords.all? { |keyword| tag["name"].match_keyword(keyword, match_options) } }
|
202
|
+
elsif bool_op == :not
|
203
|
+
re = keywords.join("|") # Join keywords with OR operator
|
204
|
+
# Remove tags that match the keywords
|
205
|
+
tags.delete_if { |tag| tag && keywords.any? { |keyword| tag["name"].match_keyword(keyword, match_options) } }
|
150
206
|
else
|
151
|
-
|
207
|
+
# :or
|
208
|
+
# Filter tags based on any keywords
|
209
|
+
tags.delete_if { |tag| tag && !keywords.any? { |keyword| tag["name"].match_keyword(keyword, match_options) } }
|
152
210
|
end
|
153
|
-
output_tags(tags, { :format => options[:format] })
|
154
211
|
end
|
212
|
+
|
213
|
+
# Remove nil and false values from the tags array
|
214
|
+
tags.delete_if { |tag| !tag }
|
215
|
+
|
216
|
+
exit_now! "No matching tags found" if tags.nil? || tags.empty?
|
217
|
+
|
218
|
+
# Format tags with counts if the counts option is enabled
|
219
|
+
if options[:count]
|
220
|
+
tags.map! { |tag| "#{tag["name"]} (#{tag["count"]})" }
|
221
|
+
else
|
222
|
+
tags.map! { |tag| tag["name"] }
|
223
|
+
end
|
224
|
+
|
225
|
+
# Output the formatted tags
|
226
|
+
@util.output_tags(tags, { :format => options[:format] })
|
155
227
|
end
|
156
228
|
end
|
157
229
|
|
@@ -172,61 +244,73 @@ command :posts_tagged do |c|
|
|
172
244
|
c.switch [:print0]
|
173
245
|
|
174
246
|
c.action do |global_options, options, args|
|
247
|
+
# Convert the boolean operator to uppercase for consistency
|
175
248
|
bool = options[:bool].upcase
|
249
|
+
|
250
|
+
# Initialize arrays to store files, tags, and matches
|
176
251
|
files = []
|
177
252
|
tags = []
|
178
253
|
matches = []
|
254
|
+
|
255
|
+
# Separate files and tags from the arguments
|
179
256
|
args.length.times do
|
180
257
|
arg = args.pop
|
181
258
|
if File.exist?(arg)
|
182
|
-
files.push(arg)
|
259
|
+
files.push(arg) # Add existing files to the files array
|
183
260
|
else
|
184
|
-
tags.push(arg)
|
261
|
+
tags.push(arg) # Add non-file arguments to the tags array
|
185
262
|
end
|
186
263
|
end
|
187
264
|
|
188
|
-
files
|
265
|
+
# If piped content is provided and it is a list of files, add them to the files array
|
266
|
+
files.concat(@piped_content.split(global_options[:null] ? "\x00" : "\n")) if @piped_content && @piped_content.file_list?(global_options[:null])
|
189
267
|
|
268
|
+
# If no files are provided, use the default post location
|
190
269
|
if files.empty?
|
191
|
-
if @jt.default_post_location && File.exist?(
|
192
|
-
files
|
270
|
+
if @jt.default_post_location && File.exist?(@jt.default_post_location)
|
271
|
+
# Retrieves a list of files from the default post location with the specified post extension.
|
272
|
+
# The files are located using a glob pattern that matches all files with the given extension.
|
273
|
+
files = Dir.glob(File.join(@jt.default_post_location, "*.#{@jt.post_extension}"))
|
193
274
|
end
|
194
275
|
end
|
195
|
-
|
196
|
-
|
276
|
+
|
277
|
+
# Exit if no valid filenames are found in the arguments
|
278
|
+
raise NoValidFile if files.empty?
|
279
|
+
|
280
|
+
# Iterate over each file to find matches based on the boolean operator
|
281
|
+
files.each do |file|
|
197
282
|
if File.exist?(file)
|
198
|
-
post_tags = @jt.post_tags(file)
|
283
|
+
post_tags = @jt.post_tags(file) # Get tags for the current file
|
199
284
|
|
200
285
|
if bool == "AND"
|
286
|
+
# Check if all tags are present in the post tags
|
201
287
|
matched = 0
|
202
|
-
tags.each { |tag|
|
203
|
-
matched += 1 if post_tags.include?(tag)
|
204
|
-
}
|
288
|
+
tags.each { |tag| matched += 1 if post_tags.include?(tag) }
|
205
289
|
matches.push(file) if matched == tags.length
|
206
290
|
elsif bool == "NOT"
|
291
|
+
# Check if none of the tags are present in the post tags
|
207
292
|
matched = false
|
208
|
-
tags.each { |tag|
|
209
|
-
matched = true if post_tags.include?(tag)
|
210
|
-
}
|
293
|
+
tags.each { |tag| matched = true if post_tags.include?(tag) }
|
211
294
|
matches.push(file) unless matched
|
212
295
|
else
|
213
|
-
tags
|
214
|
-
|
215
|
-
matches.push(file) unless matches.include?(file)
|
216
|
-
end
|
217
|
-
}
|
296
|
+
# Default to OR: Check if any of the tags are present in the post tags
|
297
|
+
tags.each { |tag| matches.push(file) unless matches.include?(file) if post_tags.include?(tag) }
|
218
298
|
end
|
219
299
|
else
|
220
|
-
|
300
|
+
# Exit if the file is not found
|
301
|
+
raise FileNotFound, "File not found: #{file}"
|
221
302
|
end
|
222
|
-
|
303
|
+
end
|
223
304
|
|
305
|
+
# Create a search string from the tags and boolean operator
|
224
306
|
search_string = tags.join(" #{bool} ")
|
307
|
+
|
308
|
+
# Output the results
|
225
309
|
if matches.empty?
|
226
|
-
console_log "No matching files found for #{search_string}"
|
310
|
+
@util.console_log "No matching files found for #{search_string}"
|
227
311
|
else
|
228
|
-
console_log "(#{search_string})", { :
|
229
|
-
output_tags(matches, { :
|
312
|
+
@util.console_log "(#{search_string})", { err: true }
|
313
|
+
@util.output_tags(matches, { format: options[:format], print0: options[:print0], grouping: "files" })
|
230
314
|
end
|
231
315
|
end
|
232
316
|
end
|
@@ -255,15 +339,48 @@ command :loners do |c|
|
|
255
339
|
c.flag %i[e edit], :default_value => false, :type => String
|
256
340
|
|
257
341
|
c.action do |global_options, options, args|
|
342
|
+
# Convert the max option to an integer
|
258
343
|
max = options[:m].to_i
|
259
|
-
|
344
|
+
|
345
|
+
# Initialize arrays to store files and keywords
|
346
|
+
files = []
|
347
|
+
keywords = []
|
348
|
+
|
349
|
+
# Separate files and keywords from the arguments
|
350
|
+
args.length.times do
|
351
|
+
arg = args.pop
|
352
|
+
if File.exist?(arg)
|
353
|
+
files.push(arg) # Add existing files to the files array
|
354
|
+
else
|
355
|
+
keywords.push(arg) # Add non-file arguments to the keywords array
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
# If piped content is provided and it is a list of files, add them to the files array
|
360
|
+
files.concat(@piped_content.split(global_options[:null] ? "\x00" : "\n")) if @piped_content && @piped_content.file_list?(global_options[:null])
|
361
|
+
|
362
|
+
# If no files are provided, use the default post location
|
363
|
+
if files.empty?
|
364
|
+
if @jt.default_post_location && File.exist?(@jt.default_post_location)
|
365
|
+
files = Dir.glob(File.join(@jt.default_post_location, "*.#{@jt.post_extension}"))
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# Get tags with counts for the specified files
|
370
|
+
loner_tags = @jt.tags({ :counts => true, :files => files })
|
371
|
+
|
372
|
+
# Remove tags that are not loners (i.e., tags with counts greater than max)
|
260
373
|
loner_tags.delete_if { |tag|
|
261
374
|
tag.class == FalseClass || tag["count"] > max
|
262
375
|
}
|
376
|
+
|
377
|
+
# Sort loner tags by count
|
263
378
|
loner_tags.sort_by! { |tag| tag["count"] }
|
264
379
|
|
380
|
+
# Exit if no tags matched the criteria
|
265
381
|
exit_now! "No tags matched the criteria" if loner_tags.empty? || loner_tags.nil?
|
266
382
|
|
383
|
+
# If the edit option is provided, write the loner tags to a file for editing
|
267
384
|
if options[:e]
|
268
385
|
path = File.expand_path(options[:e])
|
269
386
|
while File.exist?(path)
|
@@ -278,7 +395,7 @@ command :loners do |c|
|
|
278
395
|
File.open(path, "w+") do |f|
|
279
396
|
f.puts "# Edit this file to remove tags you want to keep,"
|
280
397
|
f.puts "# then run `jtag remove -p '#{path}' [/path/to/posts/*.md]`"
|
281
|
-
f.puts "# to remove any tags left in
|
398
|
+
f.puts "# to remove any tags left in this file."
|
282
399
|
f.puts "#"
|
283
400
|
f.puts "# The post counts are included for your convenience, and will"
|
284
401
|
f.puts "# be automatically ignored when reading the list back in."
|
@@ -289,15 +406,17 @@ command :loners do |c|
|
|
289
406
|
}
|
290
407
|
end
|
291
408
|
|
292
|
-
console_log "A list of results and instructions for use have been written to #{path}."
|
409
|
+
@util.console_log "A list of results and instructions for use have been written to #{path}."
|
293
410
|
if ENV["EDITOR"]
|
294
|
-
console_log
|
411
|
+
@util.console_log
|
295
412
|
print "Would you like to open the file in #{ENV["EDITOR"]} now? (y/N) "
|
296
413
|
input = STDIN.gets
|
297
414
|
if input =~ /^y/i
|
298
415
|
system "#{ENV["EDITOR"]} '#{path}'"
|
299
416
|
end
|
300
417
|
end
|
418
|
+
|
419
|
+
# If the remove option is provided, remove the loner tags from the specified files
|
301
420
|
elsif options[:r]
|
302
421
|
files = []
|
303
422
|
args.length.times do
|
@@ -305,14 +424,15 @@ command :loners do |c|
|
|
305
424
|
files.push(arg) if File.exist?(arg)
|
306
425
|
end
|
307
426
|
|
308
|
-
files.concat(@piped_content.split("\n")) if @piped_content
|
427
|
+
files.concat(@piped_content.split(global_options[:null] ? "\x00" : "\n")) if @piped_content && @piped_content.file_list?(global_options[:null])
|
309
428
|
|
310
429
|
if files.empty?
|
311
|
-
if @jt.default_post_location && File.exist?(
|
312
|
-
files = Dir.glob(@jt.default_post_location)
|
430
|
+
if @jt.default_post_location && File.exist?(@jt.default_post_location)
|
431
|
+
files = Dir.glob(File.join(@jt.default_post_location, "*.#{@jt.post_extension}"))
|
313
432
|
end
|
314
433
|
end
|
315
434
|
exit_now! "No valid filename in arguments" if files.empty?
|
435
|
+
|
316
436
|
files.each { |file|
|
317
437
|
tags = @jt.post_tags(file)
|
318
438
|
loner_tags.each { |d|
|
@@ -326,15 +446,17 @@ command :loners do |c|
|
|
326
446
|
}
|
327
447
|
unless global_options[:t]
|
328
448
|
@jt.update_file_tags(file, tags)
|
329
|
-
console_log "Updated tags for #{file}", :log => true
|
449
|
+
@util.console_log "Updated tags for #{file}", :log => true, level: :warn
|
330
450
|
end
|
331
451
|
|
332
|
-
console_log
|
333
|
-
console_log File.basename(file) + ":"
|
334
|
-
output_tags(tags, :format => options[:format], :filename => file)
|
452
|
+
@util.console_log level: :info
|
453
|
+
@util.console_log File.basename(file) + ":", level: :info
|
454
|
+
@util.output_tags(tags, :format => options[:format], :filename => file)
|
335
455
|
}
|
456
|
+
|
457
|
+
# Otherwise, output the loner tags
|
336
458
|
else
|
337
|
-
output_tags(loner_tags.map { |tag|
|
459
|
+
@util.output_tags(loner_tags.map { |tag|
|
338
460
|
count = options[:no_counts] ? "" : " (#{tag["count"]})"
|
339
461
|
"#{tag["name"]}#{count}"
|
340
462
|
}, :format => options[:format])
|
@@ -359,24 +481,24 @@ command :tags do |c|
|
|
359
481
|
end
|
360
482
|
end
|
361
483
|
|
362
|
-
files.concat(@piped_content.split("\n")) if @piped_content && @piped_content.file_list?
|
484
|
+
files.concat(@piped_content.split(global_options[:null] ? "\x00" : "\n")) if @piped_content && @piped_content.file_list?(global_options[:null])
|
363
485
|
|
364
486
|
tags = []
|
365
487
|
files.each do |file|
|
366
488
|
tags.concat(@jt.post_tags(file)) if File.exist?(file)
|
367
489
|
|
368
490
|
if args.length > 0
|
369
|
-
console_log
|
370
|
-
console_log "STDIN:"
|
491
|
+
@util.console_log level: :info
|
492
|
+
@util.console_log "STDIN:", level: :info
|
371
493
|
end
|
372
494
|
end
|
373
495
|
|
374
496
|
if tags.empty? || tags.nil?
|
375
|
-
console_log "No tags in post",
|
497
|
+
@util.console_log "No tags in post", err: true, level: :info
|
376
498
|
else
|
377
499
|
tags.sort!
|
378
500
|
tags.uniq!
|
379
|
-
output_tags(tags, { :format => options[:format] })
|
501
|
+
@util.output_tags(tags, { :format => options[:format] })
|
380
502
|
end
|
381
503
|
end
|
382
504
|
end
|
@@ -398,7 +520,7 @@ command :sort do |c|
|
|
398
520
|
end
|
399
521
|
end
|
400
522
|
|
401
|
-
files.concat(@piped_content.split("\n")) if @piped_content
|
523
|
+
files.concat(@piped_content.split(global_options[:null] ? "\x00" : "\n")) if @piped_content && @piped_content.file_list?(global_options[:null])
|
402
524
|
|
403
525
|
files.each do |file|
|
404
526
|
tags = @jt.post_tags(file)
|
@@ -407,13 +529,13 @@ command :sort do |c|
|
|
407
529
|
unless global_options[:t]
|
408
530
|
@jt.update_file_tags(file, tags)
|
409
531
|
end
|
410
|
-
console_log
|
411
|
-
console_log File.basename(file)
|
532
|
+
@util.console_log level: :info
|
533
|
+
@util.console_log "#{File.basename(file)}:", level: :info
|
412
534
|
|
413
535
|
if tags.empty? || tags.nil?
|
414
|
-
console_log "No tags in post",
|
536
|
+
@util.console_log "No tags in post", err: true, level: :info
|
415
537
|
else
|
416
|
-
output_tags(tags, { :format => options[:format], :filename => file })
|
538
|
+
@util.output_tags(tags, { :format => options[:format], :filename => file })
|
417
539
|
end
|
418
540
|
end
|
419
541
|
end
|
@@ -440,31 +562,31 @@ command :merge do |c|
|
|
440
562
|
end
|
441
563
|
end
|
442
564
|
|
443
|
-
files.concat(@piped_content.split("\n")) if @piped_content
|
565
|
+
files.concat(@piped_content.split(global_options[:null] ? "\x00" : "\n")) if @piped_content && @piped_content.file_list?(global_options[:null])
|
444
566
|
|
445
567
|
if files.empty?
|
446
|
-
if @jt.default_post_location && File.exist?(
|
447
|
-
files = Dir.glob(@jt.default_post_location)
|
568
|
+
if @jt.default_post_location && File.exist?(@jt.default_post_location)
|
569
|
+
files = Dir.glob(File.join(@jt.default_post_location, "*.#{@jt.post_extension}"))
|
448
570
|
end
|
449
571
|
end
|
450
572
|
exit_now! "No valid filename in arguments" if files.empty?
|
451
573
|
exit_now! "Needs at least two tag inputs, one or more to merge, one to merge to" if tags.length < 2
|
452
574
|
tags.reverse!
|
453
575
|
merge_tag = tags.pop
|
454
|
-
console_log %
|
455
|
-
files.each
|
576
|
+
@util.console_log %(Merging #{tags.join(", ")} to #{merge_tag}), level: :info
|
577
|
+
files.each do |file|
|
456
578
|
new_tags = @jt.merge_tags(tags, merge_tag, file)
|
457
579
|
next unless new_tags
|
458
|
-
unless global_options[:
|
580
|
+
unless global_options[:test]
|
459
581
|
@jt.update_file_tags(file, new_tags)
|
460
|
-
console_log
|
461
|
-
console_log "Updated tags for #{file}", :
|
582
|
+
@util.console_log level: :info
|
583
|
+
@util.console_log "Updated tags for #{file}", log: true, level: :warn
|
462
584
|
end
|
463
585
|
|
464
|
-
console_log
|
465
|
-
console_log File.basename(file)
|
466
|
-
output_tags(new_tags, { :
|
467
|
-
|
586
|
+
@util.console_log level: :info
|
587
|
+
@util.console_log "#{File.basename(file)}:", level: :info
|
588
|
+
@util.output_tags(new_tags, { format: options[:format], filename: file })
|
589
|
+
end
|
468
590
|
end
|
469
591
|
end
|
470
592
|
|
@@ -477,22 +599,22 @@ command :blacklist do |c|
|
|
477
599
|
c.action do |global_options, options, args|
|
478
600
|
if options[:r]
|
479
601
|
@jt.unblacklist(args)
|
480
|
-
console_log "Removed #{args.join(", ")} from blacklist."
|
602
|
+
@util.console_log "Removed #{args.join(", ")} from blacklist.", level: :info, err: true
|
481
603
|
else
|
482
604
|
@jt.blacklist(args)
|
483
|
-
console_log "Blacklisted #{args.join(", ")}."
|
605
|
+
@util.console_log "Blacklisted #{args.join(", ")}.", level: :info, err: true
|
484
606
|
end
|
485
607
|
end
|
486
608
|
end
|
487
609
|
|
488
610
|
desc "Add tags to post(s)"
|
489
|
-
arg_name "tags", :multiple
|
490
|
-
arg_name "file_pattern"
|
611
|
+
arg_name "tags... file(s)", :multiple
|
491
612
|
command :add do |c|
|
492
|
-
c.desc "Format to use when outputting tags to console: list, json, plist, csv or
|
613
|
+
c.desc "Format to use when outputting tags to console: list, json, plist, csv, yaml, or complete. Defaults to yaml."
|
614
|
+
c.long_desc "Use 'complete' to output full text when input is STDIN. First two letters of format are also accepted."
|
493
615
|
c.arg_name "output_format"
|
494
616
|
c.default_value "yaml"
|
495
|
-
c.flag %i[f format], :must_match => /^(csv
|
617
|
+
c.flag %i[f format], :must_match => /^(csv?|l(?:i(?:st)?)?|y(?:a(?:ml)?)?|j(?:s(?:on)?)?|p(?:l(?:ist)?)?|co(?:mp(?:lete)?)?)$/, :type => String
|
496
618
|
|
497
619
|
c.action do |global_options, options, args|
|
498
620
|
files = []
|
@@ -506,30 +628,40 @@ command :add do |c|
|
|
506
628
|
end
|
507
629
|
end
|
508
630
|
|
509
|
-
files.concat(@piped_content.split("\n")) if @piped_content
|
631
|
+
files.concat(@piped_content.split(global_options[:null] ? "\x00" : "\n")) if @piped_content && @piped_content.file_list?(global_options[:null])
|
510
632
|
|
511
|
-
if files.empty?
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
end
|
516
|
-
|
517
|
-
|
633
|
+
# if files.empty?
|
634
|
+
# if @jt.default_post_location && File.exist?(@jt.default_post_location)
|
635
|
+
# files = Dir.glob(File.join(@jt.default_post_location, "*.#{@jt.post_extension}"))
|
636
|
+
# end
|
637
|
+
# end
|
638
|
+
# raise NoValidFile if files.empty?
|
639
|
+
raise NoTagsFound, "No tags to add specified" if new_tags.empty?
|
518
640
|
|
519
|
-
|
520
|
-
tags = @jt.post_tags(
|
641
|
+
if @piped_content && !@piped_content.file_list?(global_options[:null])
|
642
|
+
tags = @jt.post_tags(@piped_content, true)
|
521
643
|
tags.concat(new_tags)
|
522
|
-
tags.uniq!
|
523
644
|
tags.sort!
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
645
|
+
tags.uniq!
|
646
|
+
@util.console_log "STDIN:", level: :debug
|
647
|
+
# TODO: Add debug level to console_log
|
648
|
+
@util.output_tags(tags, format: options[:format], content: @piped_content)
|
649
|
+
else
|
650
|
+
files.each do |file|
|
651
|
+
tags = @jt.post_tags(file)
|
652
|
+
tags.concat(new_tags)
|
653
|
+
tags.sort!
|
654
|
+
tags.uniq!
|
655
|
+
unless global_options[:t]
|
656
|
+
@jt.update_file_tags(file, tags)
|
657
|
+
@util.console_log "Updated tags for #{file}", log: true, level: :warn
|
658
|
+
end
|
528
659
|
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
660
|
+
@util.console_log level: :info
|
661
|
+
@util.console_log "#{File.basename(file)}:", level: :info
|
662
|
+
@util.output_tags(tags, :format => options[:format], :filename => file)
|
663
|
+
end
|
664
|
+
end
|
533
665
|
end
|
534
666
|
end
|
535
667
|
|
@@ -558,11 +690,11 @@ command :remove do |c|
|
|
558
690
|
end
|
559
691
|
end
|
560
692
|
|
561
|
-
files.concat(@piped_content.split("\n")) if @piped_content
|
693
|
+
files.concat(@piped_content.split(global_options[:null] ? "\x00" : "\n")) if @piped_content && @piped_content.file_list?(global_options[:null])
|
562
694
|
|
563
695
|
if files.empty?
|
564
|
-
if @jt.default_post_location && File.exist?(
|
565
|
-
files = Dir.glob(@jt.default_post_location)
|
696
|
+
if @jt.default_post_location && File.exist?(@jt.default_post_location)
|
697
|
+
files = Dir.glob(File.join(@jt.default_post_location, "*.#{@jt.post_extension}"))
|
566
698
|
end
|
567
699
|
end
|
568
700
|
exit_now! "No valid filename in arguments" if files.empty?
|
@@ -576,10 +708,10 @@ command :remove do |c|
|
|
576
708
|
remove_tags.push($1.strip)
|
577
709
|
end
|
578
710
|
}
|
579
|
-
console_log "Found #{remove_tags.length} tags in #{File.basename(path)}..."
|
711
|
+
@util.console_log "Found #{remove_tags.length} tags in #{File.basename(path)}...", level: :info, err: true
|
580
712
|
end
|
581
713
|
|
582
|
-
|
714
|
+
raise NoTagsFound if remove_tags.empty?
|
583
715
|
|
584
716
|
files.each { |file|
|
585
717
|
tags = @jt.post_tags(file)
|
@@ -594,12 +726,12 @@ command :remove do |c|
|
|
594
726
|
}
|
595
727
|
unless global_options[:t]
|
596
728
|
@jt.update_file_tags(file, tags)
|
597
|
-
console_log "Updated tags for #{file}", :
|
729
|
+
@util.console_log "Updated tags for #{file}", log: true, level: :warn, err: true
|
598
730
|
end
|
599
731
|
|
600
|
-
console_log
|
601
|
-
console_log File.basename(file)
|
602
|
-
output_tags(tags, { :format => options[:format], :filename => file })
|
732
|
+
@util.console_log level: :info
|
733
|
+
@util.console_log "#{File.basename(file)}:", level: :info
|
734
|
+
@util.output_tags(tags, { :format => options[:format], :filename => file })
|
603
735
|
}
|
604
736
|
end
|
605
737
|
end
|
@@ -612,17 +744,14 @@ command :tag do |c|
|
|
612
744
|
c.flag %i[f format], :must_match => /^(csv|list|yaml|json|plist|complete)$/, :type => String, :default_value => "yaml"
|
613
745
|
|
614
746
|
c.action do |global_options, options, args|
|
615
|
-
if @piped_content && !@piped_content.file_list?
|
747
|
+
if @piped_content && !@piped_content.file_list?(global_options[:null])
|
616
748
|
suggestions = @jt.suggest(@piped_content)
|
617
749
|
if args.length > 0
|
618
|
-
console_log
|
619
|
-
console_log "STDIN:", :err
|
620
|
-
end
|
621
|
-
if options[:format] == "complete"
|
622
|
-
@jt.update_file_tags(@piped_content, suggestions, true)
|
623
|
-
else
|
624
|
-
output_tags(suggestions, :format => options[:format], :filename => nil)
|
750
|
+
@util.console_log level: :info
|
751
|
+
@util.console_log "STDIN:", level: info, err: true
|
625
752
|
end
|
753
|
+
|
754
|
+
@util.output_tags(suggestions, :format => options[:format], :filename => nil)
|
626
755
|
end
|
627
756
|
|
628
757
|
files = []
|
@@ -631,7 +760,7 @@ command :tag do |c|
|
|
631
760
|
files.push(arg) if File.exist?(arg)
|
632
761
|
end
|
633
762
|
|
634
|
-
files.concat(@piped_content.split("\n")) if @piped_content && @piped_content.file_list?
|
763
|
+
files.concat(@piped_content.split(global_options[:null] ? "\x00" : "\n")) if @piped_content && @piped_content.file_list?(global_options[:null])
|
635
764
|
|
636
765
|
files.each do |file|
|
637
766
|
if File.exist?(File.expand_path(file))
|
@@ -640,61 +769,37 @@ command :tag do |c|
|
|
640
769
|
|
641
770
|
unless global_options[:t]
|
642
771
|
if @jt.update_file_tags(file, suggestions)
|
643
|
-
console_log
|
644
|
-
console_log "Updated file #{file} with:", :
|
772
|
+
@util.console_log level: :info
|
773
|
+
@util.console_log "Updated file #{file} with:", log: true, level: :warn
|
645
774
|
else
|
646
|
-
console_log
|
647
|
-
console_log "Failed to update #{file} with:", :
|
775
|
+
@util.console_log level: :info
|
776
|
+
@util.console_log "Failed to update #{file} with:", log: true, level: :error
|
648
777
|
end
|
649
778
|
end
|
650
779
|
if !global_options[:s] || global_options[:t]
|
651
780
|
if args.length > 1
|
652
|
-
console_log
|
653
|
-
console_log File.basename(file)
|
781
|
+
@util.console_log level: :info
|
782
|
+
@util.console_log "#{File.basename(file)}:", err: true, level: :info
|
654
783
|
end
|
655
|
-
output_tags(suggestions, :format => options[:format], :filename => file)
|
784
|
+
@util.output_tags(suggestions, :format => options[:format], :filename => file)
|
656
785
|
end
|
657
786
|
suggestions = nil
|
658
787
|
else
|
659
|
-
raise "No such file: #{file}"
|
788
|
+
raise FileNotFound, "No such file: #{file}"
|
660
789
|
end
|
661
790
|
end
|
662
791
|
end
|
663
792
|
end
|
664
793
|
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
console_log tags.map { |tag|
|
675
|
-
if tag.strip =~ /\b\s\b/
|
676
|
-
%Q{"#{tag.strip}"}
|
677
|
-
else
|
678
|
-
tag.strip
|
679
|
-
end
|
680
|
-
}.join(" ")
|
681
|
-
end
|
682
|
-
when "csv"
|
683
|
-
console_log tags.to_csv
|
684
|
-
when "json"
|
685
|
-
out = {}
|
686
|
-
out["tags"] = tags
|
687
|
-
out["path"] = filename if filename
|
688
|
-
console_log out.to_json
|
689
|
-
when "plist"
|
690
|
-
out = {}
|
691
|
-
out["path"] = filename if filename
|
692
|
-
console_log tags.to_plist
|
693
|
-
else
|
694
|
-
out = {}
|
695
|
-
options[:grouping] ||= "tags"
|
696
|
-
out[options[:grouping]] = tags
|
697
|
-
console_log out.to_yaml
|
794
|
+
##
|
795
|
+
## Move deprecated config files to new location
|
796
|
+
##
|
797
|
+
## @return [void]
|
798
|
+
##
|
799
|
+
def update_deprecated_config
|
800
|
+
if File.exist?(File.expand_path("~/.jtag"))
|
801
|
+
FileUtils.mv(File.expand_path("~/.jtag"), File.expand_path("~/.config/jtag"))
|
802
|
+
@util.console_log "Moved ~/.jtag to ~/.config/jtag", level: :info, err: true, log: false
|
698
803
|
end
|
699
804
|
end
|
700
805
|
|
@@ -709,30 +814,43 @@ end
|
|
709
814
|
# end
|
710
815
|
|
711
816
|
pre do |global, command, options, args|
|
712
|
-
# Use skips_pre before a command to skip this block
|
713
|
-
# on that command only
|
714
|
-
@silent = global[:silent]
|
715
|
-
|
716
817
|
Signal.trap("PIPE", "EXIT")
|
717
818
|
|
718
|
-
@
|
819
|
+
@util = JekyllTag::Util.new
|
820
|
+
@util.config_target = if global[:config_dir]
|
821
|
+
File.expand_path(global[:config_dir])
|
822
|
+
else
|
823
|
+
File.expand_path("~/.config/jtag")
|
824
|
+
end
|
825
|
+
|
826
|
+
# if global[:debug] == 0 or global[:silent], run silently
|
827
|
+
@util.silent = global[:debug] == 0 || global[:silent]
|
828
|
+
|
829
|
+
# set up logging
|
830
|
+
logfile = File.open(File.join(Dir.tmpdir, "jtag_actions.log"), "a")
|
719
831
|
|
720
|
-
|
832
|
+
# set up logger
|
833
|
+
@util.log = Logger.new(logfile, shift_age = 7, shift_size = 1048576)
|
721
834
|
|
722
|
-
|
723
|
-
|
724
|
-
|
835
|
+
# set log level based on flags
|
836
|
+
@util.debug_level = @silent ? 0 : global[:debug]
|
837
|
+
|
838
|
+
# check for config files, write them if they don't exist
|
839
|
+
unless @util.config_files_complete?
|
840
|
+
@util.write_config
|
841
|
+
@util.console_log "Missing config files written to #{@util.config_target}. Please check your configuration.", err: true, level: :error
|
725
842
|
return false
|
726
843
|
end
|
727
844
|
|
728
|
-
configfile = File.
|
845
|
+
configfile = File.join(@util.config_target, "config.yml")
|
729
846
|
|
730
|
-
|
731
|
-
|
847
|
+
# load config file
|
848
|
+
config = YAML.load_file(configfile)
|
849
|
+
global[:config] = config.symbolize_keys
|
732
850
|
|
733
851
|
@piped_content = $stdin.read if $stdin.fcntl(Fcntl::F_GETFL, 0) == 0 || $stdin.stat.pipe?
|
734
852
|
|
735
|
-
@jt = JTag.new(
|
853
|
+
@jt = JekyllTag::JTag.new(@util.config_target, global[:config])
|
736
854
|
|
737
855
|
true
|
738
856
|
end
|
@@ -744,7 +862,11 @@ post do |global, command, options, args|
|
|
744
862
|
end
|
745
863
|
|
746
864
|
on_error do |exception|
|
747
|
-
|
865
|
+
if exception.is_a?(NoValidFile) || exception.is_a?(FileNotFound) || exception.is_a?(NoTagsFound)
|
866
|
+
@util.console_log "#{@jt.color(:red)}#{exception.message}", err: true, level: :error
|
867
|
+
return false
|
868
|
+
end
|
869
|
+
|
748
870
|
# return false to skip default error handling
|
749
871
|
true
|
750
872
|
end
|