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