whale 0.0.0 → 0.1.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.
- checksums.yaml +4 -4
- data/bin/whale +108 -22
- data/lib/whale.rb +44 -61
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10aefc6ce7f9102e028c417e9e8d7a5b2f1f1ce0
|
4
|
+
data.tar.gz: 7a3dcd424e6c33d208b18ae7e3a29b7d078b564d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e308539f6f2ac801178c4edb1fbabbec49ac1e31459c5d3662e96732deb7ca19a7edaa9fbabc2e1ed37b9a3449010277dd780aab335fb4699808471e8d6c5a6
|
7
|
+
data.tar.gz: 6653cc61f4da672de5e76f677c2845ba1b66a326333206115f57bb6b1f1658d0f5b4b256afd84bd67ad1cf942b5a41eec68087b84d524e0f980da7069321abbb
|
data/bin/whale
CHANGED
@@ -1,18 +1,34 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'whale'
|
3
|
+
require 'logger'
|
4
|
+
|
3
5
|
|
4
6
|
USAGE = <<ENDUSAGE
|
5
7
|
Usage:
|
6
|
-
whale [-h] [-
|
8
|
+
whale [files] [-h] [-p paths] [-r] [-f filter] [-e id] [-s tag]
|
9
|
+
[-w] [-n] [-t] [-v] [-d]
|
7
10
|
ENDUSAGE
|
8
11
|
|
9
12
|
HELP = <<ENDHELP
|
13
|
+
files Space-separated list of files
|
10
14
|
-h, --help View this message
|
11
|
-
-
|
12
|
-
-
|
15
|
+
-p, --paths Colon-separated list of paths to search
|
16
|
+
-r, --recursive Search for files recursively
|
17
|
+
-f, --filter Filter entries by tags
|
13
18
|
-e, --edit Open the editor to given entry
|
14
|
-
-
|
15
|
-
--
|
19
|
+
-s, --sort Sort entries by tag value
|
20
|
+
-w, --write Write the entries to stdout
|
21
|
+
-n, --name List the tag names
|
22
|
+
-t, --tags Show the tag values
|
23
|
+
-v, --version Show the version number
|
24
|
+
-d, --debug Set the logging level to debug
|
25
|
+
|
26
|
+
-p, --paths
|
27
|
+
Specify the paths to search. Each path is separated by a colon. Any files
|
28
|
+
with the .wl or .whale extension are read.
|
29
|
+
|
30
|
+
-r, --recursive
|
31
|
+
Search for files recursively.
|
16
32
|
|
17
33
|
-f, --filter filter
|
18
34
|
Show entries with tags satisfying the filter. The filter is a string
|
@@ -22,28 +38,54 @@ HELP = <<ENDHELP
|
|
22
38
|
Options for regexes are specified by writing /(?option:regex)/.
|
23
39
|
The symbols for AND, OR, and NOT are &, |, and *, respectively.
|
24
40
|
|
25
|
-
-s, --sort tag
|
26
|
-
Sort the entries by tag.
|
27
|
-
|
28
41
|
-e, --edit id
|
29
42
|
Edit the entry with id id using the text editor specified by the environment
|
30
43
|
EDITOR.
|
31
44
|
|
45
|
+
-n, --name
|
46
|
+
List all tags read.
|
47
|
+
|
48
|
+
-t, --tags tags
|
49
|
+
Show the values of the given tags.
|
50
|
+
|
51
|
+
-s, --sort tag
|
52
|
+
Sort the entries by the given tag value.
|
53
|
+
|
32
54
|
-w, --write
|
33
55
|
Write the entries, including title, body, and tags, to stdout.
|
56
|
+
|
57
|
+
-v, --version
|
58
|
+
Show the version number.
|
59
|
+
|
60
|
+
-d, --debug
|
61
|
+
Set the logging level to debug.
|
62
|
+
|
63
|
+
Environment
|
64
|
+
EDITOR
|
65
|
+
The editor for the edit command.
|
66
|
+
|
67
|
+
WHALEPATH
|
68
|
+
A colon separated list of default paths to search when paths is not
|
69
|
+
provided.
|
34
70
|
ENDHELP
|
35
71
|
|
36
|
-
args = { :files => [] }
|
72
|
+
args = { :files => [], :recursive => false }
|
37
73
|
unflagged_args = [:files]
|
38
74
|
next_arg = unflagged_args.first
|
75
|
+
|
39
76
|
ARGV.each do |arg|
|
40
77
|
case arg
|
41
78
|
when '-h','--help' then args[:help] = true
|
79
|
+
when '-p','--paths' then next_arg = :paths
|
42
80
|
when '-f','--filter' then next_arg = :filter
|
43
|
-
when '-s','--sort' then next_arg = :sort
|
44
81
|
when '-e','--edit' then next_arg = :edit
|
45
|
-
when '-
|
46
|
-
when '--
|
82
|
+
when '-n','--name' then args[:name] = true
|
83
|
+
when '-t','--tags' then next_arg = :tags
|
84
|
+
when '-s','--sort' then next_arg = :sort
|
85
|
+
when '-r','--recursive' then args[:recursive] = true
|
86
|
+
when '-w','--write' then args[:write] = true
|
87
|
+
when '-v','--version' then args[:version] = true
|
88
|
+
when '-d','--debug' then args[:debug] = true
|
47
89
|
else
|
48
90
|
if next_arg == :files
|
49
91
|
args[:files] << arg
|
@@ -54,24 +96,56 @@ ARGV.each do |arg|
|
|
54
96
|
end
|
55
97
|
end
|
56
98
|
end
|
99
|
+
|
100
|
+
logger = Logger.new(STDOUT)
|
101
|
+
logger.level = Logger::WARN
|
102
|
+
logger.level = Logger::DEBUG if args[:debug]
|
103
|
+
logger.level = Logger::UNKNOWN if args[:write]
|
104
|
+
|
57
105
|
if args[:version]
|
58
106
|
puts "whale.rb version #{MAJOR_VERSION}.#{MINOR_VERSION}.#{REVISION}"
|
59
107
|
exit
|
60
108
|
end
|
109
|
+
|
110
|
+
paths = []
|
111
|
+
paths += ENV['WHALEPATH'].split(':') if ENV['WHALEPATH']
|
112
|
+
paths += args[:paths].split(':') if args[:paths]
|
113
|
+
|
114
|
+
logger.debug(paths)
|
115
|
+
|
116
|
+
paths.each { |p| args[:files] += \
|
117
|
+
list_files_in_path(p, args[:recursive], logger) }
|
118
|
+
|
119
|
+
logger.debug(args[:files])
|
120
|
+
|
61
121
|
if args[:help] or args[:files].empty?
|
62
122
|
puts USAGE
|
63
123
|
puts HELP if args[:help]
|
64
124
|
exit
|
65
125
|
end
|
126
|
+
|
66
127
|
entries = []
|
67
|
-
args[:files].each
|
68
|
-
|
128
|
+
args[:files].each do |file|
|
129
|
+
begin
|
130
|
+
entries += parse_file(file, logger)
|
131
|
+
rescue Errno::ENOENT
|
132
|
+
logger.warn("File not found: #{file}")
|
133
|
+
rescue Errno::EISDIR
|
134
|
+
logger.warn("#{file} is a directory. Use -p to specify paths")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
logger.info("Parsed #{args[:files].length} files " +
|
139
|
+
"and #{entries.length} entries")
|
140
|
+
|
69
141
|
if args[:filter]
|
70
142
|
filter = Filter.new
|
71
|
-
filter.
|
72
|
-
filter_entries(entries, filter)
|
143
|
+
filter.pgrse_filter(args[:filter], logger)
|
144
|
+
filter_entries(entries, filter, logger)
|
73
145
|
end
|
74
|
-
|
146
|
+
|
147
|
+
sort_entries_by(entries, args[:sort], logger) if args[:sort]
|
148
|
+
|
75
149
|
if args[:edit]
|
76
150
|
i = args[:edit].to_i - 1
|
77
151
|
e = entries[i]
|
@@ -79,15 +153,27 @@ if args[:edit]
|
|
79
153
|
puts "Invalid ID"
|
80
154
|
exit
|
81
155
|
end
|
82
|
-
open_editor(ENV['EDITOR'].to_sym, e.tags[:file], e.tags[:line])
|
156
|
+
open_editor(ENV['EDITOR'].to_sym, e.tags[:file], e.tags[:line], logger)
|
157
|
+
end
|
158
|
+
|
159
|
+
if args[:name] and !args[:write]
|
160
|
+
list_tags get_all_tags(entries)
|
83
161
|
end
|
84
162
|
|
85
163
|
write_entries(entries) if args[:write]
|
86
164
|
|
87
|
-
|
165
|
+
tags_to_list = [:title, :date, :tags]
|
166
|
+
tags_format = [45, 10, 24]
|
167
|
+
|
168
|
+
if args[:tags]
|
169
|
+
tags = args[:tags].split(' ')
|
170
|
+
tags.each do |t|
|
171
|
+
tags_to_list << t.to_sym
|
172
|
+
tags_format << 24
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
if !args[:write] and !args[:name]
|
88
177
|
all_tags = get_all_tags entries
|
89
|
-
debug(list_tags(all_tags))
|
90
|
-
tags_to_list = [:title, :date, :tags]
|
91
|
-
tags_format = [45, 10, 25]
|
92
178
|
list_entries(entries, tags_to_list, tags_format)
|
93
179
|
end
|
data/lib/whale.rb
CHANGED
@@ -1,26 +1,12 @@
|
|
1
1
|
# File: whale.rb
|
2
2
|
|
3
3
|
require 'set'
|
4
|
-
|
5
|
-
DEBUG = true
|
4
|
+
require 'logger'
|
6
5
|
|
7
6
|
MAJOR_VERSION = 0
|
8
|
-
MINOR_VERSION =
|
7
|
+
MINOR_VERSION = 1
|
9
8
|
REVISION = 0
|
10
9
|
|
11
|
-
|
12
|
-
def debug(msg)
|
13
|
-
puts msg if DEBUG
|
14
|
-
end
|
15
|
-
|
16
|
-
def error(msg)
|
17
|
-
puts "Error: #{msg}"
|
18
|
-
end
|
19
|
-
|
20
|
-
def warning(msg)
|
21
|
-
puts "Warning: #{msg}"
|
22
|
-
end
|
23
|
-
|
24
10
|
$DEFAULT_TAGS = [:title, :body, :line, :file, :tags]
|
25
11
|
|
26
12
|
class Entry
|
@@ -45,10 +31,9 @@ class Filter
|
|
45
31
|
|
46
32
|
# For regex options (e.g. ignorecase) use the (?opt:source) notation,
|
47
33
|
# e.g. /(?i-mx:hEllo .*)/
|
48
|
-
def parse_filter(f)
|
49
|
-
debug("parsing #{f}")
|
34
|
+
def parse_filter(f, logger)
|
35
|
+
logger.debug("parsing #{f}")
|
50
36
|
regex = false
|
51
|
-
quote = nil
|
52
37
|
escape = false
|
53
38
|
# literal = false
|
54
39
|
token = 0
|
@@ -56,34 +41,22 @@ class Filter
|
|
56
41
|
(0...f.length).each do |i|
|
57
42
|
if f[i] == '\\' or escape
|
58
43
|
escape ^= true
|
59
|
-
elsif f[i] == '"' or f[i] == "'"
|
60
|
-
unless regex
|
61
|
-
if quote == f[i]
|
62
|
-
stack << f[token, i - token]
|
63
|
-
quote = nil
|
64
|
-
else
|
65
|
-
token = i + 1
|
66
|
-
quote = f[i]
|
67
|
-
end
|
68
|
-
end
|
69
44
|
elsif f[i] == '/'
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
regex = true
|
77
|
-
end
|
45
|
+
if regex
|
46
|
+
stack << Regexp.new(f[token, i - token])
|
47
|
+
regex = false
|
48
|
+
else
|
49
|
+
token = i + 1
|
50
|
+
regex = true
|
78
51
|
end
|
79
|
-
elsif !regex
|
52
|
+
elsif !regex
|
80
53
|
stack << :FILTER_AND if f[i] == '&'
|
81
54
|
stack << :FILTER_OR if f[i] == '|'
|
82
55
|
stack << :FILTER_NOT if f[i] == '*'
|
83
56
|
stack << :FILTER_EQ if f[i] == '='
|
84
57
|
end
|
85
58
|
end
|
86
|
-
debug(stack)
|
59
|
+
logger.debug(stack)
|
87
60
|
@stack = stack
|
88
61
|
end
|
89
62
|
|
@@ -96,7 +69,7 @@ class Filter
|
|
96
69
|
end
|
97
70
|
|
98
71
|
# apply the stack to the entry tags
|
99
|
-
def filter(entry)
|
72
|
+
def filter(entry, logger)
|
100
73
|
s = []
|
101
74
|
last_tags = []
|
102
75
|
eq = false
|
@@ -108,13 +81,13 @@ class Filter
|
|
108
81
|
when :FILTER_EQ then eq = true
|
109
82
|
else
|
110
83
|
if eq
|
111
|
-
debug("last tags: #{last_tags}")
|
84
|
+
logger.debug("last tags: #{last_tags}")
|
112
85
|
match = false
|
113
86
|
s.pop
|
114
87
|
last_tags.each do |u|
|
115
88
|
if t.match entry.tags[u]
|
116
89
|
match = true
|
117
|
-
debug("#{t} matches #{entry.tags[u]}")
|
90
|
+
logger.debug("#{t} matches #{entry.tags[u]}")
|
118
91
|
break
|
119
92
|
end
|
120
93
|
end
|
@@ -126,18 +99,18 @@ class Filter
|
|
126
99
|
end
|
127
100
|
end
|
128
101
|
end
|
129
|
-
debug(s)
|
130
|
-
|
102
|
+
logger.debug(s)
|
103
|
+
logger.warn("Malformed filter") if s.length != 1
|
131
104
|
return s.first
|
132
105
|
end
|
133
106
|
|
134
107
|
end
|
135
108
|
|
136
|
-
def filter_entries(entries, filter)
|
137
|
-
entries.delete_if { |a| !filter.filter(a) }
|
109
|
+
def filter_entries(entries, filter, logger)
|
110
|
+
entries.delete_if { |a| !filter.filter(a, logger) }
|
138
111
|
end
|
139
112
|
|
140
|
-
def sort_entries_by(entries, tag)
|
113
|
+
def sort_entries_by(entries, tag, logger)
|
141
114
|
return entries.sort { |a, b| a.tags[tag] <=> b.tags[tag] }
|
142
115
|
end
|
143
116
|
|
@@ -148,8 +121,8 @@ $EDITOR_CMDS = {
|
|
148
121
|
}
|
149
122
|
$EDITOR_CMDS.default = "ed %<file>s"
|
150
123
|
|
151
|
-
def open_editor(editor, path, lineno)
|
152
|
-
debug("opening at #{lineno}")
|
124
|
+
def open_editor(editor, path, lineno, logger)
|
125
|
+
logger.debug("opening at #{lineno}")
|
153
126
|
args = {line: lineno, file: path}
|
154
127
|
cmd = $EDITOR_CMDS[editor] % args
|
155
128
|
exec(cmd)
|
@@ -230,36 +203,46 @@ def list_tags(tags)
|
|
230
203
|
puts s.slice(0, s.length - 2)
|
231
204
|
end
|
232
205
|
|
206
|
+
# List files in the path with the given extension
|
207
|
+
def list_files_in_path(path, recursive, logger)
|
208
|
+
file_glob = '*.{wl,whale}'
|
209
|
+
if recursive
|
210
|
+
glob_path = File.join(path, File.join('**', file_glob))
|
211
|
+
else
|
212
|
+
glob_path = File.join(path, file_glob)
|
213
|
+
end
|
214
|
+
return Dir.glob(glob_path)
|
215
|
+
end
|
233
216
|
|
234
217
|
EMPTY_LINE = /\A\s*\Z/
|
235
218
|
LABEL_LINE = /\A;(.*)\Z/
|
236
219
|
|
237
220
|
# Extract entries from file.
|
238
|
-
# param @file
|
239
|
-
# return Array
|
240
|
-
def parse_file(file)
|
221
|
+
# param @file a file name to read
|
222
|
+
# return Array an array of Entry
|
223
|
+
def parse_file(file, logger)
|
241
224
|
entries = []
|
242
225
|
file_entry = Entry.new
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
226
|
+
entry = nil
|
227
|
+
is_reading_tag = true
|
228
|
+
lineno = 0
|
229
|
+
File.open(file, 'r') do |f|
|
247
230
|
f.each_line do |line|
|
248
231
|
lineno += 1
|
249
232
|
# skip if the line is whitespace
|
250
233
|
next if EMPTY_LINE.match line
|
251
234
|
if (m = LABEL_LINE.match line)
|
252
|
-
debug("#{f.path}, #{lineno}, reading tag")
|
235
|
+
logger.debug("#{f.path}, #{lineno}, reading tag")
|
253
236
|
is_reading_tag = true
|
254
237
|
matched_line = m[1]
|
255
238
|
if entry.nil?
|
256
|
-
debug("adding file entry tags #{matched_line}")
|
239
|
+
logger.debug("adding file entry tags #{matched_line}")
|
257
240
|
parse_tags file_entry, matched_line
|
258
241
|
else
|
259
242
|
parse_tags entry, matched_line
|
260
243
|
end
|
261
244
|
elsif is_reading_tag
|
262
|
-
debug("#{f.path}, #{lineno}, new entry")
|
245
|
+
logger.debug("#{f.path}, #{lineno}, new entry")
|
263
246
|
is_reading_tag = false
|
264
247
|
entries << entry if !entry.nil?
|
265
248
|
entry = Entry.new
|
@@ -271,10 +254,10 @@ def parse_file(file)
|
|
271
254
|
end
|
272
255
|
end
|
273
256
|
entries << entry if !entry.nil?
|
274
|
-
|
257
|
+
logger.warn("#{file} missing last entry tags") if !is_reading_tag
|
275
258
|
end
|
276
259
|
# add the file level tags to each entry
|
277
|
-
debug(file_entry.tags)
|
260
|
+
logger.debug(file_entry.tags)
|
278
261
|
entries.each { |e| e.tags = file_entry.tags.merge(e.tags) }
|
279
262
|
return entries
|
280
263
|
end
|