tag 0.1.0 → 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/CHANGELOG.rdoc +4 -1
- data/README.md +19 -5
- data/lib/tag.rb +9 -80
- data/lib/tag/runner.rb +51 -0
- data/lib/tag/store.rb +68 -0
- data/spec/helper.rb +25 -0
- data/spec/runner_spec.rb +122 -0
- metadata +35 -35
data/CHANGELOG.rdoc
CHANGED
data/README.md
CHANGED
@@ -8,13 +8,28 @@ In making tags a first class nix citizen, perhaps they will see the light of day
|
|
8
8
|
Examples
|
9
9
|
========
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
$ tag
|
11
|
+
Let's start with tagging animals:
|
12
|
+
|
13
|
+
$ tag add horse -t fast strong
|
14
|
+
$ tag add cat -t fast independent
|
15
|
+
$ tag add dog -t loving
|
16
|
+
$ tag list fast
|
14
17
|
horse
|
15
18
|
cat
|
16
19
|
|
17
|
-
|
20
|
+
Tired of animals, let's tag cities. To avoid interfering with
|
21
|
+
the animals list, we'll use a cities model:
|
22
|
+
|
23
|
+
$ tag add nyc -t fast fun -m cities
|
24
|
+
$ tag add boston -t educated clean -m cities
|
25
|
+
$ tag add paris -t awesome delicious
|
26
|
+
$ tag list fast -m cities
|
27
|
+
nyc
|
28
|
+
# to avoid typing '-m cities' for every command
|
29
|
+
$ export TAG_MODEL=cities
|
30
|
+
|
31
|
+
Since models are isolated from one another, third party commandline apps can
|
32
|
+
use tag for their own private tagging purposes.
|
18
33
|
|
19
34
|
Motivation
|
20
35
|
==========
|
@@ -29,7 +44,6 @@ Contributing
|
|
29
44
|
Todo
|
30
45
|
====
|
31
46
|
|
32
|
-
* Tests!
|
33
47
|
* Several more tag actions
|
34
48
|
* Description and time fields
|
35
49
|
* Make storage agnostic
|
data/lib/tag.rb
CHANGED
@@ -1,88 +1,17 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'tag/runner'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'tag/store'
|
3
4
|
|
4
5
|
module Tag
|
5
|
-
VERSION = '0.
|
6
|
+
VERSION = '0.2.0'
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
self.file = Dir.home + '/.tagrc'
|
10
|
-
|
11
|
-
def initialize
|
12
|
-
@file = self.class.file
|
13
|
-
@hash = File.exists?(@file) ? YAML.load_file(@file) : {}
|
14
|
-
end
|
15
|
-
|
16
|
-
def save
|
17
|
-
File.open(@file, 'w') {|f| f.write(@hash.to_yaml) }
|
18
|
-
end
|
19
|
-
|
20
|
-
def tag(item, tag)
|
21
|
-
@hash[tag] ||= []
|
22
|
-
@hash[tag] << item
|
23
|
-
save
|
24
|
-
end
|
25
|
-
|
26
|
-
def remove_tag(item, tag)
|
27
|
-
@hash[tag] ||= []
|
28
|
-
@hash[tag].delete item
|
29
|
-
save
|
30
|
-
end
|
31
|
-
|
32
|
-
def list(tag)
|
33
|
-
@hash[tag] ||= []
|
34
|
-
@hash[tag]
|
35
|
-
end
|
36
|
-
|
37
|
-
def list_tags
|
38
|
-
@hash.keys.sort
|
39
|
-
end
|
40
|
-
|
41
|
-
def tree
|
42
|
-
list_tags.map {|tag|
|
43
|
-
["#{tag}", list(tag).map {|t| " #{t}" }]
|
44
|
-
}.flatten
|
45
|
-
end
|
46
|
-
|
47
|
-
def delete_tags(*tags)
|
48
|
-
tags.each {|tag| @hash.delete(tag) }
|
49
|
-
save
|
50
|
-
end
|
8
|
+
def self.store(model = nil)
|
9
|
+
@store ||= Store.new(model)
|
51
10
|
end
|
52
11
|
|
53
|
-
def self.
|
54
|
-
@
|
55
|
-
|
56
|
-
|
57
|
-
class Runner < Thor
|
58
|
-
desc "add ITEM TAG", 'tag an item'
|
59
|
-
def add(item, tag)
|
60
|
-
Tag.store.tag(item, tag)
|
61
|
-
end
|
62
|
-
|
63
|
-
desc "rm ITEM TAG", 'remove tag from item'
|
64
|
-
def rm(item, tag)
|
65
|
-
Tag.store.remove_tag(item, tag)
|
66
|
-
end
|
67
|
-
|
68
|
-
desc "list TAG", 'list items by tag'
|
69
|
-
def list(tag)
|
70
|
-
puts Tag.store.list(tag)
|
71
|
-
end
|
72
|
-
|
73
|
-
method_option :rm, :type => :boolean, :desc => 'remove tags'
|
74
|
-
desc "tags", 'list or remove tags'
|
75
|
-
def tags(*args)
|
76
|
-
if options[:rm]
|
77
|
-
Tag.store.delete_tags(*args)
|
78
|
-
else
|
79
|
-
puts Tag.store.list_tags
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
desc "tree", 'print tags with their items underneath them'
|
84
|
-
def tree
|
85
|
-
puts Tag.store.tree
|
12
|
+
def self.home
|
13
|
+
@home ||= (ENV['TAG_HOME'] || File.join(Dir.home, '.tag')).tap do |dir|
|
14
|
+
FileUtils.mkdir_p(dir)
|
86
15
|
end
|
87
16
|
end
|
88
17
|
end
|
data/lib/tag/runner.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Tag
|
4
|
+
class Runner < Thor
|
5
|
+
def self.model_option
|
6
|
+
method_option :model, :type => :string, :aliases => '-m'
|
7
|
+
end
|
8
|
+
|
9
|
+
model_option
|
10
|
+
method_option :tags, :type => :array, :required => true, :aliases => '-t'
|
11
|
+
desc "add *ITEMS -t *TAGS", 'add tag(s) to item(s)'
|
12
|
+
def add(*items)
|
13
|
+
Tag.store(options[:model]).multi_tag(items, options[:tags])
|
14
|
+
end
|
15
|
+
|
16
|
+
model_option
|
17
|
+
method_option :tags, :type => :array, :required => true, :aliases => '-t'
|
18
|
+
desc "rm *ITEMS -t *TAGS", 'remove tag(s) from item(s)'
|
19
|
+
def rm(*items)
|
20
|
+
Tag.store(options[:model]).multi_remove_tag(items, options[:tags])
|
21
|
+
end
|
22
|
+
|
23
|
+
model_option
|
24
|
+
desc "list TAG", 'list items by tag'
|
25
|
+
def list(tag)
|
26
|
+
puts Tag.store(options[:model]).list(tag)
|
27
|
+
end
|
28
|
+
|
29
|
+
model_option
|
30
|
+
method_option :rm, :type => :boolean, :desc => 'remove tags'
|
31
|
+
desc "tags", 'list or remove tags'
|
32
|
+
def tags(*args)
|
33
|
+
if options[:rm]
|
34
|
+
Tag.store(options[:model]).delete_tags(*args)
|
35
|
+
else
|
36
|
+
puts Tag.store(options[:model]).list_tags
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
model_option
|
41
|
+
desc "tree", 'print tags with their items underneath them'
|
42
|
+
def tree
|
43
|
+
puts Tag.store(options[:model]).tree
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "models", 'list models'
|
47
|
+
def models
|
48
|
+
puts Tag::Store.models
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/tag/store.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Tag
|
4
|
+
class Store
|
5
|
+
|
6
|
+
def self.models
|
7
|
+
Dir["#{Tag.home}/*.yml"].map {|file| file[%r{([^/]+).yml$}, 1] }.sort
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(model = nil)
|
11
|
+
model ||= ENV['TAG_MODEL'] || 'default'
|
12
|
+
@file = File.join(Tag.home, "#{model}.yml")
|
13
|
+
@hash = File.exists?(@file) ? YAML.load_file(@file) : {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def save
|
17
|
+
File.open(@file, 'w') {|f| f.write(@hash.to_yaml) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def tag(item, tag)
|
21
|
+
@hash[tag] ||= []
|
22
|
+
@hash[tag] << item
|
23
|
+
end
|
24
|
+
|
25
|
+
def multi_tag(items, tags)
|
26
|
+
tags.each do |tag|
|
27
|
+
items.each do |item|
|
28
|
+
tag(item, tag)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
save
|
32
|
+
end
|
33
|
+
|
34
|
+
def multi_remove_tag(items, tags)
|
35
|
+
tags.each do |tag|
|
36
|
+
items.each do |item|
|
37
|
+
remove_tag(item, tag)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
save
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove_tag(item, tag)
|
44
|
+
@hash[tag] ||= []
|
45
|
+
@hash[tag].delete item
|
46
|
+
end
|
47
|
+
|
48
|
+
def list(tag)
|
49
|
+
@hash[tag] ||= []
|
50
|
+
@hash[tag]
|
51
|
+
end
|
52
|
+
|
53
|
+
def list_tags
|
54
|
+
@hash.keys.sort
|
55
|
+
end
|
56
|
+
|
57
|
+
def tree
|
58
|
+
list_tags.map {|tag|
|
59
|
+
["#{tag}", list(tag).map {|t| " #{t}" }]
|
60
|
+
}.flatten
|
61
|
+
end
|
62
|
+
|
63
|
+
def delete_tags(*tags)
|
64
|
+
tags.each {|tag| @hash.delete(tag) }
|
65
|
+
save
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
gem 'minitest' unless ENV['NO_RUBYGEMS']
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'tag'
|
4
|
+
require 'open3'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
ENV['TAG_HOME'] = File.dirname(__FILE__) + '/.tag'
|
8
|
+
|
9
|
+
module TestHelpers
|
10
|
+
attr_reader :stdout, :stderr, :process
|
11
|
+
|
12
|
+
def tag(cmd)
|
13
|
+
args = cmd.split(/\s+/)
|
14
|
+
args.unshift File.dirname(__FILE__) + '/../bin/tag'
|
15
|
+
args.unshift({'RUBYLIB' => "#{File.dirname(__FILE__)}/../lib:" +
|
16
|
+
ENV['RUBYLIB']})
|
17
|
+
@stdout, @stderr, @process = Open3.capture3(*args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class MiniTest::Unit::TestCase
|
22
|
+
include TestHelpers
|
23
|
+
end
|
24
|
+
|
25
|
+
MiniTest::Unit.after_tests { FileUtils.rm_rf(ENV['TAG_HOME']) }
|
data/spec/runner_spec.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/helper')
|
2
|
+
|
3
|
+
describe Tag::Runner do
|
4
|
+
def tagged(tag)
|
5
|
+
tag "list #{tag}"
|
6
|
+
stdout.split("\n")
|
7
|
+
end
|
8
|
+
|
9
|
+
before { FileUtils.rm_rf(ENV['TAG_HOME']) }
|
10
|
+
|
11
|
+
describe "add" do
|
12
|
+
it "adds a tag" do
|
13
|
+
tag 'add feynman -t physicist'
|
14
|
+
tagged('physicist').must_equal ["feynman"]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "adds two tags" do
|
18
|
+
tag 'add feynman -t physicist teacher'
|
19
|
+
tagged('physicist').must_equal ["feynman"]
|
20
|
+
tagged('physicist').must_equal ["feynman"]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "adds a tag to two items" do
|
24
|
+
tag 'add feynman fermi -t physicist'
|
25
|
+
tagged('physicist').must_equal %w{feynman fermi}
|
26
|
+
end
|
27
|
+
|
28
|
+
it "fails if no tag given" do
|
29
|
+
tag 'add um'
|
30
|
+
stderr.must_include 'required option'
|
31
|
+
end
|
32
|
+
|
33
|
+
it "with model option adds to a different model" do
|
34
|
+
tag 'add feynman -t physicist -m physics'
|
35
|
+
tagged('physicist -m physics').must_equal ['feynman']
|
36
|
+
tagged('physicist').must_equal []
|
37
|
+
end
|
38
|
+
|
39
|
+
it "with $TAG_MODEL adds to a different model" do
|
40
|
+
ENV['TAG_MODEL'] = 'physica'
|
41
|
+
tag 'add feynman -t physicist'
|
42
|
+
tagged('physicist').must_equal ['feynman']
|
43
|
+
tagged('physicist -m default').must_equal []
|
44
|
+
ENV['TAG_MODEL'] = nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "rm" do
|
49
|
+
it "removes a tag" do
|
50
|
+
tag 'add newton -t physicist fig'
|
51
|
+
tag 'rm newton -t fig'
|
52
|
+
tagged('physicist').must_equal ['newton']
|
53
|
+
tagged('fig').must_equal []
|
54
|
+
end
|
55
|
+
|
56
|
+
it "removes two tags" do
|
57
|
+
tag 'add newton -t physicist fig sir'
|
58
|
+
tag 'rm newton -t fig sir'
|
59
|
+
tagged('physicist').must_equal ['newton']
|
60
|
+
tagged('fig').must_equal []
|
61
|
+
tagged('sir').must_equal []
|
62
|
+
end
|
63
|
+
|
64
|
+
it "removes two items from a tag" do
|
65
|
+
tag 'add newton -t physicist fig'
|
66
|
+
tag 'add tree -t fig'
|
67
|
+
tag 'rm newton tree -t fig'
|
68
|
+
tagged('fig').must_equal []
|
69
|
+
end
|
70
|
+
|
71
|
+
it "fails if no tag given" do
|
72
|
+
tag 'rm um'
|
73
|
+
stderr.must_include 'required option'
|
74
|
+
end
|
75
|
+
|
76
|
+
it "with model option removes a tag from other model" do
|
77
|
+
tag 'add newton -t physicist fig -m physics'
|
78
|
+
tag 'rm newton -t fig -m physics'
|
79
|
+
tagged('physicist -m physics').must_equal ['newton']
|
80
|
+
tagged('physicist').must_equal []
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "tags" do
|
85
|
+
it "by default lists all tags" do
|
86
|
+
tag 'add fermi -t italian german irish'
|
87
|
+
tag 'tags'
|
88
|
+
stdout.split("\n").must_equal %w{german irish italian}
|
89
|
+
end
|
90
|
+
|
91
|
+
it "with model option lists all tags for another model" do
|
92
|
+
tag 'add fermi -t italian german irish -m physicist'
|
93
|
+
tag 'tags -m physicist'
|
94
|
+
stdout.split("\n").must_equal %w{german irish italian}
|
95
|
+
tag 'tags'
|
96
|
+
stdout.split("\n").must_equal []
|
97
|
+
end
|
98
|
+
|
99
|
+
it "with rm option deletes tags" do
|
100
|
+
tag 'add fermi -t italian german irish'
|
101
|
+
tag 'add davinci -t italian german irish'
|
102
|
+
tag 'tags german irish --rm'
|
103
|
+
tagged('german').must_equal []
|
104
|
+
tagged('irish').must_equal []
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it "tree prints tree" do
|
109
|
+
tag 'add child -t parent1 parent2'
|
110
|
+
tag 'tree'
|
111
|
+
stdout.chomp.must_equal [
|
112
|
+
'parent1', ' child', 'parent2', ' child'
|
113
|
+
].join("\n")
|
114
|
+
end
|
115
|
+
|
116
|
+
it "models prints list of models" do
|
117
|
+
tag 'add feynman -t funny -m physicist'
|
118
|
+
tag 'add cat -t independent'
|
119
|
+
tag 'models'
|
120
|
+
stdout.split("\n").must_equal ['default', 'physicist']
|
121
|
+
end
|
122
|
+
end
|
metadata
CHANGED
@@ -1,40 +1,44 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: tag
|
3
|
-
version: !ruby/object:Gem::Version
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
4
5
|
prerelease:
|
5
|
-
version: 0.1.0
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Gabriel Horner
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
dependencies:
|
16
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2011-12-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
17
15
|
name: thor
|
18
|
-
|
19
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: &2152084180 !ruby/object:Gem::Requirement
|
20
17
|
none: false
|
21
|
-
requirements:
|
18
|
+
requirements:
|
22
19
|
- - ~>
|
23
|
-
- !ruby/object:Gem::Version
|
20
|
+
- !ruby/object:Gem::Version
|
24
21
|
version: 0.14.6
|
25
22
|
type: :runtime
|
26
|
-
|
27
|
-
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2152084180
|
25
|
+
description: This project lets you tag anything from the commandline. The `tag` executable
|
26
|
+
provides a consistent way to add, remove and modify tags for tagged items. The goal
|
27
|
+
is to make tagging dead simple and usable by other commandline apps. In making tags
|
28
|
+
a first class nix citizen, perhaps they will see the light of day.
|
28
29
|
email: gabriel.horner@gmail.com
|
29
|
-
executables:
|
30
|
+
executables:
|
30
31
|
- tag
|
31
32
|
extensions: []
|
32
|
-
|
33
|
-
extra_rdoc_files:
|
33
|
+
extra_rdoc_files:
|
34
34
|
- README.md
|
35
35
|
- LICENSE.txt
|
36
|
-
files:
|
36
|
+
files:
|
37
|
+
- lib/tag/runner.rb
|
38
|
+
- lib/tag/store.rb
|
37
39
|
- lib/tag.rb
|
40
|
+
- spec/helper.rb
|
41
|
+
- spec/runner_spec.rb
|
38
42
|
- bin/tag
|
39
43
|
- LICENSE.txt
|
40
44
|
- CHANGELOG.rdoc
|
@@ -42,33 +46,29 @@ files:
|
|
42
46
|
- deps.rip
|
43
47
|
- Rakefile
|
44
48
|
- .gemspec
|
45
|
-
has_rdoc: true
|
46
49
|
homepage: http://github.com/cldwalker/tag
|
47
|
-
licenses:
|
50
|
+
licenses:
|
48
51
|
- MIT
|
49
52
|
post_install_message:
|
50
53
|
rdoc_options: []
|
51
|
-
|
52
|
-
require_paths:
|
54
|
+
require_paths:
|
53
55
|
- lib
|
54
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
57
|
none: false
|
56
|
-
requirements:
|
57
|
-
- -
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version:
|
60
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
63
|
none: false
|
62
|
-
requirements:
|
63
|
-
- -
|
64
|
-
- !ruby/object:Gem::Version
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
65
67
|
version: 1.3.6
|
66
68
|
requirements: []
|
67
|
-
|
68
69
|
rubyforge_project:
|
69
|
-
rubygems_version: 1.
|
70
|
+
rubygems_version: 1.8.10
|
70
71
|
signing_key:
|
71
72
|
specification_version: 3
|
72
73
|
summary: tag anything from the commandline
|
73
74
|
test_files: []
|
74
|
-
|