jtag 0.1.18 → 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 +519 -359
- 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/lib/jtag/jekylltag.rb
CHANGED
@@ -1,267 +1,309 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
module JekyllTag
|
4
|
+
class JTag
|
5
|
+
attr_reader :default_post_location
|
6
|
+
attr_reader :tags_loc
|
7
|
+
attr_reader :post_extension
|
8
|
+
attr_accessor :tags_key
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
def initialize(support_dir, config, options = {})
|
11
|
+
config = config.symbolize_keys
|
12
|
+
@support = support_dir
|
13
|
+
@util = JekyllTag::Util.new
|
14
|
+
begin
|
15
|
+
if options[:files]
|
16
|
+
@tags_loc = :auto
|
17
|
+
files = options[:files].map(&:File.expand_path)
|
18
|
+
elsif config[:tags_location].to_s =~ /^(auto|posts?)$/
|
19
|
+
@tags_loc = :auto
|
20
|
+
files = Dir.glob(File.expand_path(File.join(config[:default_post_location], "**", "*.#{config[:post_extension]}")))
|
21
|
+
else
|
22
|
+
@tags_loc = File.expand_path(config[:tags_location])
|
23
|
+
@tags_loc.sub!(/^https?:\/\//, "")
|
24
|
+
files = nil
|
25
|
+
end
|
26
|
+
rescue StandardError => e
|
27
|
+
raise InvalidTagsFile, "Error reading configured tags locatio (#{e})"
|
28
|
+
end
|
29
|
+
@min_matches = config[:min_matches] || 2
|
30
|
+
@post_extension = config[:post_extension] || "md"
|
31
|
+
@post_extension.sub!(/^\./, "")
|
32
|
+
@tags_key = config[:tags_key] || "tags"
|
17
33
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
34
|
+
if config.has_key? :default_post_location
|
35
|
+
@default_post_location = File.expand_path(config[:default_post_location]) || false
|
36
|
+
else
|
37
|
+
str = ["#{color("yellow")}No #{color("boldyellow")}default_post_location#{color("yellow")} set.",
|
38
|
+
"If you commonly work on the same posts you can add the path and extension",
|
39
|
+
"to these key in ~/.jtag/config.yml. Then, if you don't specify files",
|
40
|
+
"to act on in a command, it will fall back to those. Nice!#{color("default")}"].join("\n")
|
41
|
+
@util.console_log str, err: true, level: :error
|
42
|
+
@default_post_location = false
|
43
|
+
end
|
44
|
+
@blacklistfile = File.join(@support, "blacklist.txt")
|
45
|
+
@blacklist = IO.read(@blacklistfile).split("\n") || []
|
46
|
+
@skipwords = IO.read(File.join(support_dir, "stopwords.txt")).split("\n") || []
|
47
|
+
remote_tags = tags(files: files)
|
48
|
+
@tags = {}
|
49
|
+
remote_tags.each do |tag|
|
50
|
+
@tags[tag.root_words] = tag if tag
|
51
|
+
end
|
52
|
+
synonyms.each { |k, v|
|
53
|
+
@tags[k.to_s.downcase] = v unless @blacklist.include?(k.to_s.downcase)
|
54
|
+
}
|
26
55
|
end
|
27
|
-
@blacklistfile = File.join(@support,'blacklist.txt')
|
28
|
-
@blacklist = IO.read(@blacklistfile).split("\n") || []
|
29
|
-
@skipwords = IO.read(File.join(support_dir,'stopwords.txt')).split("\n") || []
|
30
|
-
remote_tags = get_tags
|
31
|
-
@tags = {}
|
32
|
-
remote_tags.each {|tag| @tags[Text::PorterStemming.stem(tag).downcase] = tag if tag}
|
33
|
-
synonyms.each { |k,v|
|
34
|
-
@tags[k.to_s.downcase] = v unless @blacklist.include?(k.to_s.downcase)
|
35
|
-
}
|
36
|
-
end
|
37
56
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
57
|
+
def tags(options = {})
|
58
|
+
blacklisted = options[:blacklisted] || false
|
59
|
+
counts = options[:counts] || false
|
60
|
+
if options[:files]
|
61
|
+
tags = {}
|
62
|
+
tag_counts = {}
|
63
|
+
options[:files].each do |file|
|
64
|
+
tags["tags"] ||= []
|
65
|
+
|
66
|
+
file_tags = post_tags(File.expand_path(file))
|
67
|
+
file_tags.each do |tag|
|
68
|
+
tag_counts[tag] ||= 0
|
69
|
+
tag_counts[tag] += 1
|
70
|
+
tags["tags"].push(tag)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
tags["tags_count"] = []
|
74
|
+
tag_counts.each { |k, v| tags["tags_count"] << { "name" => k, "count" => v } }
|
75
|
+
elsif File.exist?(@tags_loc)
|
76
|
+
begin
|
77
|
+
tags = YAML.load_file(@tags_loc)
|
78
|
+
rescue LoadError
|
79
|
+
tags = JSON.parse(IO.read(@tags_loc))
|
80
|
+
rescue
|
81
|
+
raise InvalidTagsFile, "Tags file is not in YAML or JSON format."
|
82
|
+
end
|
55
83
|
else
|
56
|
-
|
57
|
-
|
84
|
+
host, path = @tags_loc.match(/^([^\/]+)(\/.*)/)[1, 2]
|
85
|
+
tags = ""
|
86
|
+
# http = Net::HTTP.new(host, 80)
|
87
|
+
# http.start do |http|
|
88
|
+
# request = Net::HTTP::Get.new(path)
|
89
|
+
# response = http.request(request)
|
90
|
+
# response.value
|
91
|
+
# tags = response.body
|
92
|
+
# end
|
93
|
+
tags = `curl -sSL "#{@tags_loc}"`
|
94
|
+
tags = YAML.load(tags) rescue JSON.parse(tags)
|
95
|
+
raise InvalidTagsFile, "Tags file is not in YAML or JSON format." unless tags
|
96
|
+
end
|
97
|
+
if tags && tags.key?("tags")
|
98
|
+
if counts && tags.key?("tags_count")
|
99
|
+
tags["tags_count"].delete_if { |tag| !tag || @blacklist.include?(tag["name"].downcase) } unless blacklisted
|
100
|
+
return tags["tags_count"]
|
101
|
+
else
|
102
|
+
tags["tags"].delete_if { |tag| !tag || @blacklist.include?(tag.downcase) } unless blacklisted
|
103
|
+
return tags["tags"]
|
58
104
|
end
|
59
|
-
|
105
|
+
else
|
106
|
+
return false
|
60
107
|
end
|
61
|
-
else
|
62
|
-
return false
|
63
108
|
end
|
64
|
-
end
|
65
109
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
110
|
+
def synonyms
|
111
|
+
if File.exist?(File.join(@support, "synonyms.yml"))
|
112
|
+
syn = YAML::load(File.open(File.join(@support, "synonyms.yml")))
|
113
|
+
compiled = {}
|
114
|
+
syn.each { |k, v|
|
115
|
+
v.each { |synonym|
|
116
|
+
compiled[synonym] = k
|
117
|
+
}
|
73
118
|
}
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
compiled
|
79
|
-
end
|
80
|
-
|
81
|
-
def split_post(file, piped = false)
|
82
|
-
input = piped ? file : IO.read(file)
|
83
|
-
# Check to see if it's a full post with YAML headers
|
84
|
-
post_parts = input.split(/^[\.\-]{3}\s*$/)
|
85
|
-
if post_parts.length >= 3
|
86
|
-
after = post_parts[2].strip
|
87
|
-
yaml = YAML::load(input)
|
88
|
-
else
|
89
|
-
after = input
|
90
|
-
yaml = YAML::load("--- title: #{File.basename(file)}")
|
119
|
+
else
|
120
|
+
return false
|
121
|
+
end
|
122
|
+
compiled
|
91
123
|
end
|
92
|
-
[yaml, after]
|
93
|
-
end
|
94
124
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
125
|
+
def split_post(file, piped = false)
|
126
|
+
input = piped ? file : IO.read(file)
|
127
|
+
# Check to see if it's a full post with YAML headers
|
128
|
+
post_parts = input.split(/^[\.\-]{3}\s*$/)
|
129
|
+
if post_parts.length >= 3
|
130
|
+
after = post_parts[2].strip
|
131
|
+
yaml = YAML::load(input)
|
132
|
+
else
|
133
|
+
after = input
|
134
|
+
yaml = YAML::load("--- title: #{File.basename(file)}")
|
135
|
+
end
|
136
|
+
[yaml, after]
|
102
137
|
end
|
103
|
-
end
|
104
138
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
139
|
+
def post_tags(file, piped = false)
|
140
|
+
begin
|
141
|
+
input = piped ? file.strip : IO.read(File.expand_path(file))
|
142
|
+
yaml = YAML::load(input)
|
143
|
+
return yaml[@tags_key] || []
|
144
|
+
rescue
|
145
|
+
return []
|
112
146
|
end
|
113
|
-
|
114
|
-
return false unless post_has_tag
|
115
|
-
current_tags.push(merged)
|
116
|
-
current_tags.uniq!
|
117
|
-
current_tags.sort
|
118
|
-
end
|
147
|
+
end
|
119
148
|
|
149
|
+
def merge_tags(tags, merged, file)
|
150
|
+
current_tags = post_tags(file)
|
151
|
+
post_has_tag = false
|
152
|
+
tags.each { |tag|
|
153
|
+
if current_tags.include?(tag)
|
154
|
+
current_tags.delete(tag)
|
155
|
+
post_has_tag = true
|
156
|
+
end
|
157
|
+
}
|
158
|
+
return false unless post_has_tag
|
159
|
+
current_tags.push(merged)
|
160
|
+
current_tags.uniq!
|
161
|
+
current_tags.sort
|
162
|
+
end
|
120
163
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
164
|
+
def suggest(input)
|
165
|
+
parts = input.split(/^[\.\-]{3}\s*$/)
|
166
|
+
if parts.length >= 2
|
167
|
+
begin
|
168
|
+
yaml = YAML::load(parts[1])
|
169
|
+
current_tags = yaml[@tags_key] || []
|
170
|
+
title = yaml["title"] || ""
|
171
|
+
rescue
|
172
|
+
current_tags = []
|
173
|
+
title = ""
|
174
|
+
end
|
175
|
+
else
|
129
176
|
current_tags = []
|
130
177
|
title = ""
|
131
178
|
end
|
132
|
-
|
133
|
-
|
134
|
-
|
179
|
+
@content = (title + parts[2..-1].join(" ")).strip_all.strip_urls rescue input.strip_all.strip_urls
|
180
|
+
@words = split_words
|
181
|
+
@auto_tags = []
|
182
|
+
populate_auto_tags
|
183
|
+
|
184
|
+
@auto_tags.concat(current_tags).uniq
|
135
185
|
end
|
136
|
-
@content = (title + parts[2..-1].join(" ")).strip_all.strip_urls rescue input.strip_all.strip_urls
|
137
|
-
@words = split_words
|
138
|
-
@auto_tags = []
|
139
|
-
populate_auto_tags
|
140
186
|
|
141
|
-
|
142
|
-
|
187
|
+
def split_words
|
188
|
+
@content.gsub(/([\/\\]|\s+)/, " ").gsub(/[^a-z0-9\s-]/i, "").split(" ").delete_if { |word|
|
189
|
+
word =~ /^[^a-z]+$/ || word.length < 4
|
190
|
+
}.map! { |word|
|
191
|
+
Text::PorterStemming.stem(word).downcase
|
192
|
+
}.delete_if { |word|
|
193
|
+
@skipwords.include?(word) && !@tags.keys.include?(word)
|
194
|
+
}
|
195
|
+
end
|
143
196
|
|
144
|
-
|
145
|
-
|
146
|
-
word
|
147
|
-
|
148
|
-
Text::PorterStemming.stem(word).downcase
|
149
|
-
}.delete_if{ |word|
|
150
|
-
@skipwords.include?(word) && !@tags.keys.include?(word)
|
151
|
-
}
|
152
|
-
end
|
197
|
+
def populate_auto_tags
|
198
|
+
freqs = Hash.new(0)
|
199
|
+
@words.each { |word| freqs[word] += 1 }
|
200
|
+
freqs.delete_if { |k, v| v < @min_matches }
|
153
201
|
|
154
|
-
|
155
|
-
freqs = Hash.new(0)
|
156
|
-
@words.each { |word| freqs[word] += 1 }
|
157
|
-
freqs.delete_if {|k,v| v < @min_matches }
|
202
|
+
return [] if freqs.empty?
|
158
203
|
|
159
|
-
|
204
|
+
freqs.sort_by { |k, v| [v * -1, k] }.each { |word|
|
205
|
+
index = @tags.keys.index(word[0])
|
206
|
+
unless index.nil? || @blacklist.include?(@tags.keys[index])
|
207
|
+
@auto_tags.push(@tags[@tags.keys[index]]) unless index.nil?
|
208
|
+
end
|
209
|
+
}
|
160
210
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
211
|
+
@tags.each { |k, v|
|
212
|
+
occurrences = @content.scan(/\b#{k}\b/i)
|
213
|
+
if occurrences.count >= @min_matches
|
214
|
+
@auto_tags.push(v)
|
215
|
+
end
|
216
|
+
}
|
217
|
+
end
|
167
218
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
219
|
+
def blacklist(tags)
|
220
|
+
tags.each { |word|
|
221
|
+
@blacklist.push(word.downcase)
|
222
|
+
}
|
223
|
+
File.open(@blacklistfile, "w+") do |f|
|
224
|
+
f.puts @blacklist.uniq.sort.join("\n")
|
172
225
|
end
|
173
|
-
}
|
174
|
-
end
|
175
|
-
|
176
|
-
def blacklist(tags)
|
177
|
-
tags.each {|word|
|
178
|
-
@blacklist.push(word.downcase)
|
179
|
-
}
|
180
|
-
File.open(@blacklistfile,'w+') do |f|
|
181
|
-
f.puts @blacklist.uniq.sort.join("\n")
|
182
226
|
end
|
183
|
-
end
|
184
227
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
228
|
+
def unblacklist(tags)
|
229
|
+
tags.each { |word|
|
230
|
+
@blacklist.delete_if { |x| x == word }
|
231
|
+
}
|
232
|
+
File.open(@blacklistfile, "w+") do |f|
|
233
|
+
f.puts @blacklist.uniq.sort.join("\n")
|
234
|
+
end
|
191
235
|
end
|
192
|
-
end
|
193
236
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
237
|
+
def update_file_tags(file, tags, piped = false)
|
238
|
+
begin
|
239
|
+
if File.exist?(file) || piped
|
240
|
+
yaml, after = split_post(file, piped)
|
241
|
+
yaml[@tags_key] = tags
|
242
|
+
if piped
|
243
|
+
puts yaml.to_yaml
|
244
|
+
puts "---"
|
245
|
+
puts after
|
246
|
+
else
|
247
|
+
File.open(file, "w+") do |f|
|
248
|
+
f.puts yaml.to_yaml
|
249
|
+
f.puts "---"
|
250
|
+
f.puts after
|
251
|
+
end
|
208
252
|
end
|
253
|
+
else
|
254
|
+
raise "File does not exist: #{file}"
|
209
255
|
end
|
210
|
-
|
211
|
-
|
256
|
+
return true
|
257
|
+
rescue Exception => e
|
258
|
+
raise e
|
259
|
+
return false
|
212
260
|
end
|
213
|
-
return true
|
214
|
-
rescue Exception => e
|
215
|
-
raise e
|
216
|
-
return false
|
217
261
|
end
|
218
|
-
end
|
219
262
|
|
220
|
-
|
221
|
-
|
222
|
-
def color(name)
|
263
|
+
def color(name)
|
223
264
|
color = {}
|
224
|
-
color[
|
225
|
-
color[
|
226
|
-
color[
|
227
|
-
color[
|
228
|
-
color[
|
229
|
-
color[
|
230
|
-
color[
|
231
|
-
color[
|
232
|
-
color[
|
233
|
-
color[
|
234
|
-
color[
|
235
|
-
color[
|
236
|
-
color[
|
237
|
-
color[
|
238
|
-
color[
|
239
|
-
color[
|
240
|
-
color[
|
241
|
-
color[
|
242
|
-
color[
|
243
|
-
color[
|
244
|
-
color[
|
245
|
-
color[
|
246
|
-
color[
|
247
|
-
color[
|
248
|
-
color[
|
249
|
-
color[
|
250
|
-
color[
|
251
|
-
color[
|
252
|
-
color[
|
253
|
-
color[
|
254
|
-
color[
|
255
|
-
color[
|
256
|
-
color[
|
257
|
-
color[
|
258
|
-
color[
|
259
|
-
color[
|
260
|
-
color[
|
261
|
-
color[
|
262
|
-
color[
|
263
|
-
color[
|
264
|
-
color[
|
265
|
-
color[name]
|
265
|
+
color["black"] = "\033[0;30m"
|
266
|
+
color["red"] = "\033[0;31m"
|
267
|
+
color["green"] = "\033[0;32m"
|
268
|
+
color["yellow"] = "\033[0;33m"
|
269
|
+
color["blue"] = "\033[0;34m"
|
270
|
+
color["magenta"] = "\033[0;35m"
|
271
|
+
color["cyan"] = "\033[0;36m"
|
272
|
+
color["white"] = "\033[0;37m"
|
273
|
+
color["bgblack"] = "\033[0;40m"
|
274
|
+
color["bgred"] = "\033[0;41m"
|
275
|
+
color["bggreen"] = "\033[0;42m"
|
276
|
+
color["bgyellow"] = "\033[0;43m"
|
277
|
+
color["bgblue"] = "\033[0;44m"
|
278
|
+
color["bgmagenta"] = "\033[0;45m"
|
279
|
+
color["bgcyan"] = "\033[0;46m"
|
280
|
+
color["bgwhite"] = "\033[0;47m"
|
281
|
+
color["boldblack"] = "\033[1;30m"
|
282
|
+
color["boldred"] = "\033[1;31m"
|
283
|
+
color["boldgreen"] = "\033[1;32m"
|
284
|
+
color["boldyellow"] = "\033[1;33m"
|
285
|
+
color["boldblue"] = "\033[1;34m"
|
286
|
+
color["boldmagenta"] = "\033[1;35m"
|
287
|
+
color["boldcyan"] = "\033[1;36m"
|
288
|
+
color["boldwhite"] = "\033[1;37m"
|
289
|
+
color["boldbgblack"] = "\033[1;40m"
|
290
|
+
color["boldbgred"] = "\033[1;41m"
|
291
|
+
color["boldbggreen"] = "\033[1;42m"
|
292
|
+
color["boldbgyellow"] = "\033[1;43m"
|
293
|
+
color["boldbgblue"] = "\033[1;44m"
|
294
|
+
color["boldbgmagenta"] = "\033[1;45m"
|
295
|
+
color["boldbgcyan"] = "\033[1;46m"
|
296
|
+
color["boldbgwhite"] = "\033[1;47m"
|
297
|
+
color["default"] = "\033[0;39m"
|
298
|
+
color["warning"] = color["yellow"]
|
299
|
+
color["warningb"] = color["boldyellow"]
|
300
|
+
color["success"] = color["green"]
|
301
|
+
color["successb"] = color["boldgreen"]
|
302
|
+
color["neutral"] = color["white"]
|
303
|
+
color["neutralb"] = color["boldwhite"]
|
304
|
+
color["info"] = color["cyan"]
|
305
|
+
color["infob"] = color["boldcyan"]
|
306
|
+
color[name.to_s]
|
307
|
+
end
|
266
308
|
end
|
267
309
|
end
|