jtag 0.1.22 → 0.2.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.
- checksums.yaml +4 -4
- data/bin/jtag +271 -377
- data/lib/jtag/config_files/config.yml +2 -3
- data/lib/jtag/jekylltag.rb +225 -272
- data/lib/jtag/string.rb +42 -238
- data/lib/jtag/version.rb +1 -1
- data/lib/jtag.rb +13 -19
- metadata +19 -20
- data/.irbrc +0 -6
- data/CHANGELOG.md +0 -15
- data/Gemfile +0 -3
- data/Jekyll/plugins/autotag_gen.rb +0 -58
- data/Jekyll/source/_layouts/tags_json.html +0 -11
- data/README.md +0 -159
- data/Rakefile +0 -113
- data/jtag.completion.bash +0 -9
- data/jtag.gemspec +0 -25
- data/lib/jtag/array.rb +0 -48
- data/lib/jtag/errors.rb +0 -31
- data/lib/jtag/hash.rb +0 -103
- data/lib/jtag/stupid_json.rb +0 -319
- data/lib/jtag/util.rb +0 -237
- data/mise.toml +0 -2
data/bin/jtag
CHANGED
|
@@ -10,85 +10,123 @@ 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("~/.config/jtag")
|
|
13
15
|
@piped_content = nil
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
25
32
|
|
|
26
|
-
desc "Debug level
|
|
33
|
+
desc "Debug level"
|
|
34
|
+
default_value "0"
|
|
27
35
|
arg_name "debug_level"
|
|
28
|
-
flag %i[d debug], :must_match => /\d+/, :type => Integer, :default_value =>
|
|
36
|
+
flag %i[d debug], :must_match => /\d+/, :type => Integer, :default_value => 0
|
|
29
37
|
|
|
30
|
-
desc "
|
|
31
|
-
switch %i[
|
|
38
|
+
desc "Run silently"
|
|
39
|
+
switch %i[s silent]
|
|
32
40
|
|
|
33
|
-
desc "
|
|
34
|
-
switch %i[
|
|
41
|
+
desc "Perform case-insensitive matches and searches"
|
|
42
|
+
switch %i[i case_insensitive]
|
|
35
43
|
|
|
36
|
-
desc "Test (dry run, don't update files)
|
|
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"
|
|
37
46
|
default_value false
|
|
38
|
-
switch %i[t test]
|
|
47
|
+
switch %i[t test]
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# Add a switch to reset all configuration files to default values
|
|
44
|
-
c.desc "Reset all configuration files to default values"
|
|
45
|
-
c.switch %i[r reset]
|
|
49
|
+
def console_log(msg = "", options = {})
|
|
50
|
+
err = options[:err] || false
|
|
51
|
+
options[:log] ||= false
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
if options[:log]
|
|
54
|
+
if err
|
|
55
|
+
@log.warn(msg)
|
|
56
|
+
else
|
|
57
|
+
@log.info(msg)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
49
60
|
|
|
50
|
-
|
|
51
|
-
c.arg_name "key"
|
|
52
|
-
c.flag %i[s set]
|
|
61
|
+
return if @silent
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
unless err
|
|
64
|
+
$stdout.puts msg
|
|
65
|
+
else
|
|
66
|
+
warn msg
|
|
67
|
+
end
|
|
68
|
+
end
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
def migrate_old_config
|
|
71
|
+
old_config = File.expand_path("~/.jtag")
|
|
72
|
+
new_config = File.expand_path("~/.config/jtag")
|
|
73
|
+
|
|
74
|
+
if File.directory?(old_config) && !File.directory?(new_config)
|
|
75
|
+
# Create parent directory if needed
|
|
76
|
+
FileUtils.mkdir_p(File.dirname(new_config))
|
|
77
|
+
|
|
78
|
+
# Move the directory
|
|
79
|
+
FileUtils.mv(old_config, new_config)
|
|
80
|
+
|
|
81
|
+
# ANSI color codes: yellow=\033[0;33m, boldyellow=\033[1;33m, default=\033[0;39m
|
|
82
|
+
console_log "\033[0;33mConfiguration directory has been moved from \033[1;33m~/.jtag\033[0;33m to \033[1;33m~/.config/jtag\033[0;33m.\033[0;39m", :err => true
|
|
83
|
+
console_log "\033[0;33mYour existing configuration and files have been preserved.\033[0;39m", :err => true
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
desc "Update and notify user of configuration files location"
|
|
88
|
+
command :config do |c|
|
|
89
|
+
c.desc "Reset all configuration files to default values"
|
|
90
|
+
c.switch [:r, "reset"]
|
|
60
91
|
|
|
61
|
-
|
|
92
|
+
c.skips_pre
|
|
62
93
|
c.action do |global_options, options, args|
|
|
63
|
-
if options[:
|
|
64
|
-
config
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
94
|
+
if options[:r]
|
|
95
|
+
print "Are you sure you want to reset all config files? y/N: "
|
|
96
|
+
response = STDIN.gets.strip
|
|
97
|
+
if response =~ /^y/i
|
|
98
|
+
write_config(true)
|
|
99
|
+
console_log "Config files reset"
|
|
68
100
|
end
|
|
69
|
-
|
|
101
|
+
else
|
|
102
|
+
write_config(false)
|
|
70
103
|
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
71
106
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
107
|
+
def write_config(atomic = false)
|
|
108
|
+
gem_root = Gem.loaded_specs["jtag"].full_gem_path
|
|
109
|
+
gem_lib = File.join(gem_root, "lib")
|
|
110
|
+
config_source = File.join(gem_lib, "/jtag/config_files")
|
|
81
111
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
unless File.directory?(@config_target) || atomic
|
|
113
|
+
FileUtils.cp_r(config_source, @config_target)
|
|
114
|
+
console_log "Configuration files are located in the folder: " + @config_target
|
|
115
|
+
console_log %Q{Make sure that "tags_location" in config.yml is set to your tags.json url.}
|
|
116
|
+
console_log "Configuration files written to #{@config_target}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
@config_files.each do |file|
|
|
120
|
+
unless File.exist?(File.join(@config_target, file))
|
|
121
|
+
source_file = File.join(config_source, file)
|
|
122
|
+
target_file = File.join(@config_target, file)
|
|
123
|
+
FileUtils.cp(source_file, target_file)
|
|
124
|
+
console_log "Config file #{file} added."
|
|
90
125
|
end
|
|
91
126
|
end
|
|
127
|
+
console_log
|
|
128
|
+
console_log "Configuration files are located in the folder: " + @config_target
|
|
129
|
+
console_log %Q{Make sure that "tags_location" in the config.yml file is set to your tags json file.}
|
|
92
130
|
end
|
|
93
131
|
|
|
94
132
|
desc "List tags, optionally filter for keywords/regular expressions (OR)"
|
|
@@ -105,125 +143,32 @@ command :search do |c|
|
|
|
105
143
|
c.default_value false
|
|
106
144
|
c.switch %i[c counts]
|
|
107
145
|
|
|
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
|
-
|
|
126
146
|
c.action do |global_options, options, args|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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}"))
|
|
147
|
+
tags = @jt.get_tags({ :counts => true })
|
|
148
|
+
if args.length > 0
|
|
149
|
+
re = args.join("|")
|
|
150
|
+
tags.delete_if { |tag|
|
|
151
|
+
if tag && tag["name"] =~ /(#{re})/i
|
|
152
|
+
false
|
|
153
|
+
else
|
|
154
|
+
true
|
|
156
155
|
end
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
156
|
+
}
|
|
157
|
+
if options[:c]
|
|
158
|
+
tags.map! { |tag| "#{tag["name"]} (#{tag["count"]})" }
|
|
170
159
|
else
|
|
171
|
-
tags
|
|
160
|
+
tags.map! { |tag| tag["name"] }
|
|
172
161
|
end
|
|
162
|
+
output_tags(tags, { :format => options[:format] })
|
|
173
163
|
else
|
|
174
|
-
tags
|
|
175
|
-
|
|
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) } }
|
|
164
|
+
tags.delete_if { |tag| !tag }
|
|
165
|
+
if options[:c]
|
|
166
|
+
tags.map! { |tag| "#{tag["name"]} (#{tag["count"]})" }
|
|
206
167
|
else
|
|
207
|
-
|
|
208
|
-
# Filter tags based on any keywords
|
|
209
|
-
tags.delete_if { |tag| tag && !keywords.any? { |keyword| tag["name"].match_keyword(keyword, match_options) } }
|
|
168
|
+
tags.map! { |tag| tag["name"] }
|
|
210
169
|
end
|
|
170
|
+
output_tags(tags, { :format => options[:format] })
|
|
211
171
|
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] })
|
|
227
172
|
end
|
|
228
173
|
end
|
|
229
174
|
|
|
@@ -244,73 +189,61 @@ command :posts_tagged do |c|
|
|
|
244
189
|
c.switch [:print0]
|
|
245
190
|
|
|
246
191
|
c.action do |global_options, options, args|
|
|
247
|
-
# Convert the boolean operator to uppercase for consistency
|
|
248
192
|
bool = options[:bool].upcase
|
|
249
|
-
|
|
250
|
-
# Initialize arrays to store files, tags, and matches
|
|
251
193
|
files = []
|
|
252
194
|
tags = []
|
|
253
195
|
matches = []
|
|
254
|
-
|
|
255
|
-
# Separate files and tags from the arguments
|
|
256
196
|
args.length.times do
|
|
257
197
|
arg = args.pop
|
|
258
198
|
if File.exist?(arg)
|
|
259
|
-
files.push(arg)
|
|
199
|
+
files.push(arg)
|
|
260
200
|
else
|
|
261
|
-
tags.push(arg)
|
|
201
|
+
tags.push(arg)
|
|
262
202
|
end
|
|
263
203
|
end
|
|
264
204
|
|
|
265
|
-
|
|
266
|
-
files.concat(@piped_content.split(global_options[:null] ? "\x00" : "\n")) if @piped_content && @piped_content.file_list?(global_options[:null])
|
|
205
|
+
files.concat(@piped_content.split("\n")) if @piped_content
|
|
267
206
|
|
|
268
|
-
# If no files are provided, use the default post location
|
|
269
207
|
if files.empty?
|
|
270
|
-
if @jt.default_post_location && File.exist?(@jt.default_post_location)
|
|
271
|
-
|
|
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}"))
|
|
208
|
+
if @jt.default_post_location && File.exist?(File.dirname(@jt.default_post_location))
|
|
209
|
+
files = Dir.glob(@jt.default_post_location)
|
|
274
210
|
end
|
|
275
211
|
end
|
|
276
|
-
|
|
277
|
-
|
|
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|
|
|
212
|
+
exit_now! "No valid filename in arguments" if files.empty?
|
|
213
|
+
files.each { |file|
|
|
282
214
|
if File.exist?(file)
|
|
283
|
-
post_tags = @jt.post_tags(file)
|
|
215
|
+
post_tags = @jt.post_tags(file)
|
|
284
216
|
|
|
285
217
|
if bool == "AND"
|
|
286
|
-
# Check if all tags are present in the post tags
|
|
287
218
|
matched = 0
|
|
288
|
-
tags.each { |tag|
|
|
219
|
+
tags.each { |tag|
|
|
220
|
+
matched += 1 if post_tags.include?(tag)
|
|
221
|
+
}
|
|
289
222
|
matches.push(file) if matched == tags.length
|
|
290
223
|
elsif bool == "NOT"
|
|
291
|
-
# Check if none of the tags are present in the post tags
|
|
292
224
|
matched = false
|
|
293
|
-
tags.each { |tag|
|
|
225
|
+
tags.each { |tag|
|
|
226
|
+
matched = true if post_tags.include?(tag)
|
|
227
|
+
}
|
|
294
228
|
matches.push(file) unless matched
|
|
295
229
|
else
|
|
296
|
-
|
|
297
|
-
|
|
230
|
+
tags.each { |tag|
|
|
231
|
+
if post_tags.include?(tag)
|
|
232
|
+
matches.push(file) unless matches.include?(file)
|
|
233
|
+
end
|
|
234
|
+
}
|
|
298
235
|
end
|
|
299
236
|
else
|
|
300
|
-
|
|
301
|
-
raise FileNotFound, "File not found: #{file}"
|
|
237
|
+
raise "File not found: #{file}"
|
|
302
238
|
end
|
|
303
|
-
|
|
239
|
+
}
|
|
304
240
|
|
|
305
|
-
# Create a search string from the tags and boolean operator
|
|
306
241
|
search_string = tags.join(" #{bool} ")
|
|
307
|
-
|
|
308
|
-
# Output the results
|
|
309
242
|
if matches.empty?
|
|
310
|
-
|
|
243
|
+
console_log "No matching files found for #{search_string}"
|
|
311
244
|
else
|
|
312
|
-
|
|
313
|
-
|
|
245
|
+
console_log "(#{search_string})", { :err => true }
|
|
246
|
+
output_tags(matches, { :format => options[:format], :print0 => options[:print0], :grouping => "files" })
|
|
314
247
|
end
|
|
315
248
|
end
|
|
316
249
|
end
|
|
@@ -339,48 +272,15 @@ command :loners do |c|
|
|
|
339
272
|
c.flag %i[e edit], :default_value => false, :type => String
|
|
340
273
|
|
|
341
274
|
c.action do |global_options, options, args|
|
|
342
|
-
# Convert the max option to an integer
|
|
343
275
|
max = options[:m].to_i
|
|
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)
|
|
276
|
+
loner_tags = @jt.get_tags({ :counts => true })
|
|
373
277
|
loner_tags.delete_if { |tag|
|
|
374
278
|
tag.class == FalseClass || tag["count"] > max
|
|
375
279
|
}
|
|
376
|
-
|
|
377
|
-
# Sort loner tags by count
|
|
378
280
|
loner_tags.sort_by! { |tag| tag["count"] }
|
|
379
281
|
|
|
380
|
-
# Exit if no tags matched the criteria
|
|
381
282
|
exit_now! "No tags matched the criteria" if loner_tags.empty? || loner_tags.nil?
|
|
382
283
|
|
|
383
|
-
# If the edit option is provided, write the loner tags to a file for editing
|
|
384
284
|
if options[:e]
|
|
385
285
|
path = File.expand_path(options[:e])
|
|
386
286
|
while File.exist?(path)
|
|
@@ -395,7 +295,7 @@ command :loners do |c|
|
|
|
395
295
|
File.open(path, "w+") do |f|
|
|
396
296
|
f.puts "# Edit this file to remove tags you want to keep,"
|
|
397
297
|
f.puts "# then run `jtag remove -p '#{path}' [/path/to/posts/*.md]`"
|
|
398
|
-
f.puts "# to remove any tags left in
|
|
298
|
+
f.puts "# to remove any tags left in the file."
|
|
399
299
|
f.puts "#"
|
|
400
300
|
f.puts "# The post counts are included for your convenience, and will"
|
|
401
301
|
f.puts "# be automatically ignored when reading the list back in."
|
|
@@ -406,17 +306,15 @@ command :loners do |c|
|
|
|
406
306
|
}
|
|
407
307
|
end
|
|
408
308
|
|
|
409
|
-
|
|
309
|
+
console_log "A list of results and instructions for use have been written to #{path}."
|
|
410
310
|
if ENV["EDITOR"]
|
|
411
|
-
|
|
311
|
+
console_log
|
|
412
312
|
print "Would you like to open the file in #{ENV["EDITOR"]} now? (y/N) "
|
|
413
313
|
input = STDIN.gets
|
|
414
314
|
if input =~ /^y/i
|
|
415
315
|
system "#{ENV["EDITOR"]} '#{path}'"
|
|
416
316
|
end
|
|
417
317
|
end
|
|
418
|
-
|
|
419
|
-
# If the remove option is provided, remove the loner tags from the specified files
|
|
420
318
|
elsif options[:r]
|
|
421
319
|
files = []
|
|
422
320
|
args.length.times do
|
|
@@ -424,15 +322,14 @@ command :loners do |c|
|
|
|
424
322
|
files.push(arg) if File.exist?(arg)
|
|
425
323
|
end
|
|
426
324
|
|
|
427
|
-
files.concat(@piped_content.split(
|
|
325
|
+
files.concat(@piped_content.split("\n")) if @piped_content
|
|
428
326
|
|
|
429
327
|
if files.empty?
|
|
430
|
-
if @jt.default_post_location && File.exist?(@jt.default_post_location)
|
|
431
|
-
files = Dir.glob(
|
|
328
|
+
if @jt.default_post_location && File.exist?(File.dirname(@jt.default_post_location))
|
|
329
|
+
files = Dir.glob(@jt.default_post_location)
|
|
432
330
|
end
|
|
433
331
|
end
|
|
434
332
|
exit_now! "No valid filename in arguments" if files.empty?
|
|
435
|
-
|
|
436
333
|
files.each { |file|
|
|
437
334
|
tags = @jt.post_tags(file)
|
|
438
335
|
loner_tags.each { |d|
|
|
@@ -446,17 +343,15 @@ command :loners do |c|
|
|
|
446
343
|
}
|
|
447
344
|
unless global_options[:t]
|
|
448
345
|
@jt.update_file_tags(file, tags)
|
|
449
|
-
|
|
346
|
+
console_log "Updated tags for #{file}", :log => true
|
|
450
347
|
end
|
|
451
348
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
349
|
+
console_log
|
|
350
|
+
console_log File.basename(file) + ":"
|
|
351
|
+
output_tags(tags, :format => options[:format], :filename => file)
|
|
455
352
|
}
|
|
456
|
-
|
|
457
|
-
# Otherwise, output the loner tags
|
|
458
353
|
else
|
|
459
|
-
|
|
354
|
+
output_tags(loner_tags.map { |tag|
|
|
460
355
|
count = options[:no_counts] ? "" : " (#{tag["count"]})"
|
|
461
356
|
"#{tag["name"]}#{count}"
|
|
462
357
|
}, :format => options[:format])
|
|
@@ -481,24 +376,24 @@ command :tags do |c|
|
|
|
481
376
|
end
|
|
482
377
|
end
|
|
483
378
|
|
|
484
|
-
files.concat(@piped_content.split(
|
|
379
|
+
files.concat(@piped_content.split("\n")) if @piped_content && @piped_content.file_list?
|
|
485
380
|
|
|
486
381
|
tags = []
|
|
487
382
|
files.each do |file|
|
|
488
383
|
tags.concat(@jt.post_tags(file)) if File.exist?(file)
|
|
489
384
|
|
|
490
385
|
if args.length > 0
|
|
491
|
-
|
|
492
|
-
|
|
386
|
+
console_log
|
|
387
|
+
console_log "STDIN:"
|
|
493
388
|
end
|
|
494
389
|
end
|
|
495
390
|
|
|
496
391
|
if tags.empty? || tags.nil?
|
|
497
|
-
|
|
392
|
+
console_log "No tags in post", { :err => true }
|
|
498
393
|
else
|
|
499
394
|
tags.sort!
|
|
500
395
|
tags.uniq!
|
|
501
|
-
|
|
396
|
+
output_tags(tags, { :format => options[:format] })
|
|
502
397
|
end
|
|
503
398
|
end
|
|
504
399
|
end
|
|
@@ -520,7 +415,7 @@ command :sort do |c|
|
|
|
520
415
|
end
|
|
521
416
|
end
|
|
522
417
|
|
|
523
|
-
files.concat(@piped_content.split(
|
|
418
|
+
files.concat(@piped_content.split("\n")) if @piped_content
|
|
524
419
|
|
|
525
420
|
files.each do |file|
|
|
526
421
|
tags = @jt.post_tags(file)
|
|
@@ -529,13 +424,13 @@ command :sort do |c|
|
|
|
529
424
|
unless global_options[:t]
|
|
530
425
|
@jt.update_file_tags(file, tags)
|
|
531
426
|
end
|
|
532
|
-
|
|
533
|
-
|
|
427
|
+
console_log
|
|
428
|
+
console_log File.basename(file) + ":"
|
|
534
429
|
|
|
535
430
|
if tags.empty? || tags.nil?
|
|
536
|
-
|
|
431
|
+
console_log "No tags in post", { :err => true }
|
|
537
432
|
else
|
|
538
|
-
|
|
433
|
+
output_tags(tags, { :format => options[:format], :filename => file })
|
|
539
434
|
end
|
|
540
435
|
end
|
|
541
436
|
end
|
|
@@ -562,31 +457,31 @@ command :merge do |c|
|
|
|
562
457
|
end
|
|
563
458
|
end
|
|
564
459
|
|
|
565
|
-
files.concat(@piped_content.split(
|
|
460
|
+
files.concat(@piped_content.split("\n")) if @piped_content
|
|
566
461
|
|
|
567
462
|
if files.empty?
|
|
568
|
-
if @jt.default_post_location && File.exist?(@jt.default_post_location)
|
|
569
|
-
files = Dir.glob(
|
|
463
|
+
if @jt.default_post_location && File.exist?(File.dirname(@jt.default_post_location))
|
|
464
|
+
files = Dir.glob(@jt.default_post_location)
|
|
570
465
|
end
|
|
571
466
|
end
|
|
572
467
|
exit_now! "No valid filename in arguments" if files.empty?
|
|
573
468
|
exit_now! "Needs at least two tag inputs, one or more to merge, one to merge to" if tags.length < 2
|
|
574
469
|
tags.reverse!
|
|
575
470
|
merge_tag = tags.pop
|
|
576
|
-
|
|
577
|
-
files.each
|
|
471
|
+
console_log %Q{Merging #{tags.join(", ")} to #{merge_tag}}
|
|
472
|
+
files.each { |file|
|
|
578
473
|
new_tags = @jt.merge_tags(tags, merge_tag, file)
|
|
579
474
|
next unless new_tags
|
|
580
|
-
unless global_options[:
|
|
475
|
+
unless global_options[:t]
|
|
581
476
|
@jt.update_file_tags(file, new_tags)
|
|
582
|
-
|
|
583
|
-
|
|
477
|
+
console_log
|
|
478
|
+
console_log "Updated tags for #{file}", :log => true
|
|
584
479
|
end
|
|
585
480
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
481
|
+
console_log
|
|
482
|
+
console_log File.basename(file) + ":"
|
|
483
|
+
output_tags(new_tags, { :format => options[:format], :filename => file })
|
|
484
|
+
}
|
|
590
485
|
end
|
|
591
486
|
end
|
|
592
487
|
|
|
@@ -599,22 +494,22 @@ command :blacklist do |c|
|
|
|
599
494
|
c.action do |global_options, options, args|
|
|
600
495
|
if options[:r]
|
|
601
496
|
@jt.unblacklist(args)
|
|
602
|
-
|
|
497
|
+
console_log "Removed #{args.join(", ")} from blacklist."
|
|
603
498
|
else
|
|
604
499
|
@jt.blacklist(args)
|
|
605
|
-
|
|
500
|
+
console_log "Blacklisted #{args.join(", ")}."
|
|
606
501
|
end
|
|
607
502
|
end
|
|
608
503
|
end
|
|
609
504
|
|
|
610
505
|
desc "Add tags to post(s)"
|
|
611
|
-
arg_name "tags
|
|
506
|
+
arg_name "tags", :multiple
|
|
507
|
+
arg_name "file_pattern"
|
|
612
508
|
command :add do |c|
|
|
613
|
-
c.desc "Format to use when outputting tags to console: list, json, plist, csv
|
|
614
|
-
c.long_desc "Use 'complete' to output full text when input is STDIN. First two letters of format are also accepted."
|
|
509
|
+
c.desc "Format to use when outputting tags to console: list, json, plist, csv or yaml. Defaults to yaml."
|
|
615
510
|
c.arg_name "output_format"
|
|
616
511
|
c.default_value "yaml"
|
|
617
|
-
c.flag %i[f format], :must_match => /^(csv
|
|
512
|
+
c.flag %i[f format], :must_match => /^(csv|list|yaml|json|plist)$/, :type => String
|
|
618
513
|
|
|
619
514
|
c.action do |global_options, options, args|
|
|
620
515
|
files = []
|
|
@@ -628,40 +523,30 @@ command :add do |c|
|
|
|
628
523
|
end
|
|
629
524
|
end
|
|
630
525
|
|
|
631
|
-
files.concat(@piped_content.split(
|
|
526
|
+
files.concat(@piped_content.split("\n")) if @piped_content
|
|
632
527
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
528
|
+
if files.empty?
|
|
529
|
+
if @jt.default_post_location && File.exist?(File.dirname(@jt.default_post_location))
|
|
530
|
+
files = Dir.glob(@jt.default_post_location)
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
exit_now! "No valid filename in arguments" if files.empty?
|
|
534
|
+
exit_now! "No tags found in arguments" if new_tags.empty?
|
|
640
535
|
|
|
641
|
-
|
|
642
|
-
tags = @jt.post_tags(
|
|
536
|
+
files.each { |file|
|
|
537
|
+
tags = @jt.post_tags(file)
|
|
643
538
|
tags.concat(new_tags)
|
|
644
|
-
tags.sort!
|
|
645
539
|
tags.uniq!
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
|
659
|
-
|
|
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)
|
|
540
|
+
tags.sort!
|
|
541
|
+
unless global_options[:t]
|
|
542
|
+
@jt.update_file_tags(file, tags)
|
|
543
|
+
console_log "Updated tags for #{file}", :log => true
|
|
663
544
|
end
|
|
664
|
-
|
|
545
|
+
|
|
546
|
+
console_log
|
|
547
|
+
console_log File.basename(file) + ":"
|
|
548
|
+
output_tags(tags, :format => options[:format], :filename => file)
|
|
549
|
+
}
|
|
665
550
|
end
|
|
666
551
|
end
|
|
667
552
|
|
|
@@ -690,11 +575,11 @@ command :remove do |c|
|
|
|
690
575
|
end
|
|
691
576
|
end
|
|
692
577
|
|
|
693
|
-
files.concat(@piped_content.split(
|
|
578
|
+
files.concat(@piped_content.split("\n")) if @piped_content
|
|
694
579
|
|
|
695
580
|
if files.empty?
|
|
696
|
-
if @jt.default_post_location && File.exist?(@jt.default_post_location)
|
|
697
|
-
files = Dir.glob(
|
|
581
|
+
if @jt.default_post_location && File.exist?(File.dirname(@jt.default_post_location))
|
|
582
|
+
files = Dir.glob(@jt.default_post_location)
|
|
698
583
|
end
|
|
699
584
|
end
|
|
700
585
|
exit_now! "No valid filename in arguments" if files.empty?
|
|
@@ -708,10 +593,10 @@ command :remove do |c|
|
|
|
708
593
|
remove_tags.push($1.strip)
|
|
709
594
|
end
|
|
710
595
|
}
|
|
711
|
-
|
|
596
|
+
console_log "Found #{remove_tags.length} tags in #{File.basename(path)}..."
|
|
712
597
|
end
|
|
713
598
|
|
|
714
|
-
|
|
599
|
+
exit_now! "No tags found in input, my work here is done" if remove_tags.empty?
|
|
715
600
|
|
|
716
601
|
files.each { |file|
|
|
717
602
|
tags = @jt.post_tags(file)
|
|
@@ -726,12 +611,12 @@ command :remove do |c|
|
|
|
726
611
|
}
|
|
727
612
|
unless global_options[:t]
|
|
728
613
|
@jt.update_file_tags(file, tags)
|
|
729
|
-
|
|
614
|
+
console_log "Updated tags for #{file}", :log => true
|
|
730
615
|
end
|
|
731
616
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
617
|
+
console_log
|
|
618
|
+
console_log File.basename(file) + ":"
|
|
619
|
+
output_tags(tags, { :format => options[:format], :filename => file })
|
|
735
620
|
}
|
|
736
621
|
end
|
|
737
622
|
end
|
|
@@ -744,14 +629,17 @@ command :tag do |c|
|
|
|
744
629
|
c.flag %i[f format], :must_match => /^(csv|list|yaml|json|plist|complete)$/, :type => String, :default_value => "yaml"
|
|
745
630
|
|
|
746
631
|
c.action do |global_options, options, args|
|
|
747
|
-
if @piped_content && !@piped_content.file_list?
|
|
632
|
+
if @piped_content && !@piped_content.file_list?
|
|
748
633
|
suggestions = @jt.suggest(@piped_content)
|
|
749
634
|
if args.length > 0
|
|
750
|
-
|
|
751
|
-
|
|
635
|
+
console_log
|
|
636
|
+
console_log "STDIN:", :err => true
|
|
637
|
+
end
|
|
638
|
+
if options[:format] == "complete"
|
|
639
|
+
@jt.update_file_tags(@piped_content, suggestions, true)
|
|
640
|
+
else
|
|
641
|
+
output_tags(suggestions, :format => options[:format], :filename => nil)
|
|
752
642
|
end
|
|
753
|
-
|
|
754
|
-
@util.output_tags(suggestions, :format => options[:format], :filename => nil)
|
|
755
643
|
end
|
|
756
644
|
|
|
757
645
|
files = []
|
|
@@ -760,7 +648,7 @@ command :tag do |c|
|
|
|
760
648
|
files.push(arg) if File.exist?(arg)
|
|
761
649
|
end
|
|
762
650
|
|
|
763
|
-
files.concat(@piped_content.split(
|
|
651
|
+
files.concat(@piped_content.split("\n")) if @piped_content && @piped_content.file_list?
|
|
764
652
|
|
|
765
653
|
files.each do |file|
|
|
766
654
|
if File.exist?(File.expand_path(file))
|
|
@@ -769,40 +657,61 @@ command :tag do |c|
|
|
|
769
657
|
|
|
770
658
|
unless global_options[:t]
|
|
771
659
|
if @jt.update_file_tags(file, suggestions)
|
|
772
|
-
|
|
773
|
-
|
|
660
|
+
console_log
|
|
661
|
+
console_log "Updated file #{file} with:", :log => true
|
|
774
662
|
else
|
|
775
|
-
|
|
776
|
-
|
|
663
|
+
console_log
|
|
664
|
+
console_log "Failed to update #{file} with:", :log => true
|
|
777
665
|
end
|
|
778
666
|
end
|
|
779
667
|
if !global_options[:s] || global_options[:t]
|
|
780
668
|
if args.length > 1
|
|
781
|
-
|
|
782
|
-
|
|
669
|
+
console_log
|
|
670
|
+
console_log File.basename(file) + ":", :err => true, :log => true
|
|
783
671
|
end
|
|
784
|
-
|
|
672
|
+
output_tags(suggestions, :format => options[:format], :filename => file)
|
|
785
673
|
end
|
|
786
674
|
suggestions = nil
|
|
787
675
|
else
|
|
788
|
-
raise
|
|
676
|
+
raise "No such file: #{file}"
|
|
789
677
|
end
|
|
790
678
|
end
|
|
791
679
|
end
|
|
792
680
|
end
|
|
793
681
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
682
|
+
def output_tags(tags, options)
|
|
683
|
+
format = options[:format] || "yaml"
|
|
684
|
+
print0 = options[:print0] || false
|
|
685
|
+
filename = options[:filename] || false
|
|
686
|
+
case format
|
|
687
|
+
when "list"
|
|
688
|
+
unless print0
|
|
689
|
+
console_log tags.join("\n")
|
|
690
|
+
else
|
|
691
|
+
console_log tags.map { |tag|
|
|
692
|
+
if tag.strip =~ /\b\s\b/
|
|
693
|
+
%Q{"#{tag.strip}"}
|
|
694
|
+
else
|
|
695
|
+
tag.strip
|
|
696
|
+
end
|
|
697
|
+
}.join(" ")
|
|
698
|
+
end
|
|
699
|
+
when "csv"
|
|
700
|
+
console_log tags.to_csv
|
|
701
|
+
when "json"
|
|
702
|
+
out = {}
|
|
703
|
+
out["tags"] = tags
|
|
704
|
+
out["path"] = filename if filename
|
|
705
|
+
console_log out.to_json
|
|
706
|
+
when "plist"
|
|
707
|
+
out = {}
|
|
708
|
+
out["path"] = filename if filename
|
|
709
|
+
console_log tags.to_plist
|
|
710
|
+
else
|
|
711
|
+
out = {}
|
|
712
|
+
options[:grouping] ||= "tags"
|
|
713
|
+
out[options[:grouping]] = tags
|
|
714
|
+
console_log out.to_yaml
|
|
806
715
|
end
|
|
807
716
|
end
|
|
808
717
|
|
|
@@ -817,43 +726,33 @@ end
|
|
|
817
726
|
# end
|
|
818
727
|
|
|
819
728
|
pre do |global, command, options, args|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
@
|
|
823
|
-
@util.config_target = if global[:config_dir]
|
|
824
|
-
File.expand_path(global[:config_dir])
|
|
825
|
-
else
|
|
826
|
-
File.expand_path("~/.config/jtag")
|
|
827
|
-
end
|
|
729
|
+
# Use skips_pre before a command to skip this block
|
|
730
|
+
# on that command only
|
|
731
|
+
@silent = global[:silent]
|
|
828
732
|
|
|
829
|
-
|
|
830
|
-
@util.silent = global[:debug] == 0 || global[:silent]
|
|
733
|
+
Signal.trap("PIPE", "EXIT")
|
|
831
734
|
|
|
832
|
-
|
|
833
|
-
logfile = File.open(File.join(Dir.tmpdir, "jtag_actions.log"), "a")
|
|
735
|
+
@logfile = File.open(File.join(Dir.tmpdir, "jtag_actions.log"), "a")
|
|
834
736
|
|
|
835
|
-
|
|
836
|
-
@util.log = Logger.new(logfile, shift_age = 7, shift_size = 1048576)
|
|
737
|
+
@log = Logger.new(@logfile, shift_age = 7, shift_size = 1048576)
|
|
837
738
|
|
|
838
|
-
#
|
|
839
|
-
|
|
739
|
+
# Check for old config directory and migrate if needed
|
|
740
|
+
migrate_old_config
|
|
840
741
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
@
|
|
844
|
-
@util.console_log "Missing config files written to #{@util.config_target}. Please check your configuration.", err: true, level: :error
|
|
742
|
+
unless config_files_complete?
|
|
743
|
+
write_config
|
|
744
|
+
console_log "Missing config files written to #{@config_target}. Please check your configuration.", { :err => true }
|
|
845
745
|
return false
|
|
846
746
|
end
|
|
847
747
|
|
|
848
|
-
configfile = File.
|
|
748
|
+
configfile = File.expand_path("~/.config/jtag/config.yml")
|
|
849
749
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
global[:config] = config.symbolize_keys
|
|
750
|
+
global[:config] = YAML::load(File.open(configfile, "r"))
|
|
751
|
+
global[:support] = File.expand_path("~/.config/jtag")
|
|
853
752
|
|
|
854
753
|
@piped_content = $stdin.read if $stdin.fcntl(Fcntl::F_GETFL, 0) == 0 || $stdin.stat.pipe?
|
|
855
754
|
|
|
856
|
-
@jt =
|
|
755
|
+
@jt = JTag.new(global[:support], global[:config])
|
|
857
756
|
|
|
858
757
|
true
|
|
859
758
|
end
|
|
@@ -865,12 +764,7 @@ post do |global, command, options, args|
|
|
|
865
764
|
end
|
|
866
765
|
|
|
867
766
|
on_error do |exception|
|
|
868
|
-
|
|
869
|
-
if exception.is_a?(NoValidFile) || exception.is_a?(FileNotFound) || exception.is_a?(NoTagsFound)
|
|
870
|
-
@util.console_log "#{@jt.color(:red)}#{exception.message}", err: true, level: :error
|
|
871
|
-
return false
|
|
872
|
-
end
|
|
873
|
-
|
|
767
|
+
# Error logic here
|
|
874
768
|
# return false to skip default error handling
|
|
875
769
|
true
|
|
876
770
|
end
|