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.
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
- def config_files_complete?
18
- @config_files.each do |file|
19
- return false unless File.exist?(File.join(@config_target, file))
20
- end
21
- true
22
- end
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
- class ::String
25
- def file_list?
26
- self.strip.split("\n").each do |line|
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 => 0
28
+ flag %i[d debug], :must_match => /\d+/, :type => Integer, :default_value => 3
37
29
 
38
- desc "Run silently"
39
- switch %i[s silent]
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 "Perform case-insensitive matches and searches"
42
- switch %i[i case_insensitive]
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 [:r, "reset"]
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[:r]
78
- print "Are you sure you want to reset all config files? y/N: "
79
- response = STDIN.gets.strip
80
- if response =~ /^y/i
81
- write_config(true)
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
- else
85
- write_config(false)
69
+ return
86
70
  end
87
- end
88
- end
89
71
 
90
- def write_config(atomic = false)
91
- gem_root = Gem.loaded_specs["jtag"].full_gem_path
92
- gem_lib = File.join(gem_root, "lib")
93
- config_source = File.join(gem_lib, "/jtag/config_files")
94
-
95
- unless File.directory?(@config_target) || atomic
96
- FileUtils.cp_r(config_source, @config_target)
97
- console_log "Configuration files are located in the folder: " + @config_target
98
- console_log %Q{Make sure that "tags_location" in config.yml is set to your tags.json url.}
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
- @config_files.each do |file|
103
- unless File.exist?(File.join(@config_target, file))
104
- source_file = File.join(config_source, file)
105
- target_file = File.join(@config_target, file)
106
- FileUtils.cp(source_file, target_file)
107
- console_log "Config file #{file} added."
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
- tags = @jt.get_tags({ :counts => true })
131
- if args.length > 0
132
- re = args.join("|")
133
- tags.delete_if { |tag|
134
- if tag && tag["name"] =~ /(#{re})/i
135
- false
136
- else
137
- true
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
- if options[:c]
141
- tags.map! { |tag| "#{tag["name"]} (#{tag["count"]})" }
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.map! { |tag| tag["name"] }
171
+ tags = @jt.tags({ :counts => true, :files => files })
144
172
  end
145
- output_tags(tags, { :format => options[:format] })
146
173
  else
147
- tags.delete_if { |tag| !tag }
148
- if options[:c]
149
- tags.map! { |tag| "#{tag["name"]} (#{tag["count"]})" }
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
- tags.map! { |tag| tag["name"] }
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.concat(@piped_content.split("\n")) if @piped_content
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?(File.dirname(@jt.default_post_location))
192
- files = Dir.glob(@jt.default_post_location)
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
- exit_now! "No valid filename in arguments" if files.empty?
196
- files.each { |file|
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.each { |tag|
214
- if post_tags.include?(tag)
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
- raise "File not found: #{file}"
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})", { :err => true }
229
- output_tags(matches, { :format => options[:format], :print0 => options[:print0], :grouping => "files" })
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
- loner_tags = @jt.get_tags({ :counts => true })
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 the file."
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?(File.dirname(@jt.default_post_location))
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", { :err => true }
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", { :err => true }
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?(File.dirname(@jt.default_post_location))
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 %Q{Merging #{tags.join(", ")} to #{merge_tag}}
455
- files.each { |file|
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[:t]
580
+ unless global_options[:test]
459
581
  @jt.update_file_tags(file, new_tags)
460
- console_log
461
- console_log "Updated tags for #{file}", :log => true
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, { :format => options[:format], :filename => file })
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 yaml. Defaults to yaml."
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|list|yaml|json|plist)$/, :type => String
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
- if @jt.default_post_location && File.exist?(File.dirname(@jt.default_post_location))
513
- files = Dir.glob(@jt.default_post_location)
514
- end
515
- end
516
- exit_now! "No valid filename in arguments" if files.empty?
517
- exit_now! "No tags found in arguments" if new_tags.empty?
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
- files.each { |file|
520
- tags = @jt.post_tags(file)
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
- unless global_options[:t]
525
- @jt.update_file_tags(file, tags)
526
- console_log "Updated tags for #{file}", :log => true
527
- end
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
- console_log
530
- console_log File.basename(file) + ":"
531
- output_tags(tags, :format => options[:format], :filename => file)
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?(File.dirname(@jt.default_post_location))
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
- exit_now! "No tags found in input, my work here is done" if remove_tags.empty?
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}", :log => true
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 => true
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:", :log => true
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:", :log => true
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) + ":", :err => true, :log => true
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
- def output_tags(tags, options)
666
- format = options[:format] || "yaml"
667
- print0 = options[:print0] || false
668
- filename = options[:filename] || false
669
- case format
670
- when "list"
671
- unless print0
672
- console_log tags.join("\n")
673
- else
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
- @logfile = File.open(File.join(Dir.tmpdir, "jtag_actions.log"), "a")
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
- @log = Logger.new(@logfile, shift_age = 7, shift_size = 1048576)
832
+ # set up logger
833
+ @util.log = Logger.new(logfile, shift_age = 7, shift_size = 1048576)
721
834
 
722
- unless config_files_complete?
723
- write_config
724
- console_log "Missing config files written to #{@config_target}. Please check your configuration.", { :err => true }
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.expand_path("~/.jtag/config.yml")
845
+ configfile = File.join(@util.config_target, "config.yml")
729
846
 
730
- global[:config] = YAML::load(File.open(configfile, "r"))
731
- global[:support] = File.expand_path("~/.jtag")
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(global[:support], global[:config])
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
- # Error logic here
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