artbase 0.2.2 → 0.3.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/Manifest.txt +0 -10
- data/Rakefile +4 -3
- data/lib/artbase/tool.rb +28 -13
- data/lib/artbase/version.rb +2 -2
- data/lib/artbase.rb +12 -78
- metadata +11 -21
- data/lib/artbase/attributes.rb +0 -83
- data/lib/artbase/collection/base.rb +0 -329
- data/lib/artbase/collection/image.rb +0 -39
- data/lib/artbase/collection/opensea.rb +0 -297
- data/lib/artbase/collection/token.rb +0 -400
- data/lib/artbase/collection.rb +0 -12
- data/lib/artbase/helper.rb +0 -169
- data/lib/artbase/image/sample.rb +0 -31
- data/lib/artbase/image.rb +0 -31
- data/lib/artbase/retry.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1060552f98ae102ea9d98593be2cb3dafd13d07e7d5ef20eb77cc4176c7fd94
|
4
|
+
data.tar.gz: 239322b6fcab84714305f14c2bfc91a488dba35b653b906f484f7690a99abec4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c99a8d16e494bf0572db121c2b19843d2db20b8084df874ce123c10a99cfc39a3508733f9b7eb8da450cf95bea019fe8de66cbbb7771f8e007fbaf828bcb65b
|
7
|
+
data.tar.gz: aadfdb81650d27d1f8830755b41c5ab314b2b3de9efe54c76a2b2afa121809123f686438d415d0fb55df02b5fdf498ba6bd9333a8b9e8db4fd4d6dc60e7fad44
|
data/Manifest.txt
CHANGED
@@ -4,15 +4,5 @@ README.md
|
|
4
4
|
Rakefile
|
5
5
|
bin/artbase
|
6
6
|
lib/artbase.rb
|
7
|
-
lib/artbase/attributes.rb
|
8
|
-
lib/artbase/collection.rb
|
9
|
-
lib/artbase/collection/base.rb
|
10
|
-
lib/artbase/collection/image.rb
|
11
|
-
lib/artbase/collection/opensea.rb
|
12
|
-
lib/artbase/collection/token.rb
|
13
|
-
lib/artbase/helper.rb
|
14
|
-
lib/artbase/image.rb
|
15
|
-
lib/artbase/image/sample.rb
|
16
|
-
lib/artbase/retry.rb
|
17
7
|
lib/artbase/tool.rb
|
18
8
|
lib/artbase/version.rb
|
data/Rakefile
CHANGED
@@ -26,9 +26,10 @@ Hoe.spec 'artbase' do
|
|
26
26
|
self.history_file = 'CHANGELOG.md'
|
27
27
|
|
28
28
|
self.extra_deps = [
|
29
|
-
['cocos', '>= 0.1
|
30
|
-
['
|
31
|
-
['
|
29
|
+
['artbase-cocos', '>= 0.0.1'],
|
30
|
+
# ['artbase-importers', '>= 0.0.1'], ## note: make "heavy" sql/sqlite db support "soft" dependency
|
31
|
+
['artserve'],
|
32
|
+
['artq'],
|
32
33
|
]
|
33
34
|
|
34
35
|
self.licenses = ['Public Domain']
|
data/lib/artbase/tool.rb
CHANGED
@@ -74,19 +74,7 @@ class Tool
|
|
74
74
|
if File.exist?( "./#{name}/collection.yml" )
|
75
75
|
path = "./#{name}/collection.yml"
|
76
76
|
puts "==> reading collection config >#{path}<..."
|
77
|
-
|
78
|
-
|
79
|
-
## todo - use TokenCollection.read( ) or such -- why? why not?
|
80
|
-
## or TokenCollection.build( hash ) ?? - why? why not?
|
81
|
-
self.collection = TokenCollection.new(
|
82
|
-
config['slug'],
|
83
|
-
config['count'],
|
84
|
-
token_base: config['token_base'],
|
85
|
-
image_base: config['image_base'],
|
86
|
-
format: config['format'],
|
87
|
-
source: config['source'],
|
88
|
-
offset: config['offset'] || 0
|
89
|
-
)
|
77
|
+
self.collection = TokenCollection.read( path )
|
90
78
|
else
|
91
79
|
## todo/check: keep config.rb alternate name - why? why not?
|
92
80
|
## or use collection.rb only ???
|
@@ -131,6 +119,8 @@ class Tool
|
|
131
119
|
dump_attributes
|
132
120
|
elsif ['x', 'exp', 'export'].include?( command )
|
133
121
|
export_attributes
|
122
|
+
elsif ['b', 'build'].include?( command )
|
123
|
+
build_database
|
134
124
|
elsif ['c', 'composite'].include?( command )
|
135
125
|
make_composite( limit: options[ :limit],
|
136
126
|
mirror: options[ :mirror ])
|
@@ -145,6 +135,31 @@ class Tool
|
|
145
135
|
puts "bye"
|
146
136
|
end
|
147
137
|
|
138
|
+
|
139
|
+
|
140
|
+
def self.build_database
|
141
|
+
puts "===> build database"
|
142
|
+
|
143
|
+
### note. load database "heavy" machinery only on-demand
|
144
|
+
## make it a "soft" dependency for now - why? why not?
|
145
|
+
require 'artbase-importers'
|
146
|
+
|
147
|
+
|
148
|
+
slug = @collection.slug
|
149
|
+
|
150
|
+
importer = Importer.read( "./#{slug}/build.rb" )
|
151
|
+
|
152
|
+
columns = importer.metadata_columns
|
153
|
+
pp columns
|
154
|
+
|
155
|
+
Database.connect( "./#{slug}/artbase.db" )
|
156
|
+
Database.auto_migrate!( columns )
|
157
|
+
|
158
|
+
@collection.import( importer )
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
|
148
163
|
def self.make_composite( limit: nil, mirror: false )
|
149
164
|
puts "==> make composite"
|
150
165
|
@collection.make_composite( limit: limit, mirror: mirror )
|
data/lib/artbase/version.rb
CHANGED
data/lib/artbase.rb
CHANGED
@@ -1,78 +1,12 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
### add (shared) "global" config
|
16
|
-
module Artbase
|
17
|
-
class Configuration
|
18
|
-
|
19
|
-
#######################
|
20
|
-
## accessors
|
21
|
-
|
22
|
-
## todo/check: keep trailing / in ipfs_gateway - why? why not?
|
23
|
-
def ipfs_gateway() @ipfs_gateway || 'https://ipfs.io/ipfs/'; end
|
24
|
-
def ipfs_gateway=(value) @ipfs_gateway = value; end
|
25
|
-
end # class Configuration
|
26
|
-
|
27
|
-
|
28
|
-
## lets you use
|
29
|
-
## Artbase.configure do |config|
|
30
|
-
## config.ipfs_gateway = 'https://cloudflare-ipfs.com/ipfs/'
|
31
|
-
## end
|
32
|
-
def self.configure() yield( config ); end
|
33
|
-
def self.config() @config ||= Configuration.new; end
|
34
|
-
end # module Artbase
|
35
|
-
|
36
|
-
|
37
|
-
require_relative 'artbase/image'
|
38
|
-
|
39
|
-
|
40
|
-
require_relative 'artbase/helper'
|
41
|
-
require_relative 'artbase/retry' ## (global) retry_on_error helper
|
42
|
-
|
43
|
-
require_relative 'artbase/collection'
|
44
|
-
require_relative 'artbase/attributes'
|
45
|
-
|
46
|
-
|
47
|
-
require_relative 'artbase/tool'
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
######
|
52
|
-
## move to helper - why? why not?
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
## quick ipfs (interplanetary file system) hack
|
57
|
-
## - make more reuseable
|
58
|
-
## - different name e.g. ipfs_to_http or such - why? why not?
|
59
|
-
## change/rename parameter str to url or suc - why? why not?
|
60
|
-
def handle_ipfs( str, normalize: true,
|
61
|
-
ipfs_gateway: Artbase.config.ipfs_gateway )
|
62
|
-
|
63
|
-
if normalize && str.start_with?( 'https://ipfs.io/ipfs/' )
|
64
|
-
str = str.sub( 'https://ipfs.io/ipfs/', 'ipfs://' )
|
65
|
-
end
|
66
|
-
|
67
|
-
if str.start_with?( 'ipfs://' )
|
68
|
-
str = str.sub( 'ipfs://', ipfs_gateway ) # use/replace with public gateway
|
69
|
-
end
|
70
|
-
str
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
puts Artbase.banner
|
78
|
-
puts Artbase.root
|
1
|
+
|
2
|
+
require 'artbase-cocos'
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
## our own code
|
7
|
+
require_relative 'artbase/version'
|
8
|
+
require_relative 'artbase/tool'
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
puts Artbase.banner # say hello
|
metadata
CHANGED
@@ -1,57 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: artbase
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: cocos
|
14
|
+
name: artbase-cocos
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.1
|
19
|
+
version: 0.0.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.1
|
26
|
+
version: 0.0.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: artserve
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0
|
33
|
+
version: '0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: artq
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: '0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rdoc
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -102,16 +102,6 @@ files:
|
|
102
102
|
- Rakefile
|
103
103
|
- bin/artbase
|
104
104
|
- lib/artbase.rb
|
105
|
-
- lib/artbase/attributes.rb
|
106
|
-
- lib/artbase/collection.rb
|
107
|
-
- lib/artbase/collection/base.rb
|
108
|
-
- lib/artbase/collection/image.rb
|
109
|
-
- lib/artbase/collection/opensea.rb
|
110
|
-
- lib/artbase/collection/token.rb
|
111
|
-
- lib/artbase/helper.rb
|
112
|
-
- lib/artbase/image.rb
|
113
|
-
- lib/artbase/image/sample.rb
|
114
|
-
- lib/artbase/retry.rb
|
115
105
|
- lib/artbase/tool.rb
|
116
106
|
- lib/artbase/version.rb
|
117
107
|
homepage: https://github.com/pixelartexchange/artbase
|
data/lib/artbase/attributes.rb
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
def counter_to_text( counter )
|
4
|
-
|
5
|
-
counter = counter.to_a
|
6
|
-
|
7
|
-
attribute_counter = counter[0]
|
8
|
-
more_counter = counter[1..-1]
|
9
|
-
|
10
|
-
|
11
|
-
puts "Attribute Counts\n"
|
12
|
-
trait_type, h = attribute_counter
|
13
|
-
|
14
|
-
total = h[:by_type].values.reduce(0) { |sum,count| sum+count }
|
15
|
-
|
16
|
-
|
17
|
-
types = h[:by_type]
|
18
|
-
types = types.sort { |l,r| l[0]<=>r[0] } ## sort by name
|
19
|
-
|
20
|
-
puts "\n"
|
21
|
-
puts "|Name|Total (%)|"
|
22
|
-
puts "|--------|----------:|"
|
23
|
-
|
24
|
-
types.each do |rec|
|
25
|
-
name = rec[0]
|
26
|
-
count = rec[1]
|
27
|
-
percent = Float(count*100)/Float(total)
|
28
|
-
|
29
|
-
puts "| **#{name} Attributes** | #{count} (#{'%.2f' % percent}) |"
|
30
|
-
end
|
31
|
-
puts "\n"
|
32
|
-
|
33
|
-
more_counter.each_with_index do |(trait_type, h),i|
|
34
|
-
print " · " if i > 0 ## add separator
|
35
|
-
print "#{trait_type } (#{h[:by_type].size})"
|
36
|
-
end
|
37
|
-
puts "\n\n"
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
more_counter.each do |trait_type, h|
|
42
|
-
print "### #{trait_type } (#{h[:by_type].size}) - "
|
43
|
-
print "∑Total #{h[:count]}/#{total}\n"
|
44
|
-
|
45
|
-
puts "\n"
|
46
|
-
puts "|Name|Total (%)|"
|
47
|
-
puts "|--------|----------:|"
|
48
|
-
|
49
|
-
types = h[:by_type]
|
50
|
-
types = types.sort do |l,r|
|
51
|
-
# sort by 1) by count
|
52
|
-
# 2) by name a-z
|
53
|
-
res = r[1] <=> l[1]
|
54
|
-
res = l[0] <=> r[0] if res == 0
|
55
|
-
res
|
56
|
-
end ## sort by count
|
57
|
-
types.each do |rec|
|
58
|
-
name = rec[0]
|
59
|
-
count = rec[1]
|
60
|
-
percent = Float(count*100)/Float(total)
|
61
|
-
|
62
|
-
puts "| **#{name}** | #{count} (#{'%.2f' % percent}) |"
|
63
|
-
end
|
64
|
-
puts "\n\n"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
def counter_to_csv( counter )
|
72
|
-
|
73
|
-
puts "type, name, count"
|
74
|
-
counter.each do |trait_type, h|
|
75
|
-
puts "#{trait_type}, ∑ Total, #{h[:count]}"
|
76
|
-
h[:by_type].each do |trait_value, count|
|
77
|
-
puts "#{trait_type}, #{trait_value}, #{count}"
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
|
83
|
-
|
@@ -1,329 +0,0 @@
|
|
1
|
-
|
2
|
-
module Artbase
|
3
|
-
class Base ## "abstract" Base collection - check -use a different name - why? why not?
|
4
|
-
|
5
|
-
|
6
|
-
def convert_images( overwrite: )
|
7
|
-
image_dir = "./#{slug}/token-i"
|
8
|
-
Image.convert( image_dir, from: 'jpg', to: 'png', overwrite: overwrite )
|
9
|
-
Image.convert( image_dir, from: 'gif', to: 'png', overwrite: overwrite )
|
10
|
-
Image.convert( image_dir, from: 'svg', to: 'png', overwrite: overwrite )
|
11
|
-
end
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
def make_strip
|
16
|
-
composite_count = @count - @excludes.size
|
17
|
-
|
18
|
-
composite = ImageComposite.new( 9, 1,
|
19
|
-
width: @width,
|
20
|
-
height: @height )
|
21
|
-
|
22
|
-
i = 0
|
23
|
-
each_image do |img, id|
|
24
|
-
puts "==> [#{i+1}/9] #{id}"
|
25
|
-
composite << img
|
26
|
-
|
27
|
-
i += 1
|
28
|
-
break if i >= 9
|
29
|
-
end
|
30
|
-
|
31
|
-
|
32
|
-
composite.save( "./#{@slug}/tmp/#{@slug}-strip.png" )
|
33
|
-
end
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def make_composite( limit: nil,
|
38
|
-
mirror: false )
|
39
|
-
### use well-known / pre-defined (default) grids
|
40
|
-
## (cols x rows) for now - why? why not?
|
41
|
-
|
42
|
-
composite_count = if limit
|
43
|
-
limit
|
44
|
-
else
|
45
|
-
@count - @excludes.size
|
46
|
-
end
|
47
|
-
|
48
|
-
cols, rows = case composite_count
|
49
|
-
when 99 then [10, 10]
|
50
|
-
when 100 then [10, 10]
|
51
|
-
when 150 then [15, 10]
|
52
|
-
when 314 then [15, 21]
|
53
|
-
when 500 then [25, 20]
|
54
|
-
when 1000 then [25, 40]
|
55
|
-
when 2200 then [50, 44]
|
56
|
-
when 2222 then [50, 45]
|
57
|
-
when 2469 then [50, 50]
|
58
|
-
when 3000 then [100, 30] ## or use 50*60 - why? why not?
|
59
|
-
when 3500 then [100, 35] ## or use 50*x ??
|
60
|
-
when 3979 then [100, 40]
|
61
|
-
when 4000 then [100, 40] ## or use 50x80 - why? why not?
|
62
|
-
when 4444 then [100, 45] ## or use 50x??
|
63
|
-
when 5000 then [100, 50] ## or use 50x100 - why? why not?
|
64
|
-
when 5555 then [100, 56] # 5600 (45 left empty)
|
65
|
-
when 6666 then [100, 67] # 6700 (34 left empty)
|
66
|
-
when 6688 then [100, 67] # 6700 (12 left empty)
|
67
|
-
when 6969 then [100, 70] # 7000 (31 left empty)
|
68
|
-
when 7500 then [100, 75]
|
69
|
-
when 8888 then [100, 89]
|
70
|
-
when 9969 then [100,100]
|
71
|
-
when 10000 then [100,100]
|
72
|
-
else
|
73
|
-
raise ArgumentError, "sorry - unknown composite count #{composite_count}/#{@count} for now"
|
74
|
-
end
|
75
|
-
|
76
|
-
composite = ImageComposite.new( cols, rows,
|
77
|
-
width: @width,
|
78
|
-
height: @height )
|
79
|
-
|
80
|
-
|
81
|
-
count = 0
|
82
|
-
each_image do |img, id|
|
83
|
-
puts "==> #{id}"
|
84
|
-
composite << if mirror
|
85
|
-
img.mirror
|
86
|
-
else
|
87
|
-
img
|
88
|
-
end
|
89
|
-
|
90
|
-
count += 1
|
91
|
-
break if limit && count >= limit
|
92
|
-
end
|
93
|
-
|
94
|
-
|
95
|
-
slug = "#{@slug}"
|
96
|
-
slug += "#{limit}" if limit
|
97
|
-
slug += "_left" if mirror
|
98
|
-
|
99
|
-
path = "./#{@slug}/tmp/#{slug}-#{@width}x#{@height}.png"
|
100
|
-
puts " saving #{path}..."
|
101
|
-
composite.save( path )
|
102
|
-
|
103
|
-
if composite_count < 1000
|
104
|
-
path = "./#{@slug}/tmp/#{slug}-#{@width}x#{@height}@2x.png"
|
105
|
-
puts " saving 2x #{path}..."
|
106
|
-
composite.zoom(2).save( path )
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
def calc_attribute_counters ## todo/check: use a different name _counts/_stats etc - why? why not?
|
114
|
-
|
115
|
-
attributes_by_count = { count: 0,
|
116
|
-
by_count: Hash.new(0)
|
117
|
-
}
|
118
|
-
counter = {}
|
119
|
-
|
120
|
-
|
121
|
-
each_meta do |meta, id| ## todo/fix: change id to index
|
122
|
-
traits = meta.traits
|
123
|
-
# print "#{traits.size} - "
|
124
|
-
# pp traits
|
125
|
-
|
126
|
-
print "#{id}.." if id % 100 == 0 ## print progress report
|
127
|
-
|
128
|
-
attributes_by_count[ :count ] +=1
|
129
|
-
attributes_by_count[ :by_count ][ traits.size ] += 1
|
130
|
-
|
131
|
-
traits.each do |trait_type, trait_value|
|
132
|
-
trait_type = _normalize_trait_type( trait_type )
|
133
|
-
trait_value = _normalize_trait_value( trait_value )
|
134
|
-
|
135
|
-
|
136
|
-
rec = counter[ trait_type ] ||= { count: 0,
|
137
|
-
by_type: Hash.new(0)
|
138
|
-
}
|
139
|
-
rec[ :count ] +=1
|
140
|
-
rec[ :by_type ][ trait_value ] += 1
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
print "\n"
|
145
|
-
puts
|
146
|
-
|
147
|
-
## return all-in-one hash
|
148
|
-
{
|
149
|
-
total: attributes_by_count,
|
150
|
-
traits: counter,
|
151
|
-
}
|
152
|
-
end
|
153
|
-
|
154
|
-
|
155
|
-
def dump_attributes
|
156
|
-
stats = calc_attribute_counters
|
157
|
-
|
158
|
-
total = stats[:total]
|
159
|
-
counter = stats[:traits]
|
160
|
-
|
161
|
-
puts
|
162
|
-
puts "attribute usage / counts:"
|
163
|
-
pp total
|
164
|
-
puts
|
165
|
-
|
166
|
-
puts "#{counter.size} attribute(s):"
|
167
|
-
counter.each do |trait_name, trait_rec|
|
168
|
-
puts " #{trait_name} #{trait_rec[:count]} (#{trait_rec[:by_type].size} uniques)"
|
169
|
-
end
|
170
|
-
|
171
|
-
puts
|
172
|
-
pp counter
|
173
|
-
end
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
## order - allow "custom" attribute order export
|
179
|
-
## renames - allow renames of attributes
|
180
|
-
def export_attributes(
|
181
|
-
order: [],
|
182
|
-
renames: {}
|
183
|
-
)
|
184
|
-
|
185
|
-
## step 1: get counters
|
186
|
-
stats = calc_attribute_counters
|
187
|
-
|
188
|
-
total = stats[:total]
|
189
|
-
counter = stats[:traits]
|
190
|
-
|
191
|
-
puts
|
192
|
-
puts "attribute usage / counts:"
|
193
|
-
pp total
|
194
|
-
puts
|
195
|
-
|
196
|
-
puts "#{counter.size} attribute(s):"
|
197
|
-
counter.each do |trait_name, trait_rec|
|
198
|
-
puts " #{trait_name} #{trait_rec[:count]} (#{trait_rec[:by_type].size} uniques)"
|
199
|
-
end
|
200
|
-
|
201
|
-
|
202
|
-
trait_names = []
|
203
|
-
trait_names += order ## get attributes if any in pre-defined order
|
204
|
-
counter.each do |trait_name, _|
|
205
|
-
if trait_names.include?( trait_name )
|
206
|
-
next ## skip already included
|
207
|
-
else
|
208
|
-
trait_names << trait_name
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
|
213
|
-
recs = []
|
214
|
-
|
215
|
-
|
216
|
-
## step 2: get tabular data
|
217
|
-
each_meta do |meta, id| ## todo/fix: change id to index
|
218
|
-
|
219
|
-
traits = meta.traits
|
220
|
-
# print "#{traits.size} - "
|
221
|
-
# pp traits
|
222
|
-
|
223
|
-
print "#{id}.." if id % 100 == 0 ## print progress report
|
224
|
-
|
225
|
-
## setup empty hash table (with all attributes)
|
226
|
-
rec = {}
|
227
|
-
|
228
|
-
## note: use __Slug__& __Name__
|
229
|
-
## to avoid conflict with attribute names
|
230
|
-
## e.g. attribute with "Name" will overwrite built-in and so on
|
231
|
-
|
232
|
-
rec['__Slug__'] = if respond_to?( :_meta_slugify )
|
233
|
-
_meta_slugify( meta, id )
|
234
|
-
else
|
235
|
-
## default to id (six digits) as string with leading zeros
|
236
|
-
## for easy sorting using strings
|
237
|
-
## e.g. 1 => '000001'
|
238
|
-
## 2 => '000002'
|
239
|
-
'%06d' % id
|
240
|
-
end
|
241
|
-
|
242
|
-
rec['__Name__'] = meta.name
|
243
|
-
|
244
|
-
## add all attributes/traits names/keys
|
245
|
-
trait_names.reduce( rec ) { |h,value| h[value] = []; h }
|
246
|
-
## pp rec
|
247
|
-
|
248
|
-
## note: use an array (to allow multiple values for attributes)
|
249
|
-
traits.each do |trait_type, trait_value|
|
250
|
-
trait_type = _normalize_trait_type( trait_type )
|
251
|
-
trait_value = _normalize_trait_value( trait_value )
|
252
|
-
|
253
|
-
values = rec[ trait_type ]
|
254
|
-
values << trait_value
|
255
|
-
end
|
256
|
-
recs << rec
|
257
|
-
end
|
258
|
-
print "\n"
|
259
|
-
|
260
|
-
## pp recs
|
261
|
-
|
262
|
-
## flatten recs
|
263
|
-
data = []
|
264
|
-
recs.each do |rec|
|
265
|
-
row = rec.values.map do |value|
|
266
|
-
if value.is_a?( Array )
|
267
|
-
value.join( ' / ' )
|
268
|
-
else
|
269
|
-
value
|
270
|
-
end
|
271
|
-
end
|
272
|
-
data << row
|
273
|
-
end
|
274
|
-
|
275
|
-
|
276
|
-
## sort by slug
|
277
|
-
data = data.sort {|l,r| l[0] <=> r[0] }
|
278
|
-
pp data
|
279
|
-
|
280
|
-
### save dataset
|
281
|
-
## note: change first colum Slug to ID - only used for "internal" sort etc.
|
282
|
-
headers = ['ID', 'Name']
|
283
|
-
headers += trait_names.map do |trait_name| ## check for renames
|
284
|
-
renames[trait_name] || trait_name
|
285
|
-
end
|
286
|
-
|
287
|
-
|
288
|
-
path = "./#{@slug}/tmp/#{@slug}.csv"
|
289
|
-
dirname = File.dirname( path )
|
290
|
-
FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
|
291
|
-
|
292
|
-
File.open( path, 'w:utf-8' ) do |f|
|
293
|
-
f.write( headers.join( ', ' ))
|
294
|
-
f.write( "\n" )
|
295
|
-
## note: replace ID with our own internal running (zero-based) counter
|
296
|
-
data.each_with_index do |row,i|
|
297
|
-
f.write( ([i]+row[1..-1]).join( ', '))
|
298
|
-
f.write( "\n" )
|
299
|
-
end
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
#############
|
307
|
-
# "private" helpers
|
308
|
-
|
309
|
-
def _normalize_trait_type( trait_type )
|
310
|
-
if @patch && @patch[:trait_types]
|
311
|
-
@patch[:trait_types][ trait_type ] || trait_type
|
312
|
-
else
|
313
|
-
trait_type
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
def _normalize_trait_value( trait_value )
|
318
|
-
if @patch && @patch[:trait_values]
|
319
|
-
@patch[:trait_values][ trait_value ] || trait_value
|
320
|
-
else
|
321
|
-
trait_value
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
end # class Base
|
329
|
-
end # module Artbase
|