flico 0.0.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.ruby-version +1 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +18 -18
- data/LICENSE.text +1 -1
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/bin/flico +2 -0
- data/flico.gemspec +20 -20
- data/lib/flico/app.rb +36 -37
- data/lib/flico/cell.rb +30 -28
- data/lib/flico/collager.rb +34 -35
- data/lib/flico/dictionary.rb +18 -24
- data/lib/flico/flickr_command.rb +53 -56
- data/lib/flico/grid.rb +79 -77
- data/lib/flico/saver.rb +32 -37
- data/lib/flico.rb +59 -61
- data/spec/flickr_command_spec.rb +53 -17
- data/spec/spec_helper.rb +53 -53
- metadata +37 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: adca32d7e5ee313f13e0ea06a104fe7f1f2fb0dee521b2997c96cd0901c67dcf
|
4
|
+
data.tar.gz: adf98002d3585edf868ab0ddc0a655cf48b932aa3671a079217398956b0c5705
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7813fde11543901c15b0f212e81a6d36968f82be4974784f09b5a45365c13e6e83a50502ecd70b56a7a9a10acdcc071cfa00dfc330516ce14175cb3358ed704
|
7
|
+
data.tar.gz: cf5e50c394033d3feee1f4bbb3d74ed40c07952b440b12e2e28fd16dc9d2340d12bf80526d30a2fddb2426034fc6dee57ca0947454c709ef2ba106ad77176fc8
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
3.2.2
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,33 +1,33 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
flico (0.0
|
4
|
+
flico (1.0.0)
|
5
5
|
flickraw
|
6
6
|
mini_magick
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
diff-lcs (1.
|
12
|
-
flickraw (0.9.
|
13
|
-
mini_magick (4.
|
14
|
-
rake (
|
15
|
-
rspec (3.
|
16
|
-
rspec-core (~> 3.
|
17
|
-
rspec-expectations (~> 3.
|
18
|
-
rspec-mocks (~> 3.
|
19
|
-
rspec-core (3.
|
20
|
-
rspec-support (~> 3.
|
21
|
-
rspec-expectations (3.
|
11
|
+
diff-lcs (1.5.0)
|
12
|
+
flickraw (0.9.10)
|
13
|
+
mini_magick (4.12.0)
|
14
|
+
rake (13.0.6)
|
15
|
+
rspec (3.12.0)
|
16
|
+
rspec-core (~> 3.12.0)
|
17
|
+
rspec-expectations (~> 3.12.0)
|
18
|
+
rspec-mocks (~> 3.12.0)
|
19
|
+
rspec-core (3.12.2)
|
20
|
+
rspec-support (~> 3.12.0)
|
21
|
+
rspec-expectations (3.12.3)
|
22
22
|
diff-lcs (>= 1.2.0, < 2.0)
|
23
|
-
rspec-support (~> 3.
|
24
|
-
rspec-mocks (3.
|
23
|
+
rspec-support (~> 3.12.0)
|
24
|
+
rspec-mocks (3.12.5)
|
25
25
|
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
-
rspec-support (~> 3.
|
27
|
-
rspec-support (3.
|
26
|
+
rspec-support (~> 3.12.0)
|
27
|
+
rspec-support (3.12.1)
|
28
28
|
|
29
29
|
PLATFORMS
|
30
|
-
|
30
|
+
arm64-darwin-22
|
31
31
|
|
32
32
|
DEPENDENCIES
|
33
33
|
bundler
|
@@ -36,4 +36,4 @@ DEPENDENCIES
|
|
36
36
|
rspec
|
37
37
|
|
38
38
|
BUNDLED WITH
|
39
|
-
|
39
|
+
2.4.13
|
data/LICENSE.text
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License
|
2
2
|
|
3
|
-
Copyright 2017 Amandeep Bhamra
|
3
|
+
Copyright 2017 Amandeep Singh Bhamra
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
6
|
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
data/bin/flico
CHANGED
data/flico.gemspec
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
|
5
6
|
Gem::Specification.new do |spec|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
spec.name = 'flico'
|
8
|
+
spec.version = '1.0.0'
|
9
|
+
spec.summary = 'A CLI tool to create collage from keywords using Flickr'
|
10
|
+
spec.homepage = 'https://github.com/amandeepbhamra/flico'
|
11
|
+
spec.license = 'MIT'
|
12
|
+
|
13
|
+
spec.authors = ['Amandeep Singh Bhamra']
|
14
|
+
spec.email = ['amandeep.bhamra@gmail.com']
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
spec.bindir = 'bin'
|
18
|
-
spec.executables = 'flico'
|
19
|
-
spec.require_paths = ['lib']
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.bindir = 'bin'
|
18
|
+
spec.executables = 'flico'
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
spec.add_dependency 'flickraw', '~> 0.9.10'
|
22
|
+
spec.add_dependency 'mini_magick', '~> 4.12'
|
23
|
+
spec.add_development_dependency 'bundler', '~> 2.4', '>= 2.4.14'
|
24
|
+
spec.add_development_dependency 'rake', '~> 13.0', '>= 13.0.6'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.12'
|
26
26
|
end
|
data/lib/flico/app.rb
CHANGED
@@ -1,45 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Flico
|
4
|
+
class ApplicationError < StandardError; end
|
5
|
+
class KeywordMissing < StandardError; end
|
6
|
+
class NoImage < StandardError; end
|
7
|
+
class FetchingError < StandardError; end
|
2
8
|
|
3
|
-
|
4
|
-
|
5
|
-
class NoImage < StandardError; end
|
6
|
-
class FetchingError < StandardError; end
|
7
|
-
|
8
|
-
class App
|
9
|
-
|
10
|
-
attr_reader :resources
|
9
|
+
class App
|
10
|
+
attr_reader :resources
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
def initialize(resources)
|
13
|
+
@resources = resources
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
def create_collage
|
17
|
+
image_urls = []
|
18
|
+
loop do
|
19
|
+
image_urls.push get_images
|
20
|
+
break unless image_urls.count < 10
|
21
|
+
end
|
22
|
+
collage(image_urls)
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
def collage(images)
|
26
|
+
resources.save_collage.call(resources.collager.call(images))
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
else
|
38
|
-
retry
|
39
|
-
end
|
40
|
-
rescue FetchingError => e
|
41
|
-
raise ApplicationError, e.message
|
42
|
-
end
|
43
|
-
end
|
29
|
+
def get_images
|
30
|
+
keyword = resources.dictionary.call
|
31
|
+
image_url = resources.flickr_api.call(keyword)
|
32
|
+
resources.fetch_image.call(image_url)
|
33
|
+
rescue NoImage => e
|
34
|
+
puts "Image not found for keyword '#{keyword}'. Message: #{e.message}. Retrying"
|
35
|
+
unless (tries -= 1).positive?
|
36
|
+
raise ApplicationError, "Failed getting image after retrying #{MAX_KEYWORD_RETRIES} times"
|
37
|
+
end
|
44
38
|
|
39
|
+
retry
|
40
|
+
rescue FetchingError => e
|
41
|
+
raise ApplicationError, e.message
|
42
|
+
end
|
43
|
+
end
|
45
44
|
end
|
data/lib/flico/cell.rb
CHANGED
@@ -1,30 +1,32 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class Cell
|
4
|
-
attr_reader :x, :y, :width, :height
|
5
|
-
|
6
|
-
def initialize(x=0, y=0, width, height)
|
7
|
-
@x, @y = x, y
|
8
|
-
@width, @height = width, height
|
9
|
-
end
|
10
|
-
|
11
|
-
def aspect_ratio
|
12
|
-
width.to_f / height.to_f
|
13
|
-
end
|
14
|
-
|
15
|
-
def to_mm
|
16
|
-
"#{width}x#{height}+#{x}+#{y}"
|
17
|
-
end
|
18
|
-
|
19
|
-
def ==(o)
|
20
|
-
o.class == self.class && o.state == state
|
21
|
-
end
|
22
|
-
|
23
|
-
protected
|
24
|
-
|
25
|
-
def state
|
26
|
-
[@x, @y, @width, @height]
|
27
|
-
end
|
28
|
-
end
|
1
|
+
# frozen_string_literal: true
|
29
2
|
|
3
|
+
module Flico
|
4
|
+
class Cell
|
5
|
+
attr_reader :x, :y, :width, :height
|
6
|
+
|
7
|
+
def initialize(x = 0, y = 0, width, height)
|
8
|
+
@x = x
|
9
|
+
@y = y
|
10
|
+
@width = width
|
11
|
+
@height = height
|
12
|
+
end
|
13
|
+
|
14
|
+
def aspect_ratio
|
15
|
+
width.to_f / height
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_mm
|
19
|
+
"#{width}x#{height}+#{x}+#{y}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(other)
|
23
|
+
other.class == self.class && other.state == state
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def state
|
29
|
+
[@x, @y, @width, @height]
|
30
|
+
end
|
31
|
+
end
|
30
32
|
end
|
data/lib/flico/collager.rb
CHANGED
@@ -1,45 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'flico/grid'
|
2
4
|
require 'mini_magick'
|
3
5
|
|
4
6
|
module Flico
|
5
|
-
|
6
|
-
|
7
|
-
attr_reader :grid
|
8
|
-
|
9
|
-
def initialize(grid=Grid.new)
|
10
|
-
@grid = grid
|
11
|
-
end
|
7
|
+
class Collager
|
8
|
+
attr_reader :grid
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
canvas = MiniMagick::Tool::Convert.new do |i|
|
17
|
-
i.size "#{grid.canv_width}x#{grid.canv_height}"
|
18
|
-
i.xc "white"
|
19
|
-
i << temp_file.path
|
20
|
-
end
|
10
|
+
def initialize(grid = Grid.new)
|
11
|
+
@grid = grid
|
12
|
+
end
|
21
13
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
14
|
+
def call(image_urls)
|
15
|
+
image_urls.map { |p| MiniMagick::Image.open p.path }
|
16
|
+
temp_file = Tempfile.new ['collage_maker', '.png']
|
17
|
+
MiniMagick::Tool::Convert.new do |i|
|
18
|
+
i.size "#{grid.canv_width}x#{grid.canv_height}"
|
19
|
+
i.xc 'white'
|
20
|
+
i << temp_file.path
|
21
|
+
end
|
30
22
|
|
31
|
-
|
23
|
+
image_urls.map.with_index do |path, idx|
|
24
|
+
image = MiniMagick::Image.open path.path
|
25
|
+
image.crop(grid.crop_rectangle(idx, image.width, image.height).to_mm)
|
26
|
+
image.resize(grid.resize_rectangle(idx, image.width, image.height).to_mm)
|
27
|
+
print_to_canvas(image, grid.cell_rectangle(idx), temp_file)
|
28
|
+
end
|
29
|
+
temp_file
|
30
|
+
end
|
32
31
|
|
33
|
-
|
34
|
-
canvas = MiniMagick::Image.new temp_file.path
|
35
|
-
result = canvas.composite(image) do |c|
|
36
|
-
c.compose 'Over'
|
37
|
-
c.geometry rectangle.to_mm
|
38
|
-
end
|
39
|
-
result.write temp_file.path
|
40
|
-
temp_file.rewind
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
32
|
+
private
|
44
33
|
|
34
|
+
def print_to_canvas(image, rectangle, temp_file)
|
35
|
+
canvas = MiniMagick::Image.new temp_file.path
|
36
|
+
result = canvas.composite(image) do |c|
|
37
|
+
c.compose 'Over'
|
38
|
+
c.geometry rectangle.to_mm
|
39
|
+
end
|
40
|
+
result.write temp_file.path
|
41
|
+
temp_file.rewind
|
42
|
+
end
|
43
|
+
end
|
45
44
|
end
|
data/lib/flico/dictionary.rb
CHANGED
@@ -1,30 +1,24 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class Dictionary
|
4
|
-
|
5
|
-
def initialize(dictionary_path)
|
6
|
-
@dictionary_path = dictionary_path
|
7
|
-
@words = []
|
8
|
-
end
|
1
|
+
# frozen_string_literal: true
|
9
2
|
|
10
|
-
|
11
|
-
|
12
|
-
|
3
|
+
module Flico
|
4
|
+
class Dictionary
|
5
|
+
def initialize(dictionary_path)
|
6
|
+
@dictionary_path = dictionary_path
|
7
|
+
@words = []
|
8
|
+
end
|
13
9
|
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
def call
|
11
|
+
@words.shift || dictionary_word
|
12
|
+
end
|
17
13
|
|
18
|
-
|
14
|
+
def append(keywords)
|
15
|
+
@words += keywords
|
16
|
+
end
|
19
17
|
|
20
|
-
|
21
|
-
selected_line = nil
|
22
|
-
File.foreach(@dictionary_path).each_with_index do |line, number|
|
23
|
-
selected_line = line if (rand < 1.0) / (number + 1)
|
24
|
-
end
|
25
|
-
selected_line.strip
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
18
|
+
private
|
29
19
|
|
20
|
+
def dictionary_word
|
21
|
+
File.readlines("/usr/share/dict/words").select { |word| word.size > 3 && word.size < 10 }.sample.strip
|
22
|
+
end
|
23
|
+
end
|
30
24
|
end
|
data/lib/flico/flickr_command.rb
CHANGED
@@ -1,58 +1,55 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class FlickrCommand
|
4
|
-
|
5
|
-
def initialize(search_command:, sizes_command:)
|
6
|
-
@search_command = search_command
|
7
|
-
@sizes_command = sizes_command
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.setup(flickraw)
|
11
|
-
new(search_command: SearchCommand.new(flickraw), sizes_command: SizesCommand.new(flickraw))
|
12
|
-
end
|
13
|
-
|
14
|
-
def call(keyword)
|
15
|
-
unless keyword.nil?
|
16
|
-
puts "Searching images for keyword: #{keyword}"
|
17
|
-
results = @search_command.call(keyword)
|
18
|
-
unless results.count == 0
|
19
|
-
get_image_url(results.first['id'])
|
20
|
-
else
|
21
|
-
STDERR.puts "No Image for keyword: #{keyword}"
|
22
|
-
end
|
23
|
-
else
|
24
|
-
STDERR.puts "Missing Keywords (can't be nil)"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def get_image_url(photo_id)
|
31
|
-
sizes = @sizes_command.call(photo_id)
|
32
|
-
mid = sizes.count / 2
|
33
|
-
sizes[mid]['source']
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
|
-
|
39
|
-
class FlickrBase
|
40
|
-
attr_reader :api
|
41
|
-
def initialize(flickraw)
|
42
|
-
@api = flickraw
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class SearchCommand < FlickrBase
|
47
|
-
def call(keyword)
|
48
|
-
api.photos.search({text: keyword, per_page: 10, sort: 'interestingness-desc'})
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class SizesCommand < FlickrBase
|
53
|
-
def call(photo_id)
|
54
|
-
api.photos.getSizes(photo_id: photo_id)
|
55
|
-
end
|
56
|
-
end
|
1
|
+
# frozen_string_literal: true
|
57
2
|
|
3
|
+
module Flico
|
4
|
+
class FlickrCommand
|
5
|
+
def initialize(search_command:, sizes_command:)
|
6
|
+
@search_command = search_command
|
7
|
+
@sizes_command = sizes_command
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.setup(flickraw)
|
11
|
+
new(search_command: SearchCommand.new(flickraw), sizes_command: SizesCommand.new(flickraw))
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(keyword)
|
15
|
+
if keyword.nil?
|
16
|
+
warn "Missing Keywords (can't be nil)"
|
17
|
+
else
|
18
|
+
puts "Searching images for keyword: #{keyword}"
|
19
|
+
results = @search_command.call(keyword)
|
20
|
+
if results.count.zero?
|
21
|
+
warn "No Image for keyword: #{keyword}"
|
22
|
+
else
|
23
|
+
get_image_url(results.first['id'])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def get_image_url(photo_id)
|
31
|
+
sizes = @sizes_command.call(photo_id)
|
32
|
+
sizes[sizes.count / 2]['source']
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class FlickrBase
|
37
|
+
attr_reader :api
|
38
|
+
|
39
|
+
def initialize(flickraw)
|
40
|
+
@api = flickraw
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class SearchCommand < FlickrBase
|
45
|
+
def call(keyword)
|
46
|
+
api.photos.search({ text: keyword, per_page: 10, sort: 'interestingness-desc' })
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class SizesCommand < FlickrBase
|
51
|
+
def call(photo_id)
|
52
|
+
api.photos.getSizes(photo_id:)
|
53
|
+
end
|
54
|
+
end
|
58
55
|
end
|
data/lib/flico/grid.rb
CHANGED
@@ -1,81 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'flico/cell'
|
2
4
|
|
3
5
|
module Flico
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
6
|
+
class Grid
|
7
|
+
GRID = [[0, 1, 1, 2], [3, 4, 5, 6], [7, 8, 8, 9]].freeze
|
8
|
+
CANV_WIDTH = 1750
|
9
|
+
CANV_HEIGHT = 1250
|
10
|
+
|
11
|
+
def canv_width
|
12
|
+
CANV_WIDTH
|
13
|
+
end
|
14
|
+
|
15
|
+
def canv_height
|
16
|
+
CANV_HEIGHT
|
17
|
+
end
|
18
|
+
|
19
|
+
def columns
|
20
|
+
GRID.max_by(&:size).size
|
21
|
+
end
|
22
|
+
|
23
|
+
def column_cells
|
24
|
+
GRID.transpose
|
25
|
+
end
|
26
|
+
|
27
|
+
def rows
|
28
|
+
GRID.size
|
29
|
+
end
|
30
|
+
|
31
|
+
def row_cells
|
32
|
+
GRID
|
33
|
+
end
|
34
|
+
|
35
|
+
def cell_width(name)
|
36
|
+
cell_size(CANV_WIDTH, columns, size_multiplier(row_cells, name))
|
37
|
+
end
|
38
|
+
|
39
|
+
def cell_height(name)
|
40
|
+
cell_size(CANV_HEIGHT, rows, size_multiplier(column_cells, name))
|
41
|
+
end
|
42
|
+
|
43
|
+
def crop_rectangle(cell_name, image_width, image_height)
|
44
|
+
x = 0
|
45
|
+
y = 0
|
46
|
+
image = Cell.new(image_width, image_height)
|
47
|
+
cell = cell_rectangle(cell_name)
|
48
|
+
|
49
|
+
if image.aspect_ratio >= cell.aspect_ratio
|
50
|
+
final_width = image_width - (image.width - (image.height * cell.aspect_ratio))
|
51
|
+
final_height = image.height
|
52
|
+
else
|
53
|
+
final_width = image.width
|
54
|
+
final_height = image.height - (image.height - (image.width / cell.aspect_ratio))
|
55
|
+
end
|
56
|
+
|
57
|
+
Cell.new(x, y, final_width, final_height)
|
58
|
+
end
|
59
|
+
|
60
|
+
def resize_rectangle(cell_name, image_width, image_height)
|
61
|
+
Cell.new(image_width, image_height)
|
62
|
+
cell_rectangle(cell_name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def cell_rectangle(name)
|
66
|
+
column = column_cells.find_index do |row|
|
67
|
+
row.include? name
|
68
|
+
end
|
69
|
+
row = row_cells.find_index { |row| row.include? name }
|
70
|
+
Cell.new(column * (CANV_WIDTH / columns), row * (CANV_HEIGHT / rows), cell_width(name), cell_height(name))
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def size_multiplier(table, name)
|
76
|
+
table.map { |row| row.count(name) }.max
|
77
|
+
end
|
78
|
+
|
79
|
+
def cell_size(total, divisions, multiplier)
|
80
|
+
multiplier.zero? ? 0 : ((total / divisions) * multiplier)
|
81
|
+
end
|
82
|
+
end
|
81
83
|
end
|
data/lib/flico/saver.rb
CHANGED
@@ -1,44 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'open-uri'
|
2
4
|
require 'tempfile'
|
3
5
|
require 'fileutils'
|
4
6
|
require 'date'
|
5
7
|
|
6
|
-
|
7
8
|
module Flico
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
file_name = STDIN.gets.strip
|
39
|
-
file_name.empty? ? "flicollage-#{Time.now.strftime('%Y%m%d%H%M%S')}.png" : file_name
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
|
9
|
+
class FetchingError < StandardError; end
|
10
|
+
|
11
|
+
class FetchImage
|
12
|
+
def call(url)
|
13
|
+
tempfile = Tempfile.new 'temp_image'
|
14
|
+
IO.copy_stream(URI.open(url, read_timeout: 5), tempfile)
|
15
|
+
tempfile.rewind
|
16
|
+
tempfile
|
17
|
+
rescue OpenURI::HTTPError => e
|
18
|
+
raise FetchingError, "Couldn't download from: #{url} due to #{e.message}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class SaveCollage
|
23
|
+
attr_accessor :output_file_name
|
24
|
+
|
25
|
+
def call(file_path)
|
26
|
+
file_name = output_file_name || validate_file_name
|
27
|
+
FileUtils.mv file_path, file_name
|
28
|
+
puts "Flicollage saved at #{file_name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def validate_file_name
|
34
|
+
puts "Enter file name for collage (press ENTER to use '#{default}')"
|
35
|
+
file_name = $stdin.gets.strip
|
36
|
+
file_name.empty? ? "flicollage-#{Time.now.strftime('%Y%m%d%H%M%S')}.png" : file_name
|
37
|
+
end
|
38
|
+
end
|
44
39
|
end
|
data/lib/flico.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'flickraw'
|
2
4
|
require 'ostruct'
|
3
5
|
require 'optparse'
|
@@ -8,72 +10,68 @@ require 'flico/saver'
|
|
8
10
|
require 'flico/collager'
|
9
11
|
|
10
12
|
module Flico
|
13
|
+
class CommandLineInterface
|
14
|
+
def self.parse_args(args)
|
15
|
+
options = {}
|
16
|
+
OptionParser.new do |opts|
|
17
|
+
opts.banner = 'Usage: flico [options] [10 keywords with space as delimiter]'
|
18
|
+
opts.on('-f', '--file_name [FileName]', 'Collage FileName') do |f|
|
19
|
+
options[:file_name] = f
|
20
|
+
end
|
21
|
+
end.parse!(args)
|
22
|
+
[args, options]
|
23
|
+
end
|
11
24
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
opts.on("-f", "--file_name [FileName]", "Collage FileName") do |f|
|
19
|
-
options[:file_name] = f
|
20
|
-
end
|
21
|
-
end.parse!(args)
|
22
|
-
[args, options]
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.start(args)
|
26
|
-
keywords, options = parse_args(args)
|
27
|
-
puts "Given Keywords: #{keywords.join(' ')} with Options: #{options}"
|
28
|
-
resources = validate_resources(keywords, options)
|
29
|
-
App.new(resources).create_collage
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.validate_resources(keywords, options={})
|
33
|
-
dictionary = validate_dictionary
|
34
|
-
dictionary.append(keywords)
|
35
|
-
col = SaveCollage.new
|
36
|
-
col.output_file_name = options[:file_name]
|
37
|
-
|
38
|
-
Resource.new(flickr_api: validate_flickr_api, dictionary: dictionary, fetch_image: FetchImage.new, collager: Collager.new, save_collage: col)
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.validate_flickr_api
|
42
|
-
key, secret = ENV['FLICKR_KEY'], ENV['FLICKR_SECRET']
|
43
|
-
unless key == nil && secret == nil
|
44
|
-
FlickRaw.api_key = key
|
45
|
-
FlickRaw.shared_secret = secret
|
46
|
-
FlickrCommand.setup(FlickRaw::Flickr.new)
|
47
|
-
else
|
48
|
-
STDERR.puts "Performing AutoExit due to missing required environment variables: FLICKR_KEY and FLICKR_SECRET"
|
49
|
-
exit 1
|
50
|
-
end
|
51
|
-
end
|
25
|
+
def self.start(args)
|
26
|
+
keywords, options = parse_args(args)
|
27
|
+
puts "Given Keywords: #{keywords.join(' ')} with Options: #{options}"
|
28
|
+
resources = validate_resources(keywords, options)
|
29
|
+
App.new(resources).create_collage
|
30
|
+
end
|
52
31
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
STDERR.puts "Performing AutoExit due to missing required dictionary at path: #{dictionary_path}"
|
59
|
-
exit 1
|
60
|
-
end
|
61
|
-
end
|
32
|
+
def self.validate_resources(keywords, options = {})
|
33
|
+
dictionary = validate_dictionary
|
34
|
+
dictionary.append(keywords)
|
35
|
+
col = SaveCollage.new
|
36
|
+
col.output_file_name = options[:file_name]
|
62
37
|
|
63
|
-
|
38
|
+
Resource.new(flickr_api: validate_flickr_api, dictionary:, fetch_image: FetchImage.new,
|
39
|
+
collager: Collager.new, save_collage: col)
|
40
|
+
end
|
64
41
|
|
65
|
-
|
66
|
-
|
67
|
-
|
42
|
+
def self.validate_flickr_api
|
43
|
+
key = ENV['FLICKR_KEY']
|
44
|
+
secret = ENV['FLICKR_SECRET']
|
45
|
+
if key.nil? && secret.nil?
|
46
|
+
warn 'Performing AutoExit due to missing required environment variables: FLICKR_KEY and FLICKR_SECRET'
|
47
|
+
exit 1
|
48
|
+
else
|
49
|
+
FlickRaw.api_key = key
|
50
|
+
FlickRaw.shared_secret = secret
|
51
|
+
FlickrCommand.setup(FlickRaw::Flickr.new)
|
52
|
+
end
|
53
|
+
end
|
68
54
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
55
|
+
def self.validate_dictionary
|
56
|
+
dictionary_path = '/usr/share/dict/words'
|
57
|
+
if File.exist?(dictionary_path)
|
58
|
+
Dictionary.new(dictionary_path)
|
59
|
+
else
|
60
|
+
warn "Performing AutoExit due to missing required dictionary at path: #{dictionary_path}"
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
76
65
|
|
77
|
-
|
66
|
+
class Resource
|
67
|
+
attr_reader :flickr_api, :dictionary, :fetch_image, :collager, :save_collage
|
78
68
|
|
69
|
+
def initialize(params = {})
|
70
|
+
@flickr_api = params.fetch(:flickr_api)
|
71
|
+
@dictionary = params.fetch(:dictionary)
|
72
|
+
@fetch_image = params.fetch(:fetch_image)
|
73
|
+
@collager = params.fetch(:collager)
|
74
|
+
@save_collage = params.fetch(:save_collage)
|
75
|
+
end
|
76
|
+
end
|
79
77
|
end
|
data/spec/flickr_command_spec.rb
CHANGED
@@ -1,24 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'flico/flickr_command'
|
3
5
|
|
4
6
|
describe Flico::FlickrCommand do
|
7
|
+
let(:search_results) do
|
8
|
+
[
|
9
|
+
{
|
10
|
+
'id' => '33472607802',
|
11
|
+
'owner' => '78759190@N05',
|
12
|
+
'secret' => '42e011bcf5',
|
13
|
+
'server' => '2805',
|
14
|
+
'farm' => 3,
|
15
|
+
'title' => 'Volkswagen Golf in Luxembourg',
|
16
|
+
'ispublic' => 1,
|
17
|
+
'isfriend' => 0,
|
18
|
+
'isfamily' => 0
|
19
|
+
}
|
20
|
+
]
|
21
|
+
end
|
22
|
+
let(:sizes_results) do
|
23
|
+
[
|
24
|
+
{
|
25
|
+
'label' => 'Square',
|
26
|
+
'width' => 75,
|
27
|
+
'height' => 75,
|
28
|
+
'source' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_s.jpg',
|
29
|
+
'url' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5/sizes/sq/',
|
30
|
+
'media' => 'photo'
|
31
|
+
},
|
32
|
+
{
|
33
|
+
'label' => 'Large Square',
|
34
|
+
'width' => '150',
|
35
|
+
'height' => '150',
|
36
|
+
'source' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_q.jpg',
|
37
|
+
'url' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5/sizes/q/',
|
38
|
+
'media' => 'photo'
|
39
|
+
},
|
5
40
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
it 'should return image url for search keyword' do
|
20
|
-
expect(subject.call 'Volkswagen Golf in Luxembourg').to eq('https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_q.jpg')
|
21
|
-
end
|
22
|
-
end
|
41
|
+
{
|
42
|
+
'label' => 'Thumbnail',
|
43
|
+
'width' => '100',
|
44
|
+
'height' => '66',
|
45
|
+
'source' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_t.jpg',
|
46
|
+
'url' => 'https://farm3.staticflickr.com/2805/33472607802_42e011bcf5/sizes/t/',
|
47
|
+
'media' => 'photo'
|
48
|
+
}
|
49
|
+
]
|
50
|
+
end
|
51
|
+
let(:subject) { described_class.new(search_command:, sizes_command:) }
|
52
|
+
let(:search_command) { double('search', call: search_results) }
|
53
|
+
let(:sizes_command) { double('sizes', call: sizes_results) }
|
23
54
|
|
55
|
+
context 'search success' do
|
56
|
+
it 'should return image url for search keyword' do
|
57
|
+
expect(subject.call('Volkswagen Golf in Luxembourg')).to eq('https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_q.jpg')
|
58
|
+
end
|
59
|
+
end
|
24
60
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
4
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
5
|
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
@@ -44,57 +46,55 @@ RSpec.configure do |config|
|
|
44
46
|
# triggering implicit auto-inclusion in groups with matching metadata.
|
45
47
|
config.shared_context_metadata_behavior = :apply_to_host_groups
|
46
48
|
|
47
|
-
# The settings below are suggested to provide a good initial experience
|
48
|
-
# with RSpec, but feel free to customize to your heart's content.
|
49
|
-
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
#
|
63
|
-
#
|
64
|
-
# - http://
|
65
|
-
# - http://
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
#
|
70
|
-
#
|
71
|
-
|
72
|
-
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
|
87
|
-
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
|
93
|
-
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
Kernel.srand config.seed
|
99
|
-
=end
|
49
|
+
# The settings below are suggested to provide a good initial experience
|
50
|
+
# with RSpec, but feel free to customize to your heart's content.
|
51
|
+
# # This allows you to limit a spec run to individual examples or groups
|
52
|
+
# # you care about by tagging them with `:focus` metadata. When nothing
|
53
|
+
# # is tagged with `:focus`, all examples get run. RSpec also provides
|
54
|
+
# # aliases for `it`, `describe`, and `context` that include `:focus`
|
55
|
+
# # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
56
|
+
# config.filter_run_when_matching :focus
|
57
|
+
#
|
58
|
+
# # Allows RSpec to persist some state between runs in order to support
|
59
|
+
# # the `--only-failures` and `--next-failure` CLI options. We recommend
|
60
|
+
# # you configure your source control system to ignore this file.
|
61
|
+
# config.example_status_persistence_file_path = "spec/examples.txt"
|
62
|
+
#
|
63
|
+
# # Limits the available syntax to the non-monkey patched syntax that is
|
64
|
+
# # recommended. For more details, see:
|
65
|
+
# # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
66
|
+
# # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
67
|
+
# # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
68
|
+
# config.disable_monkey_patching!
|
69
|
+
#
|
70
|
+
# # This setting enables warnings. It's recommended, but in some cases may
|
71
|
+
# # be too noisy due to issues in dependencies.
|
72
|
+
# config.warnings = true
|
73
|
+
#
|
74
|
+
# # Many RSpec users commonly either run the entire suite or an individual
|
75
|
+
# # file, and it's useful to allow more verbose output when running an
|
76
|
+
# # individual spec file.
|
77
|
+
# if config.files_to_run.one?
|
78
|
+
# # Use the documentation formatter for detailed output,
|
79
|
+
# # unless a formatter has already been configured
|
80
|
+
# # (e.g. via a command-line flag).
|
81
|
+
# config.default_formatter = "doc"
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# # Print the 10 slowest examples and example groups at the
|
85
|
+
# # end of the spec run, to help surface which specs are running
|
86
|
+
# # particularly slow.
|
87
|
+
# config.profile_examples = 10
|
88
|
+
#
|
89
|
+
# # Run specs in random order to surface order dependencies. If you find an
|
90
|
+
# # order dependency and want to debug it, you can fix the order by providing
|
91
|
+
# # the seed, which is printed after each run.
|
92
|
+
# # --seed 1234
|
93
|
+
# config.order = :random
|
94
|
+
#
|
95
|
+
# # Seed global randomization in this process using the `--seed` CLI option.
|
96
|
+
# # Setting this allows you to use `--seed` to deterministically reproduce
|
97
|
+
# # test failures related to randomization by passing the same `--seed` value
|
98
|
+
# # as the one that triggered the failure.
|
99
|
+
# Kernel.srand config.seed
|
100
100
|
end
|
metadata
CHANGED
@@ -1,86 +1,98 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flico
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Amandeep Bhamra
|
8
|
-
autorequire:
|
7
|
+
- Amandeep Singh Bhamra
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-06-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: flickraw
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.9.10
|
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:
|
26
|
+
version: 0.9.10
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: mini_magick
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '4.12'
|
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: '
|
40
|
+
version: '4.12'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.4'
|
45
48
|
- - ">="
|
46
49
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
50
|
+
version: 2.4.14
|
48
51
|
type: :development
|
49
52
|
prerelease: false
|
50
53
|
version_requirements: !ruby/object:Gem::Requirement
|
51
54
|
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '2.4'
|
52
58
|
- - ">="
|
53
59
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
60
|
+
version: 2.4.14
|
55
61
|
- !ruby/object:Gem::Dependency
|
56
62
|
name: rake
|
57
63
|
requirement: !ruby/object:Gem::Requirement
|
58
64
|
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '13.0'
|
59
68
|
- - ">="
|
60
69
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
70
|
+
version: 13.0.6
|
62
71
|
type: :development
|
63
72
|
prerelease: false
|
64
73
|
version_requirements: !ruby/object:Gem::Requirement
|
65
74
|
requirements:
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '13.0'
|
66
78
|
- - ">="
|
67
79
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
80
|
+
version: 13.0.6
|
69
81
|
- !ruby/object:Gem::Dependency
|
70
82
|
name: rspec
|
71
83
|
requirement: !ruby/object:Gem::Requirement
|
72
84
|
requirements:
|
73
|
-
- - "
|
85
|
+
- - "~>"
|
74
86
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
87
|
+
version: '3.12'
|
76
88
|
type: :development
|
77
89
|
prerelease: false
|
78
90
|
version_requirements: !ruby/object:Gem::Requirement
|
79
91
|
requirements:
|
80
|
-
- - "
|
92
|
+
- - "~>"
|
81
93
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
83
|
-
description:
|
94
|
+
version: '3.12'
|
95
|
+
description:
|
84
96
|
email:
|
85
97
|
- amandeep.bhamra@gmail.com
|
86
98
|
executables:
|
@@ -114,7 +126,7 @@ homepage: https://github.com/amandeepbhamra/flico
|
|
114
126
|
licenses:
|
115
127
|
- MIT
|
116
128
|
metadata: {}
|
117
|
-
post_install_message:
|
129
|
+
post_install_message:
|
118
130
|
rdoc_options: []
|
119
131
|
require_paths:
|
120
132
|
- lib
|
@@ -129,9 +141,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
141
|
- !ruby/object:Gem::Version
|
130
142
|
version: '0'
|
131
143
|
requirements: []
|
132
|
-
|
133
|
-
|
134
|
-
signing_key:
|
144
|
+
rubygems_version: 3.4.14
|
145
|
+
signing_key:
|
135
146
|
specification_version: 4
|
136
|
-
summary: CLI tool to create collage using Flickr
|
147
|
+
summary: A CLI tool to create collage from keywords using Flickr
|
137
148
|
test_files: []
|