stickyflag 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.
- data/.gitignore +7 -0
- data/.rspec +4 -0
- data/.simplecov +9 -0
- data/.travis.yml +13 -0
- data/Gemfile +2 -0
- data/LICENSE.md +7 -0
- data/README.md +49 -0
- data/Rakefile +19 -0
- data/TODO.md +3 -0
- data/bin/stickyflag +6 -0
- data/features/clear.feature +14 -0
- data/features/clear_quietly.feature +23 -0
- data/features/configuration.feature +14 -0
- data/features/get.feature +14 -0
- data/features/get_quietly.feature +23 -0
- data/features/set.feature +14 -0
- data/features/set_quietly.feature +22 -0
- data/features/step_definitions/configuration_steps.rb +31 -0
- data/features/step_definitions/database_steps.rb +41 -0
- data/features/step_definitions/pending_steps.rb +5 -0
- data/features/step_definitions/tag_steps.rb +62 -0
- data/features/support/cukegem.rb +82 -0
- data/features/support/env.rb +37 -0
- data/features/tags.feature +18 -0
- data/features/unset.feature +14 -0
- data/features/unset_quietly.feature +23 -0
- data/lib/stickyflag/configuration.rb +66 -0
- data/lib/stickyflag/database.rb +162 -0
- data/lib/stickyflag/external_cmds.rb +64 -0
- data/lib/stickyflag/patches/tempfile_encoding.rb +22 -0
- data/lib/stickyflag/patches/tmpnam.rb +38 -0
- data/lib/stickyflag/paths.rb +47 -0
- data/lib/stickyflag/tag_factory.rb +108 -0
- data/lib/stickyflag/tags/c.rb +25 -0
- data/lib/stickyflag/tags/mmd.rb +168 -0
- data/lib/stickyflag/tags/pdf.rb +119 -0
- data/lib/stickyflag/tags/png.rb +61 -0
- data/lib/stickyflag/tags/source_code.rb +99 -0
- data/lib/stickyflag/tags/tex.rb +25 -0
- data/lib/stickyflag/version.rb +12 -0
- data/lib/stickyflag.rb +253 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/stickyflag/configuration_spec.rb +132 -0
- data/spec/stickyflag/database_spec.rb +331 -0
- data/spec/stickyflag/external_cmds_spec.rb +175 -0
- data/spec/stickyflag/patches/tempfile_encoding_spec.rb +26 -0
- data/spec/stickyflag/patches/tmpnam_spec.rb +35 -0
- data/spec/stickyflag/paths_spec.rb +29 -0
- data/spec/stickyflag/tag_factory_spec.rb +185 -0
- data/spec/stickyflag/tags/c_spec.rb +14 -0
- data/spec/stickyflag/tags/mmd_spec.rb +40 -0
- data/spec/stickyflag/tags/pdf_spec.rb +39 -0
- data/spec/stickyflag/tags/png_spec.rb +6 -0
- data/spec/stickyflag/tags/tex_spec.rb +6 -0
- data/spec/stickyflag_spec.rb +482 -0
- data/spec/support/examples/c_all_comments.c +3 -0
- data/spec/support/examples/c_no_tags.c +5 -0
- data/spec/support/examples/c_with_tag.c +6 -0
- data/spec/support/examples/mmd_all_meta.mmd +6 -0
- data/spec/support/examples/mmd_crazy_keys.mmd +8 -0
- data/spec/support/examples/mmd_crazy_tags.mmd +9 -0
- data/spec/support/examples/mmd_no_tags.mmd +1 -0
- data/spec/support/examples/mmd_with_tag.mmd +3 -0
- data/spec/support/examples/pdf_no_tags.pdf +0 -0
- data/spec/support/examples/pdf_with_tag.pdf +0 -0
- data/spec/support/examples/png_no_tags.png +0 -0
- data/spec/support/examples/png_with_tag.png +0 -0
- data/spec/support/examples/tex_no_tags.tex +10 -0
- data/spec/support/examples/tex_with_tag.tex +11 -0
- data/spec/support/examples/untaggable.txt +0 -0
- data/spec/support/examples.rb +32 -0
- data/spec/support/run_with_args.rb +36 -0
- data/spec/support/silence_stream.rb +12 -0
- data/spec/support/tag_handler_behavior.rb +125 -0
- data/stickyflag.gemspec +48 -0
- metadata +399 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'thor'
|
3
|
+
require 'open3'
|
4
|
+
require 'stickyflag/patches/tempfile_encoding'
|
5
|
+
|
6
|
+
module StickyFlag
|
7
|
+
module Tags
|
8
|
+
module PDF
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def get(file_name, pdftk_path = 'pdftk')
|
12
|
+
stdout_str = ''
|
13
|
+
stderr_str = ''
|
14
|
+
|
15
|
+
begin
|
16
|
+
Open3.popen3(pdftk_path, file_name.to_s, 'dump_data_utf8') do |i, o, e, t|
|
17
|
+
out_reader = Thread.new { o.read }
|
18
|
+
err_reader = Thread.new { e.read }
|
19
|
+
i.close
|
20
|
+
stdout_str = out_reader.value
|
21
|
+
stderr_str = err_reader.value
|
22
|
+
|
23
|
+
stdout_str.force_encoding("UTF-8") if RUBY_VERSION >= "1.9.0"
|
24
|
+
stderr_str.force_encoding("UTF-8") if RUBY_VERSION >= "1.9.0"
|
25
|
+
end
|
26
|
+
rescue Exception
|
27
|
+
raise Thor::Error.new("ERROR: Failed to get tags for #{file_name}; pdftk call failed")
|
28
|
+
end
|
29
|
+
if stderr_str.start_with?("Error: ") || stderr_str.include?("Errno::ENOENT")
|
30
|
+
raise Thor::Error.new("ERROR: Failed to get tags for #{file_name}; pdftk call failed")
|
31
|
+
end
|
32
|
+
|
33
|
+
# More than one of these shouldn't be possible, but try to recover if
|
34
|
+
# it somehow happens.
|
35
|
+
matches = stdout_str.scan(/InfoKey: X-StickyFlag-Flags\nInfoValue: (.*?)\n/)
|
36
|
+
return [] if matches.empty?
|
37
|
+
|
38
|
+
tags = []
|
39
|
+
|
40
|
+
matches.each do |m|
|
41
|
+
match_string = m[0]
|
42
|
+
match_tags = match_string.split(',').map { |t| t.empty? ? nil : t.strip }.compact
|
43
|
+
next if match_tags.empty?
|
44
|
+
|
45
|
+
tags.concat(match_tags)
|
46
|
+
end
|
47
|
+
|
48
|
+
tags
|
49
|
+
end
|
50
|
+
|
51
|
+
def set(file_name, tag, pdftk_path = 'pdftk')
|
52
|
+
tags = get(file_name, pdftk_path)
|
53
|
+
return if tags.include? tag
|
54
|
+
|
55
|
+
tags << tag
|
56
|
+
|
57
|
+
info = Tempfile.new_with_encoding ['sfpdftag', '.txt']
|
58
|
+
begin
|
59
|
+
info.write("InfoKey: X-StickyFlag-Flags\n")
|
60
|
+
info.write("InfoValue: #{tags.join(', ')}\n")
|
61
|
+
info.close
|
62
|
+
|
63
|
+
outpath = File.tmpnam('.pdf')
|
64
|
+
ret = system(pdftk_path, file_name.to_s, 'update_info', info.path, 'output', outpath)
|
65
|
+
unless ret == true
|
66
|
+
raise Thor::Error.new("ERROR: Failed to set tag for #{file_name}; pdftk call failed")
|
67
|
+
end
|
68
|
+
|
69
|
+
FileUtils.mv outpath, file_name
|
70
|
+
ensure
|
71
|
+
info.unlink
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def unset(file_name, tag, pdftk_path = 'pdftk')
|
76
|
+
tags = get(file_name, pdftk_path)
|
77
|
+
return unless tags.include? tag
|
78
|
+
|
79
|
+
tags.delete(tag)
|
80
|
+
|
81
|
+
info = Tempfile.new_with_encoding ['sfpdftag', '.txt']
|
82
|
+
begin
|
83
|
+
info.write("InfoKey: X-StickyFlag-Flags\n")
|
84
|
+
info.write("InfoValue: #{tags.join(', ')}\n")
|
85
|
+
info.close
|
86
|
+
|
87
|
+
outpath = File.tmpnam('.pdf')
|
88
|
+
ret = system(pdftk_path, file_name.to_s, 'update_info', info.path, 'output', outpath)
|
89
|
+
unless ret == true
|
90
|
+
raise Thor::Error.new("ERROR: Failed to unset tag for #{file_name}; pdftk call failed")
|
91
|
+
end
|
92
|
+
|
93
|
+
FileUtils.mv outpath, file_name
|
94
|
+
ensure
|
95
|
+
info.unlink
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def clear(file_name, pdftk_path = 'pdftk')
|
100
|
+
info = Tempfile.new_with_encoding ['sfpdftag', '.txt']
|
101
|
+
begin
|
102
|
+
info.write("InfoKey: X-StickyFlag-Flags\n")
|
103
|
+
info.write("InfoValue: \n")
|
104
|
+
info.close
|
105
|
+
|
106
|
+
outpath = File.tmpnam('.pdf')
|
107
|
+
ret = system(pdftk_path, file_name.to_s, 'update_info', info.path, 'output', outpath)
|
108
|
+
unless ret == true
|
109
|
+
raise Thor::Error.new("ERROR: Failed to clear tags for #{file_name}; pdftk call failed")
|
110
|
+
end
|
111
|
+
|
112
|
+
FileUtils.mv outpath, file_name
|
113
|
+
ensure
|
114
|
+
info.unlink
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'thor'
|
3
|
+
begin
|
4
|
+
require 'oily_png'
|
5
|
+
rescue LoadError
|
6
|
+
require 'chunky_png'
|
7
|
+
end
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
module StickyFlag
|
11
|
+
module Tags
|
12
|
+
module PNG
|
13
|
+
module_function
|
14
|
+
|
15
|
+
def get(file_name)
|
16
|
+
image = ChunkyPNG::Image.from_file(file_name)
|
17
|
+
tag_string = image.metadata['X-StickyFlag-Flags']
|
18
|
+
return [] if tag_string.nil?
|
19
|
+
tag_string.force_encoding('UTF-8') if RUBY_VERSION >= "1.9.0"
|
20
|
+
tag_string.split(',').map { |t| t.empty? ? nil : t.strip }.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
def set(file_name, tag)
|
24
|
+
tags = get(file_name)
|
25
|
+
return if tags.include? tag
|
26
|
+
|
27
|
+
tags << tag
|
28
|
+
|
29
|
+
image = ChunkyPNG::Image.from_file(file_name)
|
30
|
+
image.metadata['X-StickyFlag-Flags'] = tags.join(', ')
|
31
|
+
|
32
|
+
outpath = File.tmpnam('.png')
|
33
|
+
image.save(outpath)
|
34
|
+
FileUtils.mv(outpath, file_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def unset(file_name, tag)
|
38
|
+
tags = get(file_name)
|
39
|
+
return unless tags.include? tag
|
40
|
+
|
41
|
+
tags.delete(tag)
|
42
|
+
|
43
|
+
image = ChunkyPNG::Image.from_file(file_name)
|
44
|
+
image.metadata['X-StickyFlag-Flags'] = tags.join(', ')
|
45
|
+
|
46
|
+
outpath = File.tmpnam('.png')
|
47
|
+
image.save(outpath)
|
48
|
+
FileUtils.mv(outpath, file_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def clear(file_name)
|
52
|
+
image = ChunkyPNG::Image.from_file(file_name)
|
53
|
+
image.metadata.delete('X-StickyFlag-Flags')
|
54
|
+
|
55
|
+
outpath = File.tmpnam('.png')
|
56
|
+
image.save(outpath)
|
57
|
+
FileUtils.mv(outpath, file_name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module StickyFlag
|
4
|
+
module Tags
|
5
|
+
module SourceCode
|
6
|
+
module_function
|
7
|
+
|
8
|
+
#
|
9
|
+
# Specify:
|
10
|
+
# comment_line_regex: matches any line of comments at the start of the source code (when this doesn't match, we'll stop looking)
|
11
|
+
#
|
12
|
+
# tag_line_regex: matches the specific line that contains the tags, the one group in this regex should be the comma-separated list of tags
|
13
|
+
#
|
14
|
+
# tag_line_for(tags): convert the tag string (already a string!) into the output line we should print
|
15
|
+
#
|
16
|
+
|
17
|
+
def get(file_name)
|
18
|
+
File.open(file_name, 'r:UTF-8').each_line do |line|
|
19
|
+
return [] unless line =~ comment_line_regex
|
20
|
+
|
21
|
+
m = line.match(tag_line_regex)
|
22
|
+
if m
|
23
|
+
tag_string = m[1]
|
24
|
+
return [] if tag_string.nil? || tag_string.empty?
|
25
|
+
return tag_string.split(',').map { |t| t.empty? ? nil : t.strip }.compact
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
[]
|
30
|
+
end
|
31
|
+
|
32
|
+
def set(file_name, tag)
|
33
|
+
tags = get(file_name)
|
34
|
+
return if tags.include? tag
|
35
|
+
|
36
|
+
tags << tag
|
37
|
+
set_tags = false
|
38
|
+
|
39
|
+
outpath = File.tmpnam
|
40
|
+
File.open(outpath, 'w:UTF-8') do |outfile|
|
41
|
+
File.open(file_name, 'r:UTF-8').each_line do |line|
|
42
|
+
if line !~ comment_line_regex
|
43
|
+
if set_tags == false
|
44
|
+
# We haven't set the tags yet, but the current line does *not*
|
45
|
+
# match the comment line regex. Add the tags line as a new line
|
46
|
+
# at the end of the comment block.
|
47
|
+
outfile.puts tag_line_for(tags.join(', '))
|
48
|
+
set_tags = true
|
49
|
+
else
|
50
|
+
outfile.puts line
|
51
|
+
end
|
52
|
+
else
|
53
|
+
if line =~ tag_line_regex
|
54
|
+
# Replace the old tag line with the new tag line
|
55
|
+
outfile.puts tag_line_for(tags.join(', '))
|
56
|
+
else
|
57
|
+
outfile.puts line
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
FileUtils.mv(outpath, file_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def unset(file_name, tag)
|
67
|
+
tags = get(file_name)
|
68
|
+
return unless tags.include? tag
|
69
|
+
|
70
|
+
tags.delete(tag)
|
71
|
+
|
72
|
+
outpath = File.tmpnam
|
73
|
+
File.open(outpath, 'w:UTF-8') do |outfile|
|
74
|
+
File.open(file_name, 'r:UTF-8').each_line do |line|
|
75
|
+
if line =~ tag_line_regex
|
76
|
+
outfile.puts tag_line_for(tags.join(', '))
|
77
|
+
else
|
78
|
+
outfile.puts line
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
FileUtils.mv(outpath, file_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def clear(file_name)
|
87
|
+
outpath = File.tmpnam
|
88
|
+
File.open(outpath, 'w:UTF-8') do |outfile|
|
89
|
+
File.open(file_name, 'r:UTF-8').each_line do |line|
|
90
|
+
next if line =~ tag_line_regex
|
91
|
+
outfile.puts line
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
FileUtils.mv(outpath, file_name)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'stickyflag/tags/source_code'
|
3
|
+
|
4
|
+
module StickyFlag
|
5
|
+
module Tags
|
6
|
+
module TeX
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def comment_line_regex
|
10
|
+
/\A% .*/
|
11
|
+
end
|
12
|
+
|
13
|
+
def tag_line_regex
|
14
|
+
/\A% SF_TAGS = (.*)/
|
15
|
+
end
|
16
|
+
|
17
|
+
def tag_line_for(str)
|
18
|
+
"% SF_TAGS = #{str}"
|
19
|
+
end
|
20
|
+
|
21
|
+
include SourceCode
|
22
|
+
module_function :get, :set, :unset, :clear
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/stickyflag.rb
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'thor'
|
5
|
+
require 'backports'
|
6
|
+
|
7
|
+
require 'stickyflag/version'
|
8
|
+
|
9
|
+
require 'stickyflag/patches/tempfile_encoding'
|
10
|
+
require 'stickyflag/patches/tmpnam.rb'
|
11
|
+
|
12
|
+
require 'stickyflag/tags/source_code'
|
13
|
+
require 'stickyflag/tags/c'
|
14
|
+
require 'stickyflag/tags/mmd'
|
15
|
+
require 'stickyflag/tags/pdf'
|
16
|
+
require 'stickyflag/tags/png'
|
17
|
+
require 'stickyflag/tags/tex'
|
18
|
+
|
19
|
+
require 'stickyflag/configuration'
|
20
|
+
require 'stickyflag/paths'
|
21
|
+
require 'stickyflag/external_cmds'
|
22
|
+
require 'stickyflag/database'
|
23
|
+
require 'stickyflag/tag_factory'
|
24
|
+
|
25
|
+
module StickyFlag
|
26
|
+
class ThorApp < Thor
|
27
|
+
include Thor::Actions
|
28
|
+
class_option :color, :required => false, :default => true,
|
29
|
+
:desc => 'if true, print our status messages in color', :type => :boolean
|
30
|
+
class_option :force, :requires => false, :default => false, :aliases => '-f',
|
31
|
+
:desc => 'do not print diagnostic messages for missing files or empty tags on multiple-file operations'
|
32
|
+
class_option :quiet, :required => false, :default => false, :aliases => '-q',
|
33
|
+
:desc => 'if true, print only essential output to the console (skip empty tags, diagnostics)'
|
34
|
+
|
35
|
+
include Paths
|
36
|
+
include Configuration
|
37
|
+
include ExternalCmds
|
38
|
+
include TagFactory
|
39
|
+
include Database
|
40
|
+
|
41
|
+
def initialize(*args)
|
42
|
+
super
|
43
|
+
|
44
|
+
if options.color?
|
45
|
+
self.shell = Thor::Shell::Color.new
|
46
|
+
end
|
47
|
+
|
48
|
+
load_config!
|
49
|
+
find_external_cmds
|
50
|
+
load_database
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'config', "display and set configuration parameters"
|
54
|
+
long_desc <<-LONGDESC
|
55
|
+
`stickyflag config` allows you to set persistent configuration parameters
|
56
|
+
for StickyFlag.
|
57
|
+
|
58
|
+
With only --key <key> specified, display the current value for the given key.
|
59
|
+
|
60
|
+
With --key <key> <value>, set the value for the key.
|
61
|
+
|
62
|
+
With the --list option, list all available configuration parameters and
|
63
|
+
their current values.
|
64
|
+
LONGDESC
|
65
|
+
method_option :key, :aliases => '-k', :required => false,
|
66
|
+
:desc => 'the configuration key to set', :type => :string
|
67
|
+
method_option :list, :aliases => '-l', :default => false, :required => false,
|
68
|
+
:desc => 'list all available configuration options', :type => :boolean
|
69
|
+
method_option :reset, :default => false, :required => false,
|
70
|
+
:desc => 'reset *all* configuration settings to defaults', :type => :boolean
|
71
|
+
def config(value = nil)
|
72
|
+
if options.reset?
|
73
|
+
reset_config!
|
74
|
+
return
|
75
|
+
end
|
76
|
+
|
77
|
+
if options.list? || (options[:key].nil? && value.nil?)
|
78
|
+
dump_config
|
79
|
+
return
|
80
|
+
end
|
81
|
+
|
82
|
+
if options[:key].nil?
|
83
|
+
raise Thor::Error.new("ERROR: Cannot set a value without a key specified")
|
84
|
+
end
|
85
|
+
|
86
|
+
if value.nil?
|
87
|
+
value = get_config options[:key]
|
88
|
+
say "#{options[:key]}: '#{value}'"
|
89
|
+
return
|
90
|
+
end
|
91
|
+
|
92
|
+
set_config options[:key], value
|
93
|
+
say "'#{options[:key]}' set to '#{value}'" unless options.quiet?
|
94
|
+
|
95
|
+
save_config!
|
96
|
+
end
|
97
|
+
|
98
|
+
desc 'get [FILES]', "print the tags set for a set of files"
|
99
|
+
long_desc <<-LONGDESC
|
100
|
+
`stickyflag get` lets you look at the tags that have been applied to
|
101
|
+
a file or set of files.
|
102
|
+
LONGDESC
|
103
|
+
def get(*files)
|
104
|
+
if files.empty?
|
105
|
+
raise Thor::Error.new("stickyflag get requires at least 1 argument: \"stickyflag get [FILES]\"")
|
106
|
+
end
|
107
|
+
|
108
|
+
files.each do |file_name|
|
109
|
+
unless File.exist? file_name
|
110
|
+
say_status :error, "File #{file_name} does not exist", :red unless options.force? || options.quiet?
|
111
|
+
next
|
112
|
+
end
|
113
|
+
|
114
|
+
tags = get_tags_for file_name
|
115
|
+
if tags.empty?
|
116
|
+
say "#{file_name}: no tags" unless options.force? || options.quiet?
|
117
|
+
next
|
118
|
+
else
|
119
|
+
say "#{file_name}: #{tags.join(', ')}"
|
120
|
+
next
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
desc 'set [FILE] [TAG]', "set a tag for a file"
|
127
|
+
long_desc <<-LONGDESC
|
128
|
+
`stickyflag set` lets you add one particular tag to the tags present in a
|
129
|
+
given file. Specify the file you want to modify, and the tag you want to
|
130
|
+
add.
|
131
|
+
LONGDESC
|
132
|
+
def set(file_name, tag)
|
133
|
+
check_tag tag
|
134
|
+
|
135
|
+
unless File.exist? file_name
|
136
|
+
say_status :error, "File #{file_name} does not exist", :red unless options.quiet?
|
137
|
+
return
|
138
|
+
end
|
139
|
+
|
140
|
+
set_tag_for file_name, tag
|
141
|
+
|
142
|
+
tags = get_tags_for file_name
|
143
|
+
say_status :success, "New tags for #{file_name}: #{tags.join(', ')}", :green unless options.quiet?
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
desc 'unset [FILE] [TAG]', "remove a tag from a file"
|
148
|
+
long_desc <<-LONGDESC
|
149
|
+
`stickyflag unset` lets you delete one tag from the tags present in a file.
|
150
|
+
Specify the file you want to modify, and the tag you want to remove. This
|
151
|
+
action will fail if the tag is not set in the requested file.
|
152
|
+
LONGDESC
|
153
|
+
def unset(file_name, tag)
|
154
|
+
check_tag tag
|
155
|
+
|
156
|
+
unless File.exist? file_name
|
157
|
+
say_status :error, "File #{file_name} does not exist", :red unless options.quiet?
|
158
|
+
return
|
159
|
+
end
|
160
|
+
|
161
|
+
unset_tag_for file_name, tag
|
162
|
+
|
163
|
+
# Unsetting a tag might leave us with no tags at all, which makes this more
|
164
|
+
# complicated than the #set behavior
|
165
|
+
unless options.quiet?
|
166
|
+
tags = get_tags_for file_name
|
167
|
+
if tags.empty?
|
168
|
+
say_status :success, "New tags for #{file_name}: no tags", :green
|
169
|
+
else
|
170
|
+
say_status :success, "New tags for #{file_name}: #{tags.join(', ')}", :green
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
desc 'clear [FILES]', "remove all tags from a set of files"
|
177
|
+
long_desc <<-LONGDESC
|
178
|
+
`stickyflag clear` removes all tags that have been applied to the given
|
179
|
+
list of files.
|
180
|
+
LONGDESC
|
181
|
+
def clear(*files)
|
182
|
+
if files.empty?
|
183
|
+
raise Thor::Error.new("stickyflag clear requires at least 1 argument: \"stickyflag clear [FILES]\"")
|
184
|
+
end
|
185
|
+
|
186
|
+
files.each do |file_name|
|
187
|
+
unless File.exist? file_name
|
188
|
+
say_status :error, "File #{file_name} does not exist", :red unless options.force? || options.quiet?
|
189
|
+
return
|
190
|
+
end
|
191
|
+
|
192
|
+
clear_tags_for file_name
|
193
|
+
say_status :success, "Tags cleared for #{file_name}", :green unless options.quiet?
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
desc 'update', "update the tag database from the files on disk"
|
198
|
+
long_desc <<-LONGDESC
|
199
|
+
`stickyflag update` will read all files under the current directory (or
|
200
|
+
using the `root` configuration key) and refresh the database based on their
|
201
|
+
contents.
|
202
|
+
LONGDESC
|
203
|
+
def update
|
204
|
+
root = get_config(:root).strip
|
205
|
+
root = '.' if root.empty? || root.nil?
|
206
|
+
|
207
|
+
update_database_from_files root
|
208
|
+
end
|
209
|
+
|
210
|
+
desc 'tags', "show the list of currently used tags"
|
211
|
+
long_desc <<-LONGDESC
|
212
|
+
`stickyflag tags` shows the list of currently used tags as present in the
|
213
|
+
database. Note that this may be out of date with respect to the contents
|
214
|
+
of the disk, and can be refreshed using `stickyflag update`.
|
215
|
+
LONGDESC
|
216
|
+
def tags
|
217
|
+
say "Tags currently in use:" unless options.quiet?
|
218
|
+
padding = ''
|
219
|
+
padding = ' ' unless options.quiet?
|
220
|
+
|
221
|
+
tag_list.each do |t|
|
222
|
+
say "#{padding}#{t}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
desc 'find [TAG] [...]', "show all files that are tagged with the given tags"
|
227
|
+
long_desc <<-LONGDESC
|
228
|
+
`stickyflag find` locates all files that have a given tag or set of tags.
|
229
|
+
If multiple tags are provided, returned files must have all those tags.
|
230
|
+
LONGDESC
|
231
|
+
def find(*tags)
|
232
|
+
if tags.empty?
|
233
|
+
raise Thor::Error.new("stickyflag find requires at least 1 argument: \"stickyflag find [TAG] [...]\"")
|
234
|
+
end
|
235
|
+
tags.each { |t| check_tag t }
|
236
|
+
|
237
|
+
files_for_tags(tags).each do |file|
|
238
|
+
say file
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
private
|
243
|
+
|
244
|
+
def check_tag(tag)
|
245
|
+
if tag.include? ','
|
246
|
+
raise Thor::Error.new("ERROR: Tag names cannot include a comma.")
|
247
|
+
end
|
248
|
+
if tag.empty?
|
249
|
+
raise Thor::Error.new("ERROR: Cannot set an empty tag.")
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
$KCODE = 'U' if RUBY_VERSION < "1.9.0"
|
3
|
+
ENV['RSPEC_TESTING'] = 'true'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'bundler/setup'
|
7
|
+
require 'simplecov' unless ENV["CI"] == 'true'
|
8
|
+
|
9
|
+
require 'backports'
|
10
|
+
require 'rspec/autorun'
|
11
|
+
require 'stickyflag'
|
12
|
+
|
13
|
+
Dir['./spec/support/**/*.rb'].map { |f| require f }
|
14
|
+
|
15
|
+
RSpec.configure do |c|
|
16
|
+
c.before(:each) do
|
17
|
+
# Don't write anything to the console
|
18
|
+
Thor::Shell::Basic.any_instance.stub(:say) { }
|
19
|
+
Thor::Shell::Basic.any_instance.stub(:say_status) { }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|