stickyflag 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,162 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'thread'
|
3
|
+
require 'sequel'
|
4
|
+
|
5
|
+
module StickyFlag
|
6
|
+
module Database
|
7
|
+
def load_database
|
8
|
+
@database.disconnect if @database
|
9
|
+
|
10
|
+
if RUBY_PLATFORM == 'java'
|
11
|
+
@database = Sequel.connect("jdbc:sqlite:#{database_path}")
|
12
|
+
else
|
13
|
+
@database = Sequel.sqlite(:database => database_path)
|
14
|
+
end
|
15
|
+
raise Thor::Error.new("ERROR: Could not create database at '#{database_path}'") if @database.nil?
|
16
|
+
|
17
|
+
create_tables
|
18
|
+
|
19
|
+
# Do not do automatic cleanup from the RSpec test suite; this registers
|
20
|
+
# dozens of at_exit hooks and crashes Ruby
|
21
|
+
unless ENV['RSPEC_TESTING']
|
22
|
+
at_exit { @database.disconnect }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_tables
|
27
|
+
@database.create_table?(:tag_list) do
|
28
|
+
primary_key :id, :type => Bignum
|
29
|
+
String :tag_name, :text => true
|
30
|
+
end
|
31
|
+
@database.create_table?(:file_list) do
|
32
|
+
primary_key :id, :type => Bignum
|
33
|
+
String :file_name, :text => true
|
34
|
+
end
|
35
|
+
@database.create_table?(:tagged_files) do
|
36
|
+
primary_key :id
|
37
|
+
Bignum :file
|
38
|
+
Bignum :tag
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def drop_tables
|
43
|
+
@database.drop_table(:tag_list, :file_list, :tagged_files)
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_tag_id(tag)
|
47
|
+
tag_id = @database[:tag_list].where(:tag_name => tag).get(:id)
|
48
|
+
unless tag_id
|
49
|
+
tag_id = @database[:tag_list].insert(:tag_name => tag)
|
50
|
+
end
|
51
|
+
|
52
|
+
tag_id
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_file_id(file_name)
|
56
|
+
file_id = @database[:file_list].where(:file_name => file_name).get(:id)
|
57
|
+
unless file_id
|
58
|
+
file_id = @database[:file_list].insert(:file_name => file_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
file_id
|
62
|
+
end
|
63
|
+
|
64
|
+
def update_database_from_files(directory = '.')
|
65
|
+
drop_tables
|
66
|
+
create_tables
|
67
|
+
|
68
|
+
Dir.glob(File.join(directory, '**', "*{#{available_tagging_extensions.join(',')}}")).each do |file|
|
69
|
+
begin
|
70
|
+
tags = get_tags_for file
|
71
|
+
rescue Thor::Error
|
72
|
+
# Just skip this file, then, don't error out entirely
|
73
|
+
say_status :warning, "Could not read tags from '#{file}', despite a valid extension", :yellow
|
74
|
+
next
|
75
|
+
end
|
76
|
+
|
77
|
+
# Don't record files in the DB that have no tags
|
78
|
+
next if tags.empty?
|
79
|
+
|
80
|
+
file_id = get_file_id file
|
81
|
+
tags.each do |tag|
|
82
|
+
tag_id = get_tag_id tag
|
83
|
+
@database[:tagged_files].insert(:file => file_id, :tag => tag_id)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def set_database_tag(file_name, tag)
|
89
|
+
# Don't put in multiple entries for the same tag
|
90
|
+
file_id = get_file_id file_name
|
91
|
+
tag_id = get_tag_id tag
|
92
|
+
|
93
|
+
return unless @database[:tagged_files].where(:file => file_id).and(:tag => tag_id).empty?
|
94
|
+
@database[:tagged_files].insert(:file => file_id, :tag => tag_id)
|
95
|
+
end
|
96
|
+
|
97
|
+
def unset_database_tag(file_name, tag)
|
98
|
+
file_id = get_file_id file_name
|
99
|
+
tag_id = get_tag_id tag
|
100
|
+
@database[:tagged_files].where(:file => file_id).and(:tag => tag_id).delete
|
101
|
+
|
102
|
+
# See if that was the last file with this tag, and delete it if so
|
103
|
+
if @database[:tagged_files].where(:tag => tag_id).empty?
|
104
|
+
@database[:tag_list].where(:id => tag_id).delete
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def clear_database_tags(file_name)
|
109
|
+
file_id = get_file_id file_name
|
110
|
+
@database[:tagged_files].where(:file => file_id).delete
|
111
|
+
|
112
|
+
# That operation might have removed the last instance of a tag, clean up
|
113
|
+
# the tag list
|
114
|
+
@database[:tag_list].each do |row|
|
115
|
+
raise Thor::Error.new("INTERNAL ERROR: Database row error in tag_list") unless row.include? :id
|
116
|
+
tag_id = row[:id]
|
117
|
+
|
118
|
+
if @database[:tagged_files].where(:tag => tag_id).empty?
|
119
|
+
@database[:tag_list].where(:id => tag_id).delete
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def files_for_tags(tags)
|
125
|
+
# Map tags to tag IDs, but don't add any missing tags
|
126
|
+
bad_tag = false
|
127
|
+
tag_ids = []
|
128
|
+
tags.each do |tag|
|
129
|
+
tag_id = @database[:tag_list].where(:tag_name => tag).get(:id)
|
130
|
+
unless tag_id
|
131
|
+
say_status :warning, "Tag '#{tag}' is not present in the database (try `stickyflag update`)", :yellow unless options.quiet?
|
132
|
+
bad_tag = true
|
133
|
+
next
|
134
|
+
end
|
135
|
+
|
136
|
+
tag_ids << tag_id
|
137
|
+
end
|
138
|
+
return [] if bad_tag
|
139
|
+
|
140
|
+
file_rows = @database[:tagged_files].where(:tag => tag_ids).group_by(:file).having{[[count(:*){}, tag_ids.count]]}
|
141
|
+
if file_rows.empty?
|
142
|
+
say_status :warning, "Requested combination of tags not found", :yellow unless options.quiet?
|
143
|
+
return []
|
144
|
+
end
|
145
|
+
|
146
|
+
files = []
|
147
|
+
file_rows.each do |row|
|
148
|
+
file_id = row[:file]
|
149
|
+
file = @database[:file_list].where(:id => file_id).get(:file_name)
|
150
|
+
raise Thor::Error.new("ERROR: Could not get file_name for id saved in database (re-run `stickyflag update`)") unless file
|
151
|
+
|
152
|
+
files << file
|
153
|
+
end
|
154
|
+
|
155
|
+
files
|
156
|
+
end
|
157
|
+
|
158
|
+
def tag_list
|
159
|
+
@database[:tag_list].map { |r| r[:tag_name] }
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module StickyFlag
|
4
|
+
module ExternalCmds
|
5
|
+
|
6
|
+
EXTERNAL_CMDS = {
|
7
|
+
'pdftk' => 'read and write PDF tags'
|
8
|
+
}
|
9
|
+
|
10
|
+
def find_external_cmds
|
11
|
+
EXTERNAL_CMDS.each do |cmd, desc|
|
12
|
+
path = get_config "#{cmd}_path"
|
13
|
+
|
14
|
+
# First, make sure that the listed file actually exists and is executable,
|
15
|
+
# so that if it isn't, we'll be able to fix it by checking $PATH down
|
16
|
+
# below.
|
17
|
+
unless path.nil? || path.empty?
|
18
|
+
unless File.executable? path
|
19
|
+
say_status :error, "Path set for #{cmd} is invalid", :red unless options.quiet?
|
20
|
+
|
21
|
+
path = ''
|
22
|
+
set_config "#{cmd}_path", ''
|
23
|
+
set_config "have_#{cmd}", false
|
24
|
+
else
|
25
|
+
# We do have this, make sure it's set that way
|
26
|
+
set_config "have_#{cmd}", true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if path.nil? || path.empty?
|
31
|
+
# We don't have a path for this command, see if we can find it
|
32
|
+
found = which(cmd)
|
33
|
+
if found.nil?
|
34
|
+
say_status :warning, "Cannot find #{cmd} in path, will not be able to #{desc}", :yellow unless options.quiet?
|
35
|
+
set_config "have_#{cmd}", false
|
36
|
+
next
|
37
|
+
end
|
38
|
+
|
39
|
+
# Okay, found it, set the configuration parameter
|
40
|
+
set_config "#{cmd}_path", found
|
41
|
+
set_config "have_#{cmd}", true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Save our results out to the configuration file
|
46
|
+
save_config!
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Thanks to mislav on Stack Overflow for this
|
52
|
+
def which(cmd)
|
53
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
54
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
55
|
+
exts.each { |ext|
|
56
|
+
exe = "#{path}#{File::SEPARATOR}#{cmd}#{ext}"
|
57
|
+
return exe if File.executable? exe
|
58
|
+
}
|
59
|
+
end
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
class Tempfile
|
5
|
+
|
6
|
+
unless Tempfile.respond_to?(:new_with_encoding)
|
7
|
+
def self.new_with_encoding(params)
|
8
|
+
# JRuby's Tempfile constructor got the third hash parameter in JRuby
|
9
|
+
# 1.7.0-preview1.
|
10
|
+
if RUBY_VERSION >= "1.9.0" && (RUBY_PLATFORM != 'java' || JRUBY_VERSION >= '1.7.0')
|
11
|
+
return Tempfile.new(params, Dir.tmpdir, :encoding => "UTF-8")
|
12
|
+
else
|
13
|
+
# No coverage on Ruby 1.8, ignore
|
14
|
+
#:nocov:
|
15
|
+
return Tempfile.new(params)
|
16
|
+
#:nocov:
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
class File
|
5
|
+
|
6
|
+
unless File.respond_to?(:tmpnam)
|
7
|
+
def self.tmpnam(ext = '')
|
8
|
+
if ext != ''
|
9
|
+
if ext[0] != '.'
|
10
|
+
ext = ".#{ext}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
pid = Process.pid
|
15
|
+
time = Time.now
|
16
|
+
sec = time.to_i
|
17
|
+
usec = time.usec
|
18
|
+
|
19
|
+
counter = 0
|
20
|
+
path = File.join(Dir.tmpdir, "#{pid}_#{sec}_#{usec}_#{rand(1000)}#{ext}")
|
21
|
+
|
22
|
+
# This is a corner case that should effectively never be possible, so
|
23
|
+
# don't try to cover it.
|
24
|
+
#:nocov:
|
25
|
+
while File.exist? path
|
26
|
+
sec = Time.now.to_i
|
27
|
+
usec = Time.now.usec
|
28
|
+
path = File.join(Dir.tmpdir, "#{pid}_#{sec}_#{usec}_#{rand(1000)}#{ext}")
|
29
|
+
|
30
|
+
counter += 1
|
31
|
+
raise 'ERROR: Cannot get unique temporary name' if counter >= 100
|
32
|
+
end
|
33
|
+
#:nocov:
|
34
|
+
|
35
|
+
path
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'rbconfig'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module StickyFlag
|
6
|
+
module Paths
|
7
|
+
def config_path
|
8
|
+
# No code coverage: only ever runs one operating system branch
|
9
|
+
#:nocov:
|
10
|
+
case RbConfig::CONFIG['target_os']
|
11
|
+
when /darwin/i
|
12
|
+
root_dir = File.expand_path("~/Library/Application Support/StickyFlag")
|
13
|
+
when /linux/i
|
14
|
+
require 'xdg'
|
15
|
+
root_dir = File.join(XDG['CONFIG_HOME'].to_s, 'stickyflag')
|
16
|
+
when /mswin|mingw/i
|
17
|
+
root_dir = File.join(ENV['APPDATA'], 'StickyFlag')
|
18
|
+
else
|
19
|
+
root_dir = File.expand_path('~/.stickyflag')
|
20
|
+
end
|
21
|
+
#:nocov:
|
22
|
+
|
23
|
+
FileUtils.mkdir_p root_dir
|
24
|
+
File.join(root_dir, 'config.yml')
|
25
|
+
end
|
26
|
+
|
27
|
+
def database_path
|
28
|
+
# No code coverage: only ever runs one operating system branch
|
29
|
+
#:nocov:
|
30
|
+
case RbConfig::CONFIG['target_os']
|
31
|
+
when /darwin/i
|
32
|
+
root_dir = File.expand_path("~/Library/Application Support/StickyFlag")
|
33
|
+
when /linux/i
|
34
|
+
require 'xdg'
|
35
|
+
root_dir = File.join(XDG['DATA_HOME'].to_s, 'stickyflag')
|
36
|
+
when /mswin|mingw/i
|
37
|
+
root_dir = File.join(ENV['APPDATA'], 'StickyFlag')
|
38
|
+
else
|
39
|
+
root_dir = File.expand_path('~/.stickyflag')
|
40
|
+
end
|
41
|
+
#:nocov:
|
42
|
+
|
43
|
+
FileUtils.mkdir_p root_dir
|
44
|
+
File.join(root_dir, 'db.sqlite')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'thor'
|
3
|
+
require 'pathname'
|
4
|
+
require 'backports'
|
5
|
+
require 'stickyflag/tags/pdf'
|
6
|
+
require 'stickyflag/tags/pdf'
|
7
|
+
require 'stickyflag/tags/png'
|
8
|
+
require 'stickyflag/tags/tex'
|
9
|
+
require 'stickyflag/tags/c'
|
10
|
+
require 'stickyflag/tags/mmd'
|
11
|
+
|
12
|
+
module StickyFlag
|
13
|
+
module TagFactory
|
14
|
+
def available_tagging_extensions
|
15
|
+
['.pdf', '.png', '.tex', '.c', '.cpp', '.cxx', '.c++',
|
16
|
+
'.h', '.hpp', '.hxx', '.mmd']
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_tags_for(file_name)
|
20
|
+
extension = File.extname file_name
|
21
|
+
case extension
|
22
|
+
when '.pdf' then return StickyFlag::Tags::PDF::get(file_name, get_config(:pdftk_path))
|
23
|
+
when '.png' then return StickyFlag::Tags::PNG::get(file_name)
|
24
|
+
when '.tex' then return StickyFlag::Tags::TeX::get(file_name)
|
25
|
+
when '.c' then return StickyFlag::Tags::C::get(file_name)
|
26
|
+
when '.cpp' then return StickyFlag::Tags::C::get(file_name)
|
27
|
+
when '.cxx' then return StickyFlag::Tags::C::get(file_name)
|
28
|
+
when '.c++' then return StickyFlag::Tags::C::get(file_name)
|
29
|
+
when '.h' then return StickyFlag::Tags::C::get(file_name)
|
30
|
+
when '.hpp' then return StickyFlag::Tags::C::get(file_name)
|
31
|
+
when '.hxx' then return StickyFlag::Tags::C::get(file_name)
|
32
|
+
when '.mmd' then return StickyFlag::Tags::MMD::get(file_name)
|
33
|
+
else raise Thor::Error.new("ERROR: Don't know how to get tags for a file of extension '#{extension}'")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_tag_for(file_name, tag)
|
38
|
+
tags = get_tags_for file_name
|
39
|
+
if tags.include? tag
|
40
|
+
# Already set
|
41
|
+
return
|
42
|
+
end
|
43
|
+
|
44
|
+
set_database_tag file_name, tag
|
45
|
+
|
46
|
+
extension = File.extname file_name
|
47
|
+
case extension
|
48
|
+
when '.pdf' then return StickyFlag::Tags::PDF::set(file_name, tag, get_config(:pdftk_path))
|
49
|
+
when '.png' then return StickyFlag::Tags::PNG::set(file_name, tag)
|
50
|
+
when '.tex' then return StickyFlag::Tags::TeX::set(file_name, tag)
|
51
|
+
when '.c' then return StickyFlag::Tags::C::set(file_name, tag)
|
52
|
+
when '.cpp' then return StickyFlag::Tags::C::set(file_name, tag)
|
53
|
+
when '.cxx' then return StickyFlag::Tags::C::set(file_name, tag)
|
54
|
+
when '.c++' then return StickyFlag::Tags::C::set(file_name, tag)
|
55
|
+
when '.h' then return StickyFlag::Tags::C::set(file_name, tag)
|
56
|
+
when '.hpp' then return StickyFlag::Tags::C::set(file_name, tag)
|
57
|
+
when '.hxx' then return StickyFlag::Tags::C::set(file_name, tag)
|
58
|
+
when '.mmd' then return StickyFlag::Tags::MMD::set(file_name, tag)
|
59
|
+
else raise Thor::Error.new("ERROR: Don't know how to set tags for a file of extension '#{extension}'")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def unset_tag_for(file_name, tag)
|
64
|
+
tags = get_tags_for file_name
|
65
|
+
unless tags.include? tag
|
66
|
+
raise Thor::Error.new("ERROR: Cannot unset tag #{tag} from file, not set")
|
67
|
+
end
|
68
|
+
|
69
|
+
unset_database_tag file_name, tag
|
70
|
+
|
71
|
+
extension = File.extname file_name
|
72
|
+
case extension
|
73
|
+
when '.pdf' then return StickyFlag::Tags::PDF::unset(file_name, tag, get_config(:pdftk_path))
|
74
|
+
when '.png' then return StickyFlag::Tags::PNG::unset(file_name, tag)
|
75
|
+
when '.tex' then return StickyFlag::Tags::TeX::unset(file_name, tag)
|
76
|
+
when '.c' then return StickyFlag::Tags::C::unset(file_name, tag)
|
77
|
+
when '.cpp' then return StickyFlag::Tags::C::unset(file_name, tag)
|
78
|
+
when '.cxx' then return StickyFlag::Tags::C::unset(file_name, tag)
|
79
|
+
when '.c++' then return StickyFlag::Tags::C::unset(file_name, tag)
|
80
|
+
when '.h' then return StickyFlag::Tags::C::unset(file_name, tag)
|
81
|
+
when '.hpp' then return StickyFlag::Tags::C::unset(file_name, tag)
|
82
|
+
when '.hxx' then return StickyFlag::Tags::C::unset(file_name, tag)
|
83
|
+
when '.mmd' then return StickyFlag::Tags::MMD::unset(file_name, tag)
|
84
|
+
else raise Thor::Error.new("ERROR: Don't know how to unset tags for a file of extension '#{extension}'")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def clear_tags_for(file_name)
|
89
|
+
clear_database_tags file_name
|
90
|
+
|
91
|
+
extension = File.extname file_name
|
92
|
+
case extension
|
93
|
+
when '.pdf' then return StickyFlag::Tags::PDF::clear(file_name, get_config(:pdftk_path))
|
94
|
+
when '.png' then return StickyFlag::Tags::PNG::clear(file_name)
|
95
|
+
when '.tex' then return StickyFlag::Tags::TeX::clear(file_name)
|
96
|
+
when '.c' then return StickyFlag::Tags::C::clear(file_name)
|
97
|
+
when '.cpp' then return StickyFlag::Tags::C::clear(file_name)
|
98
|
+
when '.cxx' then return StickyFlag::Tags::C::clear(file_name)
|
99
|
+
when '.c++' then return StickyFlag::Tags::C::clear(file_name)
|
100
|
+
when '.h' then return StickyFlag::Tags::C::clear(file_name)
|
101
|
+
when '.hpp' then return StickyFlag::Tags::C::clear(file_name)
|
102
|
+
when '.hxx' then return StickyFlag::Tags::C::clear(file_name)
|
103
|
+
when '.mmd' then return StickyFlag::Tags::MMD::clear(file_name)
|
104
|
+
else raise Thor::Error.new("ERROR: Don't know how to clear all tags for a file of extension '#{extension}'")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'stickyflag/tags/source_code'
|
3
|
+
|
4
|
+
module StickyFlag
|
5
|
+
module Tags
|
6
|
+
module C
|
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 StickyFlag::Tags::SourceCode
|
22
|
+
module_function :get, :set, :unset, :clear
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'stickyflag/patches/tmpnam.rb'
|
3
|
+
|
4
|
+
module StickyFlag
|
5
|
+
module Tags
|
6
|
+
module MMD
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# We duplicate the source-code module here because we want to have
|
10
|
+
# cute support for the indentation of tag contents in MMD, and because
|
11
|
+
# MMD metadata blocks have slightly weird syntax rules.
|
12
|
+
|
13
|
+
def metadata_key_regex
|
14
|
+
/([A-Za-z0-9][A-Za-z0-9\-_ ]*):\s*(.*)/
|
15
|
+
end
|
16
|
+
def tag_line_regex
|
17
|
+
/[Tt]\s*[Aa]\s*[Gg]\s*[Ss]\s*:.*/
|
18
|
+
end
|
19
|
+
|
20
|
+
def tag_line_for(tags, last_line = nil)
|
21
|
+
padding = ''
|
22
|
+
|
23
|
+
if last_line
|
24
|
+
match = last_line.match(/([A-Za-z0-9][A-Za-z0-9\-_ ]*:\s*|\s*).*/)
|
25
|
+
if match
|
26
|
+
num = match[1].length - 'Tags: '.length
|
27
|
+
if num > 0
|
28
|
+
padding = ' ' * num
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
"Tags: #{padding}#{tags.join(', ')} "
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_tags(file_name, tags)
|
37
|
+
set_tags = false
|
38
|
+
eating_tags = false
|
39
|
+
last_line = ''
|
40
|
+
counter = 0
|
41
|
+
|
42
|
+
outpath = File.tmpnam
|
43
|
+
File.open(outpath, 'w:UTF-8') do |outfile|
|
44
|
+
File.open(file_name, 'r:UTF-8').each_line do |line|
|
45
|
+
if counter == 0
|
46
|
+
if line !~ metadata_key_regex
|
47
|
+
# If we're on the first line and there's no metadata at all,
|
48
|
+
# then we have to add a metadata block
|
49
|
+
outfile.puts tag_line_for(tags) if tags
|
50
|
+
outfile.puts ''
|
51
|
+
set_tags = true
|
52
|
+
end
|
53
|
+
else
|
54
|
+
# Not on the first line
|
55
|
+
if set_tags == true
|
56
|
+
# If we've already set tags, this is easy
|
57
|
+
outfile.puts line
|
58
|
+
else
|
59
|
+
# We're somewhere in the metadta, and we've yet to set any
|
60
|
+
# tags.
|
61
|
+
if line =~ tag_line_regex
|
62
|
+
# We want to eat the tag block, which could theoretically
|
63
|
+
# be extended onto multiple lines
|
64
|
+
eating_tags = true
|
65
|
+
elsif eating_tags
|
66
|
+
# We're currently eating tags; if this line is blank or a
|
67
|
+
# key/value pair, we're done eating tags, time to print
|
68
|
+
if line =~ metadata_key_regex || line.strip.empty?
|
69
|
+
eating_tags = false
|
70
|
+
outfile.puts tag_line_for(tags, last_line) if tags
|
71
|
+
set_tags = true
|
72
|
+
end
|
73
|
+
else
|
74
|
+
# Not eating the tags key, just keep going, checking to see
|
75
|
+
# if the metadata block is over
|
76
|
+
if line.strip.empty?
|
77
|
+
outfile.puts tag_line_for(tags, last_line) if tags
|
78
|
+
set_tags = true
|
79
|
+
end
|
80
|
+
|
81
|
+
outfile.puts line
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
counter += 1
|
87
|
+
last_line = line
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
FileUtils.mv(outpath, file_name)
|
92
|
+
end
|
93
|
+
|
94
|
+
def get(file_name)
|
95
|
+
counter = 0
|
96
|
+
key = ''
|
97
|
+
value = ''
|
98
|
+
|
99
|
+
File.open(file_name, 'r:UTF-8').each_line do |line|
|
100
|
+
if counter == 0
|
101
|
+
# Check to see if there's any metadata at all
|
102
|
+
m = line.match(metadata_key_regex)
|
103
|
+
return [] unless m
|
104
|
+
|
105
|
+
# Start capturing values
|
106
|
+
key = m[1]
|
107
|
+
value = m[2]
|
108
|
+
else
|
109
|
+
# Check to see if the metadata is over, or if there's another
|
110
|
+
# key/value pair starting on this line
|
111
|
+
m = line.match(metadata_key_regex)
|
112
|
+
|
113
|
+
if m || line.strip.empty?
|
114
|
+
# We're done capturing this value, check if it's 'tags' (after
|
115
|
+
# removing whitespace and lowercasing, as per the spec)
|
116
|
+
key.gsub!(/\s/, '')
|
117
|
+
key.downcase!
|
118
|
+
|
119
|
+
if key == 'tags'
|
120
|
+
return [] if value.nil? || value.empty?
|
121
|
+
return value.split(',').map { |t| t.empty? ? nil : t.strip }.compact
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Quit if there's no more metadata
|
126
|
+
return if line.strip.empty?
|
127
|
+
|
128
|
+
# Start grabbing a new key/value if there is one, or else just
|
129
|
+
# add to the current value
|
130
|
+
if m
|
131
|
+
key = m[1]
|
132
|
+
value = m[2]
|
133
|
+
else
|
134
|
+
# This doesn't strip the indentation off of the values, but
|
135
|
+
# that's okay, since we always strip whitespace off
|
136
|
+
# individual tags
|
137
|
+
value << line
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
counter += 1
|
142
|
+
end
|
143
|
+
|
144
|
+
[]
|
145
|
+
end
|
146
|
+
|
147
|
+
def set(file_name, tag)
|
148
|
+
tags = get(file_name)
|
149
|
+
return if tags.include? tag
|
150
|
+
|
151
|
+
tags << tag
|
152
|
+
write_tags(file_name, tags)
|
153
|
+
end
|
154
|
+
|
155
|
+
def unset(file_name, tag)
|
156
|
+
tags = get(file_name)
|
157
|
+
return unless tags.include? tag
|
158
|
+
|
159
|
+
tags.delete(tag)
|
160
|
+
write_tags(file_name, tags)
|
161
|
+
end
|
162
|
+
|
163
|
+
def clear(file_name)
|
164
|
+
write_tags(file_name, nil)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|