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