dircat 0.1.12 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +1 -1
- data/README.md +31 -6
- data/bin/dircat +5 -1
- data/bin/scat +9 -0
- data/dircat.gemspec +48 -15
- data/examples/example.rb +2 -1
- data/lib/dircat.rb +29 -34
- data/lib/dircat/cat_on_sqlite/cat_on_sqlite.rb +177 -0
- data/lib/dircat/cat_on_sqlite/directory_visitor.rb +106 -0
- data/lib/dircat/cat_on_sqlite/migration/00_create_dircat_properties.rb +13 -0
- data/lib/dircat/cat_on_sqlite/migration/01_create_categories.rb +12 -0
- data/lib/dircat/cat_on_sqlite/migration/02_create_images.rb +16 -0
- data/lib/dircat/cat_on_sqlite/migration/03_create_items.rb +23 -0
- data/lib/dircat/cat_on_sqlite/migration/04_create_taggings.rb +13 -0
- data/lib/dircat/cat_on_sqlite/migration/05_create_tags.rb +13 -0
- data/lib/dircat/cat_on_sqlite/model/category.rb +16 -0
- data/lib/dircat/cat_on_sqlite/model/image.rb +24 -0
- data/lib/dircat/cat_on_sqlite/model/item.rb +19 -0
- data/lib/dircat/cat_on_sqlite/model/tag.rb +58 -0
- data/lib/dircat/cat_on_sqlite/model/tagging.rb +9 -0
- data/lib/dircat/cat_on_sqlite/simple_cataloger_error.rb +9 -0
- data/lib/dircat/cat_on_sqlite_cli/cli_cat.rb +25 -0
- data/lib/dircat/cat_on_sqlite_cli/cli_server.rb +65 -0
- data/lib/dircat/cat_on_sqlite_cli/cmd_create.rb +56 -0
- data/lib/dircat/cat_on_sqlite_cli/cmd_list.rb +72 -0
- data/lib/dircat/cat_on_sqlite_cli/cmd_server.rb +62 -0
- data/lib/dircat/cat_on_sqlite_cli/cmd_update.rb +49 -0
- data/lib/dircat/{cat.rb → cat_on_yaml/cat_on_yaml.rb} +22 -10
- data/lib/dircat/cat_on_yaml/entry.rb +64 -0
- data/lib/dircat/{cli → cat_on_yaml_cli}/cli_dircat.rb +0 -0
- data/lib/dircat/{cli → cat_on_yaml_cli}/command_build.rb +53 -12
- data/lib/dircat/{cli → cat_on_yaml_cli}/command_diff.rb +4 -4
- data/lib/dircat/{cli → cat_on_yaml_cli}/command_query.rb +16 -2
- data/lib/dircat/config.rb +25 -0
- data/lib/dircat/extensions.rb +26 -0
- data/lib/dircat/server/helpers.rb +60 -0
- data/lib/dircat/server/my_static.rb +40 -0
- data/lib/dircat/server/web_server.rb +176 -0
- data/lib/dircat/version.rb +2 -1
- data/lib/dircat_on_sqlite.rb +31 -0
- data/lib/dircat_on_sqlite_cli.rb +37 -0
- data/lib/simple_cataloger_dm/cli.rb +32 -0
- data/lib/simple_cataloger_dm/core.rb +43 -0
- data/lib/simple_cataloger_dm/core/catalog.rb +141 -0
- data/lib/simple_cataloger_dm/core/directory_visitor.rb +107 -0
- data/lib/simple_cataloger_dm/core/extensions.rb +26 -0
- data/lib/simple_cataloger_dm/core/simple_cataloger_error.rb +9 -0
- data/lib/simple_cataloger_dm/models/category.rb +21 -0
- data/lib/simple_cataloger_dm/models/image.rb +30 -0
- data/lib/simple_cataloger_dm/models/item.rb +24 -0
- data/lib/simple_cataloger_dm/models/tag.rb +62 -0
- data/lib/simple_cataloger_dm/models/tagging.rb +11 -0
- data/lib/simple_cataloger_dm/server/helpers.rb +60 -0
- data/lib/simple_cataloger_dm/server/my_static.rb +40 -0
- data/lib/simple_cataloger_dm/server/web_server.rb +171 -0
- data/spec/dircat/cat_on_sqlite/cat_on_sqlite_spec.rb +41 -0
- data/spec/dircat/cat_on_sqlite_web/web_server_spec.rb +30 -0
- data/spec/dircat/{cat_spec.rb → cat_on_yaml/cat_on_yaml_spec.rb} +20 -14
- data/spec/dircat/{cli → cat_on_yaml_cli}/cli_dircat_spec.rb +0 -0
- data/spec/dircat/{cli → cat_on_yaml_cli}/command_build_spec.rb +4 -4
- data/spec/dircat/{cli → cat_on_yaml_cli}/command_diff_spec.rb +0 -0
- data/spec/dircat/{cli → cat_on_yaml_cli}/command_query_spec.rb +1 -1
- data/spec/fixtures/certified_output/cat_dir1_20120811.yaml +25 -0
- data/spec/fixtures/certified_output/cat_dir2_20120811.yaml +34 -0
- data/spec/fixtures/certified_output/{dircat1.yaml → dircat1_version_0.1.yaml} +0 -0
- data/spec/fixtures/certified_output/{dircat2.yaml → dircat2_version_0.1.yaml} +0 -0
- data/spec/fixtures/dir4/file1.txt +1 -0
- data/spec/fixtures/dir4/file2.txt +1 -0
- data/spec/fixtures/dir4/subdir/file3.txt +1 -0
- data/spec/fixtures/films/A-Z/A/Arancia meccanica [Stanley Kubrick][1971]/folder.jpg +0 -0
- data/spec/fixtures/films/A-Z/P/Plan 9 [Edward Wood][1959]/folder.jpg +0 -0
- data/spec/fixtures/films/Directors/[Akira Kurosawa]/[1940] Dersu Uzala [5]/folder.jpg +0 -0
- data/spec/fixtures/films/Directors/[Akira Kurosawa]/[1965] Barbarossa [Toshiro Mifune][4]/folder.jpg +0 -0
- data/spec/fixtures/films/Directors/[Federico Fellini]/[1963] 8 e mezzo [Marcello Mastroianni][Claudia Cardinale][5]/folder.jpg +0 -0
- data/spec/fixtures/films/Directors/[Woody Allen]/[1977] Annie Hall [Diane Keaton][5]/folder.jpg +0 -0
- data/{.gemtest → spec/fixtures/readme.txt} +0 -0
- data/spec/fixtures/tmp/test_ar.sqlite3 +0 -0
- data/spec/fixtures/tmp/test_ar.yml +7 -0
- data/spec/generate_directories_to_catalog.rb +80 -0
- data/spec/generate_mysql_catalog.rb +124 -0
- data/spec/spec_helper.rb +29 -0
- data/tasks/rspec.rake +0 -2
- metadata +390 -112
- data/lib/dircat/entry.rb +0 -61
- data/lib/dircat/extension_md5.rb +0 -25
- data/lib/dircat/extension_numeric.rb +0 -15
- data/spec/dircat/md5_spec.rb +0 -8
- data/spec/dircat/numeric_spec.rb +0 -13
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# std lib
|
4
|
+
#
|
5
|
+
require 'pp'
|
6
|
+
require 'optparse'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'ostruct'
|
9
|
+
|
10
|
+
#
|
11
|
+
# rubygems
|
12
|
+
#
|
13
|
+
gem "treevisitor", "0.2.2"
|
14
|
+
require 'treevisitor'
|
15
|
+
|
16
|
+
gem "optparse-command", "0.1.7"
|
17
|
+
require 'optparse-command'
|
18
|
+
|
19
|
+
require 'dm-core'
|
20
|
+
require 'dm-migrations'
|
21
|
+
|
22
|
+
#
|
23
|
+
# simple catalog
|
24
|
+
#
|
25
|
+
require 'simple_cataloger/version'
|
26
|
+
require 'simple_cataloger/simple_cataloger_error'
|
27
|
+
require 'simple_cataloger/extensions'
|
28
|
+
|
29
|
+
#
|
30
|
+
# db models
|
31
|
+
#
|
32
|
+
require 'simple_cataloger/models/item'
|
33
|
+
require 'simple_cataloger/models/tag'
|
34
|
+
require 'simple_cataloger/models/tagging'
|
35
|
+
require 'simple_cataloger/models/category'
|
36
|
+
require 'simple_cataloger/models/image'
|
37
|
+
|
38
|
+
#
|
39
|
+
# app
|
40
|
+
#
|
41
|
+
require 'simple_cataloger/directory_visitor'
|
42
|
+
require 'simple_cataloger/catalog'
|
43
|
+
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module SimpleCataloger
|
3
|
+
class Catalog
|
4
|
+
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
|
10
|
+
# find directory where to store catalog data
|
11
|
+
home_dir = ENV['HOME'] || ENV['APPDATA']
|
12
|
+
if RUBY_PLATFORM =~ /linux/
|
13
|
+
catalogs_dir = File.expand_path(File.join(home_dir, ".simple_cataloger"))
|
14
|
+
else
|
15
|
+
catalogs_dir = File.expand_path(File.join(home_dir, "simple_cataloger"))
|
16
|
+
end
|
17
|
+
Dir.mkdir(catalogs_dir) unless File.directory?(catalogs_dir)
|
18
|
+
|
19
|
+
db_filepath = File.join(catalogs_dir, "#{name}.sqlite3")
|
20
|
+
db_log_filepath = File.join(catalogs_dir, "#{name}.log")
|
21
|
+
@config_filepath = File.join(catalogs_dir, "#{name}.yml")
|
22
|
+
setup_db(db_filepath, db_log_filepath)
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Create a new catalog
|
27
|
+
# @param array of directories
|
28
|
+
#
|
29
|
+
def create(*catalog_roots)
|
30
|
+
if File.exist? @config_filepath
|
31
|
+
raise SimpleCatalogerError, "cannot create already existent catalog '#{@name}'"
|
32
|
+
end
|
33
|
+
@config = {
|
34
|
+
:roots => catalog_roots,
|
35
|
+
:ignore => ['sub', 'subtitles', 'images'],
|
36
|
+
:version => SimpleCataloger::VERSION
|
37
|
+
}
|
38
|
+
write_config
|
39
|
+
update
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Rebuild catalog
|
44
|
+
#
|
45
|
+
def update
|
46
|
+
unless File.exist? @config_filepath
|
47
|
+
raise "cannot update catalog #{@name}"
|
48
|
+
end
|
49
|
+
read_config
|
50
|
+
#
|
51
|
+
# migrate db
|
52
|
+
#
|
53
|
+
DataMapper.auto_migrate!
|
54
|
+
#DataMapper.auto_upgrade!
|
55
|
+
|
56
|
+
#
|
57
|
+
# Initialize categories
|
58
|
+
#
|
59
|
+
if @config[:categories]
|
60
|
+
# pp @config[:categories]
|
61
|
+
@config[:categories].each_pair do |category, tags|
|
62
|
+
# puts category
|
63
|
+
cat = Category.first_or_create(:name => category)
|
64
|
+
tags.each do |name|
|
65
|
+
tag = Tag.first_or_create(:name => name)
|
66
|
+
tag.category = cat
|
67
|
+
tag.save
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# read catalog root
|
74
|
+
#
|
75
|
+
@config[:roots].each do |root|
|
76
|
+
dtw = TreeRb::DirTreeWalker.new(root)
|
77
|
+
dtw.ignore /^\./
|
78
|
+
@config[:ignore].each do |i|
|
79
|
+
dtw.ignore i
|
80
|
+
end
|
81
|
+
dtw.visit_file=true
|
82
|
+
dtw.run(DirectoryVisitor.new(root))
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# cache rating tag
|
87
|
+
#
|
88
|
+
rating = Category.first(:name => "rating")
|
89
|
+
if rating
|
90
|
+
Item.all.each do |item|
|
91
|
+
t = item.tags.find { |t| t.category == rating }
|
92
|
+
if t
|
93
|
+
item.rating = t.name.to_i
|
94
|
+
item.save
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def setup_db(db_path, db_log_filepath)
|
101
|
+
#TODO: logger usabile per loggare altri eventi oltre a quelli del db
|
102
|
+
DataMapper::Logger.new(db_log_filepath, :debug)
|
103
|
+
DataMapper.setup(:default, "sqlite3:#{db_path}")
|
104
|
+
DataMapper.finalize
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# array of roots
|
109
|
+
#
|
110
|
+
def roots
|
111
|
+
read_config unless @config
|
112
|
+
@config[:roots]
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# update config file with tag category list and association from category to tag name,
|
117
|
+
# so to not lose the association next time the catalog is rebuilt (updated)
|
118
|
+
#
|
119
|
+
def update_categories
|
120
|
+
h = {}
|
121
|
+
Category.all.each do |category|
|
122
|
+
next if ["rating", "year", "unknown"].include?(category.name)
|
123
|
+
h[category.name] = category.tags.collect { |tag| tag.name }
|
124
|
+
end
|
125
|
+
@config[:categories] = h
|
126
|
+
end
|
127
|
+
|
128
|
+
def write_config
|
129
|
+
File.open(@config_filepath, "w") { |f| f.write @config.to_yaml }
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def read_config
|
135
|
+
#noinspection RubyResolve
|
136
|
+
@config = YAML.load(File.open(@config_filepath))
|
137
|
+
end
|
138
|
+
|
139
|
+
end # class
|
140
|
+
|
141
|
+
end # module SimpleCatalog
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module SimpleCataloger
|
2
|
+
|
3
|
+
#
|
4
|
+
# Find directory without subdirectories
|
5
|
+
#
|
6
|
+
class DirectoryVisitor
|
7
|
+
|
8
|
+
def initialize(catalog_root)
|
9
|
+
super
|
10
|
+
@stack = []
|
11
|
+
@files = []
|
12
|
+
@nr = 0
|
13
|
+
@catalog_root = catalog_root
|
14
|
+
end
|
15
|
+
|
16
|
+
def enter_node(pathname)
|
17
|
+
# each directory is associated to a number,
|
18
|
+
# so when exit_tree_node is called and the number @nr is not increased
|
19
|
+
# the directory doesn't have subdirectories
|
20
|
+
@nr += 1
|
21
|
+
|
22
|
+
dirname = File.basename(pathname)
|
23
|
+
tags = Tag.extract_tags(dirname)
|
24
|
+
tags = tags.concat(@stack.last.tags).uniq unless @stack.empty?
|
25
|
+
info = OpenStruct.new(:nr => @nr, :pathname => pathname, :tags => tags, :name => Tag.extract_name(dirname))
|
26
|
+
|
27
|
+
@files = []
|
28
|
+
@stack.push(info)
|
29
|
+
end
|
30
|
+
|
31
|
+
def exit_node(pathname)
|
32
|
+
info = @stack.pop
|
33
|
+
|
34
|
+
if info.nr == @nr
|
35
|
+
# this directory doesn't have subdirectories
|
36
|
+
name = info.name
|
37
|
+
tag_names = info.tags
|
38
|
+
puts name
|
39
|
+
# pp tags
|
40
|
+
|
41
|
+
# every name must be unique, two films cannot have the some name
|
42
|
+
if Item.first( :name => name )
|
43
|
+
raise "item #{name} is not unique path #{pathname}"
|
44
|
+
end
|
45
|
+
|
46
|
+
item = Item.create(
|
47
|
+
:name => name,
|
48
|
+
:added_at => File.lstat(pathname).ctime,
|
49
|
+
:path => pathname,
|
50
|
+
:path_from_catalog_root => pathname[File.dirname(@catalog_root).length..-1]
|
51
|
+
)
|
52
|
+
|
53
|
+
#
|
54
|
+
# Associate item and tags
|
55
|
+
#
|
56
|
+
unknown = Category.first_or_create(:name => "unknown")
|
57
|
+
tag_names.each do |tag_name|
|
58
|
+
tag = Tag.first(:name => tag_name)
|
59
|
+
unless tag
|
60
|
+
tag = Tag.match_category(tag_name)
|
61
|
+
unless tag
|
62
|
+
tag = Tag.first_or_create(
|
63
|
+
:name => tag_name,
|
64
|
+
:category => unknown
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
Tagging.first_or_create(:item=>item, :tag=> tag)
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Searches images
|
73
|
+
#
|
74
|
+
@files.each do |pathname|
|
75
|
+
next unless pathname.match /(jpg|jpeg)$/
|
76
|
+
|
77
|
+
image = Image.first_or_create(
|
78
|
+
:path => pathname,
|
79
|
+
:path_from_catalog_root => pathname[File.dirname(@catalog_root).length..-1]
|
80
|
+
)
|
81
|
+
item.images << image
|
82
|
+
|
83
|
+
filename = File.basename(pathname)
|
84
|
+
tag_names = Tag.extract_tags(filename)
|
85
|
+
tag_names.each do |tag_name|
|
86
|
+
tag = Tag.first(:name => tag_name)
|
87
|
+
if tag
|
88
|
+
tag.images << image
|
89
|
+
tag.save
|
90
|
+
else
|
91
|
+
puts "WARNING: tag '#{tag_name}' not found"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
item.save
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
#
|
101
|
+
# called when visit leaf node
|
102
|
+
#
|
103
|
+
def visit_leaf(pathname)
|
104
|
+
@files << pathname
|
105
|
+
end
|
106
|
+
end # class
|
107
|
+
end # module SimpleCataloger
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module SimpleCataloger
|
3
|
+
module CoreExtensions
|
4
|
+
module Array
|
5
|
+
def extract_options!
|
6
|
+
last.is_a?(::Hash) ? pop : {}
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Array
|
13
|
+
include SimpleCataloger::CoreExtensions::Array
|
14
|
+
end
|
15
|
+
|
16
|
+
module URI
|
17
|
+
class << self
|
18
|
+
|
19
|
+
def parse_with_safety(uri)
|
20
|
+
parse_without_safety uri.gsub('[', '%5B').gsub(']', '%5D')
|
21
|
+
end
|
22
|
+
|
23
|
+
# alias parse_without_safety parse
|
24
|
+
# alias parse parse_with_safety
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module SimpleCataloger
|
3
|
+
|
4
|
+
class Category
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
property :id, Serial
|
8
|
+
property :name, String
|
9
|
+
|
10
|
+
has n, :tags, :order => [:name]
|
11
|
+
|
12
|
+
def self.order_by_name
|
13
|
+
all(:order => [:name])
|
14
|
+
end
|
15
|
+
|
16
|
+
def items
|
17
|
+
tags.collect { |t| t.items }.flatten
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module SimpleCataloger
|
3
|
+
|
4
|
+
class Image
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
property :id, Serial
|
8
|
+
property :path, String
|
9
|
+
property :path_from_catalog_root, String
|
10
|
+
|
11
|
+
belongs_to :item, :required => false
|
12
|
+
belongs_to :tag, :required => false
|
13
|
+
|
14
|
+
def to_json(*a)
|
15
|
+
{:item => if item then
|
16
|
+
item.name
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end,
|
20
|
+
:tag => if tag then
|
21
|
+
tag.name
|
22
|
+
else
|
23
|
+
nil
|
24
|
+
end,
|
25
|
+
:src => path_from_catalog_root}.to_json(*a)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module SimpleCataloger
|
3
|
+
|
4
|
+
class Item
|
5
|
+
include DataMapper::Resource
|
6
|
+
property :id, Serial
|
7
|
+
property :name, String
|
8
|
+
|
9
|
+
property :added_at, Date
|
10
|
+
property :path, String
|
11
|
+
property :path_from_catalog_root, String
|
12
|
+
property :rating, Integer
|
13
|
+
property :flagged_at, Date
|
14
|
+
|
15
|
+
has n, :taggings
|
16
|
+
has n, :tags, :through => :taggings
|
17
|
+
has n, :images
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
@name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module SimpleCataloger
|
3
|
+
|
4
|
+
class Tag
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
property :id, Serial
|
8
|
+
property :name, String
|
9
|
+
|
10
|
+
belongs_to :category, 'Category', :child_key => [:category_id]
|
11
|
+
has n, :taggings
|
12
|
+
has n, :items, :through => :taggings, :order => [:name]
|
13
|
+
has n, :images
|
14
|
+
|
15
|
+
##################################################
|
16
|
+
#
|
17
|
+
|
18
|
+
RE_TAG = /\[([^\]]+)\]/
|
19
|
+
|
20
|
+
# extracts tags from directory or file name
|
21
|
+
def self.extract_tags(dir_or_file_name)
|
22
|
+
tags = []
|
23
|
+
match_data = dir_or_file_name.match(RE_TAG)
|
24
|
+
while match_data
|
25
|
+
tags << match_data[1]
|
26
|
+
match_data = dir_or_file_name.match(RE_TAG, match_data.end(0))
|
27
|
+
end
|
28
|
+
tags
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.extract_name(dir_or_file_name)
|
32
|
+
dir_or_file_name.gsub(Tag::RE_TAG, '').strip
|
33
|
+
end
|
34
|
+
|
35
|
+
RE_RATING = /^\d$/
|
36
|
+
RE_YEAR = /^\d\d\d\d$/
|
37
|
+
|
38
|
+
MATCHES = [
|
39
|
+
[RE_RATING, "rating", proc { |tag_name| tag_name.to_i }],
|
40
|
+
[RE_YEAR, "year", proc { |tag_name| tag_name.to_i }]
|
41
|
+
]
|
42
|
+
|
43
|
+
#
|
44
|
+
# decode standard tags
|
45
|
+
#
|
46
|
+
def self.match_category(tag_name)
|
47
|
+
MATCHES.each do |re, category_name, convert|
|
48
|
+
if re.match tag_name
|
49
|
+
category = Category.first_or_create(:name => category_name)
|
50
|
+
tag = Tag.first_or_create(
|
51
|
+
:name => convert.call(tag_name),
|
52
|
+
:category => category
|
53
|
+
)
|
54
|
+
return tag
|
55
|
+
end
|
56
|
+
end
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
end # class Tag
|
61
|
+
|
62
|
+
end # module SimpleCatalog
|