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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d21b66c84738f5f1ef760829af88cf4f40d7c8fb
4
- data.tar.gz: b27c22ad9028030c2be42e66b9d00a7f42534811
2
+ SHA256:
3
+ metadata.gz: adca32d7e5ee313f13e0ea06a104fe7f1f2fb0dee521b2997c96cd0901c67dcf
4
+ data.tar.gz: adf98002d3585edf868ab0ddc0a655cf48b932aa3671a079217398956b0c5705
5
5
  SHA512:
6
- metadata.gz: affd36e436da1a851f4e3d43382b8530d17c8db550d220bf89063567076cbb61d87c69e4068ce5bd4dc4b0492abd29fe0a570c50a85c8935844d911d66ce7a3c
7
- data.tar.gz: 9186a05a4a81b5f15eea86c955eb8e92b1e6061bf7ae84fa401393e07c12a26b30df7c8a2133cdbab9264d8ff22b3375c3987f41109025f7ddb8c72565a7f928
6
+ metadata.gz: f7813fde11543901c15b0f212e81a6d36968f82be4974784f09b5a45365c13e6e83a50502ecd70b56a7a9a10acdcc071cfa00dfc330516ce14175cb3358ed704
7
+ data.tar.gz: cf5e50c394033d3feee1f4bbb3d74ed40c07952b440b12e2e28fd16dc9d2340d12bf80526d30a2fddb2426034fc6dee57ca0947454c709ef2ba106ad77176fc8
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.3.0
1
+ 3.2.2
data/Gemfile CHANGED
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
  gemspec
data/Gemfile.lock CHANGED
@@ -1,33 +1,33 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- flico (0.0.1)
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.3)
12
- flickraw (0.9.9)
13
- mini_magick (4.7.1)
14
- rake (12.0.0)
15
- rspec (3.6.0)
16
- rspec-core (~> 3.6.0)
17
- rspec-expectations (~> 3.6.0)
18
- rspec-mocks (~> 3.6.0)
19
- rspec-core (3.6.0)
20
- rspec-support (~> 3.6.0)
21
- rspec-expectations (3.6.0)
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.6.0)
24
- rspec-mocks (3.6.0)
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.6.0)
27
- rspec-support (3.6.0)
26
+ rspec-support (~> 3.12.0)
27
+ rspec-support (3.12.1)
28
28
 
29
29
  PLATFORMS
30
- ruby
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
- 1.15.1
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
@@ -1,5 +1,7 @@
1
- require "rspec/core/rake_task"
2
- require "bundler/gem_tasks"
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/rake_task'
4
+ require 'bundler/gem_tasks'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
- task :default => :spec
7
+ task default: :spec
data/bin/console CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "flico"
5
- require "irb"
4
+ require 'bundler/setup'
5
+ require 'flico'
6
+ require 'irb'
6
7
 
7
8
  IRB.start
data/bin/flico CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  $LOAD_PATH << './lib'
3
5
  require 'flico'
4
6
  Flico::CommandLineInterface.start(ARGV)
data/flico.gemspec CHANGED
@@ -1,26 +1,26 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
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
- spec.version = '0.0.2'
8
- spec.name = 'flico'
9
- spec.summary = %q{CLI tool to create collage using Flickr}
10
- spec.homepage = "https://github.com/amandeepbhamra/flico"
11
- spec.license = 'MIT'
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
- spec.authors = ['Amandeep Bhamra']
14
- spec.email = ["amandeep.bhamra@gmail.com"]
15
-
16
- spec.files = `git ls-files -z`.split("\x0")
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
- spec.add_dependency 'flickraw'
22
- spec.add_dependency 'mini_magick'
23
- spec.add_development_dependency "bundler"
24
- spec.add_development_dependency "rake"
25
- spec.add_development_dependency "rspec"
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
- class ApplicationError < StandardError; end
4
- class KeywordMissing < StandardError; end
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
- def initialize(resources)
13
- @resources = resources
14
- end
12
+ def initialize(resources)
13
+ @resources = resources
14
+ end
15
15
 
16
- def create_collage
17
- image_urls = []
18
- begin
19
- image_urls.push get_images
20
- end while image_urls.count < 10
21
- collage(image_urls)
22
- end
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
- def collage(images)
25
- resources.save_collage.call(resources.collager.call(images))
26
- end
25
+ def collage(images)
26
+ resources.save_collage.call(resources.collager.call(images))
27
+ end
27
28
 
28
- def get_images
29
- keywords_count = 10
30
- keyword = resources.dictionary.call
31
- image_url = resources.flickr_api.call(keyword)
32
- downloaded_image = 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) > 0
36
- raise ApplicationError, "Failed getting image after retrying #{MAX_KEYWORD_RETRIES} times"
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
- module Flico
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
@@ -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
- class Collager
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
- def call(image_urls)
14
- images = image_urls.map { |p| MiniMagick::Image.open p.path }
15
- temp_file = Tempfile.new ['collage_maker', '.png']
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
- resized_images = image_urls.map.with_index do |path, idx|
23
- image = MiniMagick::Image.open path.path
24
- image.crop(grid.crop_rectangle(idx, image.width, image.height).to_mm)
25
- image.resize(grid.resize_rectangle(idx, image.width, image.height).to_mm)
26
- print_to_canvas(image, grid.cell_rectangle(idx), temp_file)
27
- end
28
- temp_file
29
- end
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
- private
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
- def print_to_canvas(image, rectangle, temp_file)
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
@@ -1,30 +1,24 @@
1
- module Flico
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
- def call
11
- @words.shift || dictionary_word
12
- end
3
+ module Flico
4
+ class Dictionary
5
+ def initialize(dictionary_path)
6
+ @dictionary_path = dictionary_path
7
+ @words = []
8
+ end
13
9
 
14
- def append(keywords)
15
- @words += keywords
16
- end
10
+ def call
11
+ @words.shift || dictionary_word
12
+ end
17
13
 
18
- private
14
+ def append(keywords)
15
+ @words += keywords
16
+ end
19
17
 
20
- def dictionary_word
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
@@ -1,58 +1,55 @@
1
- module Flico
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
- class Grid
6
-
7
- GRID = [[0,1,1,2], [3,4,5,6], [7,8,8,9]]
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, y = 0, 0
45
- image = Cell.new(image_width, image_height)
46
- cell = cell_rectangle(cell_name)
47
-
48
- unless image.aspect_ratio >= cell.aspect_ratio
49
- final_width = image.width
50
- final_height = image.height - (image.height - (image.width / cell.aspect_ratio))
51
- else
52
- final_width = image_width - (image.width - (image.height * cell.aspect_ratio))
53
- final_height = image.height
54
- end
55
-
56
- crop_rectangle = Cell.new(x, y, final_width, final_height)
57
- crop_rectangle
58
- end
59
-
60
- def resize_rectangle(cell_name, image_width, image_height)
61
- image = Cell.new(image_width, image_height)
62
- cell_rectangle(cell_name)
63
- end
64
-
65
- def cell_rectangle(name)
66
- row, column = row_cells.find_index {|row| row.include? name }, column_cells.find_index {|row| row.include? name }
67
- Cell.new(column * (CANV_WIDTH / columns), row * (CANV_HEIGHT / rows), cell_width(name), cell_height(name))
68
- end
69
-
70
- private
71
-
72
- def size_multiplier(table, name)
73
- table.map {|row| row.count(name)}.max
74
- end
75
-
76
- def cell_size(total, divisions, multiplier)
77
- (multiplier == 0) ? 0 : ((total / divisions) * multiplier)
78
- end
79
- end
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
- class FetchingError < StandardError; end
10
-
11
- class FetchImage
12
-
13
- def call(url)
14
- tempfile = Tempfile.new 'temp_image'
15
- IO.copy_stream(open(url, read_timeout: 5), tempfile)
16
- tempfile.rewind
17
- tempfile
18
- rescue OpenURI::HTTPError => e
19
- raise FetchingError, "Couldn't download from: #{url} due to #{e.message}"
20
- end
21
-
22
- end
23
-
24
- class SaveCollage
25
-
26
- attr_accessor :output_file_name
27
-
28
- def call(file_path)
29
- file_name = output_file_name || validate_file_name
30
- FileUtils.mv file_path, file_name
31
- puts "Flicollage saved at #{file_name}"
32
- end
33
-
34
- private
35
-
36
- def validate_file_name
37
- puts "Enter file name for collage (press ENTER to use '#{default}')"
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
- class CommandLineInterface
13
-
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
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
- def self.validate_dictionary
54
- dictionary_path = '/usr/share/dict/words'
55
- if File.exist?(dictionary_path)
56
- dictionary = Dictionary.new(dictionary_path)
57
- else
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
- end
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
- class Resource
66
-
67
- attr_reader :flickr_api, :dictionary, :fetch_image, :collager, :save_collage
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
- 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
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
- end
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
@@ -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
- let(:search_results) {[{"id"=>"33472607802", "owner"=>"78759190@N05", "secret"=>"42e011bcf5", "server"=>"2805", "farm"=>3, "title"=>"Volkswagen Golf in Luxembourg", "ispublic"=>1, "isfriend"=>0, "isfamily"=>0}]}
7
- let(:sizes_results) {
8
- [
9
- {"label"=>"Square", "width"=>75, "height"=>75, "source"=>"https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_s.jpg", "url"=>"https://farm3.staticflickr.com/2805/33472607802_42e011bcf5/sizes/sq/", "media"=>"photo"},
10
- {"label"=>"Large Square", "width"=>"150", "height"=>"150", "source"=>"https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_q.jpg", "url"=>"https://farm3.staticflickr.com/2805/33472607802_42e011bcf5/sizes/q/", "media"=>"photo"},
11
- {"label"=>"Thumbnail", "width"=>"100", "height"=>"66", "source"=>"https://farm3.staticflickr.com/2805/33472607802_42e011bcf5_t.jpg", "url"=>"https://farm3.staticflickr.com/2805/33472607802_42e011bcf5/sizes/t/", "media"=>"photo"}
12
- ]
13
- }
14
- let(:subject) { described_class.new(search_command: search_command, sizes_command: sizes_command) }
15
- let(:search_command) { double('search', call: search_results ) }
16
- let(:sizes_command) { double('sizes', call: sizes_results)}
17
-
18
- context 'search success' do
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
- =begin
50
- # This allows you to limit a spec run to individual examples or groups
51
- # you care about by tagging them with `:focus` metadata. When nothing
52
- # is tagged with `:focus`, all examples get run. RSpec also provides
53
- # aliases for `it`, `describe`, and `context` that include `:focus`
54
- # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
55
- config.filter_run_when_matching :focus
56
-
57
- # Allows RSpec to persist some state between runs in order to support
58
- # the `--only-failures` and `--next-failure` CLI options. We recommend
59
- # you configure your source control system to ignore this file.
60
- config.example_status_persistence_file_path = "spec/examples.txt"
61
-
62
- # Limits the available syntax to the non-monkey patched syntax that is
63
- # recommended. For more details, see:
64
- # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
65
- # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
66
- # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
67
- config.disable_monkey_patching!
68
-
69
- # This setting enables warnings. It's recommended, but in some cases may
70
- # be too noisy due to issues in dependencies.
71
- config.warnings = true
72
-
73
- # Many RSpec users commonly either run the entire suite or an individual
74
- # file, and it's useful to allow more verbose output when running an
75
- # individual spec file.
76
- if config.files_to_run.one?
77
- # Use the documentation formatter for detailed output,
78
- # unless a formatter has already been configured
79
- # (e.g. via a command-line flag).
80
- config.default_formatter = "doc"
81
- end
82
-
83
- # Print the 10 slowest examples and example groups at the
84
- # end of the spec run, to help surface which specs are running
85
- # particularly slow.
86
- config.profile_examples = 10
87
-
88
- # Run specs in random order to surface order dependencies. If you find an
89
- # order dependency and want to debug it, you can fix the order by providing
90
- # the seed, which is printed after each run.
91
- # --seed 1234
92
- config.order = :random
93
-
94
- # Seed global randomization in this process using the `--seed` CLI option.
95
- # Setting this allows you to use `--seed` to deterministically reproduce
96
- # test failures related to randomization by passing the same `--seed` value
97
- # as the one that triggered the failure.
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.2
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: 2017-06-18 00:00:00.000000000 Z
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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
- rubyforge_project:
133
- rubygems_version: 2.5.1
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: []