flickrage 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+ require 'thor'
3
+ require 'fileutils'
4
+ require 'flickraw'
5
+ require 'flickrage'
6
+
7
+ module Flickrage
8
+ # CLI is here
9
+ #
10
+ class CLI < Thor # rubocop:disable ClassLength
11
+ include Flickrage::Helpers::Log
12
+
13
+ default_task :help
14
+
15
+ map %w(c) => :collage
16
+ map %w(-V) => :version
17
+
18
+ # Overriding Thor method for custom initialization
19
+ #
20
+ def initialize(*args)
21
+ super
22
+
23
+ setup_config
24
+ set_logger
25
+ end
26
+
27
+ desc 'c / collage', 'Download & generate collage\'s'
28
+ long_desc <<-LONGDESC
29
+ `flickrage` is a tool which loves search on the Flickr & making collages from findings.
30
+
31
+ You have to enter name of the output file and a max number of downloading files.
32
+
33
+ Parameters helps you specify rectangle size for each image, collage name, it's location,
34
+ ..., and well the grid base size.
35
+
36
+ ### Examples
37
+
38
+ Set keywords:
39
+
40
+ $ flickrage -k some nice grapefruit
41
+
42
+ Set Flickr API keys:
43
+
44
+ $ flickrage --flickr-api-key SOMELONGKEY --flickr-shared-secret SOMELONGSECRET
45
+
46
+ Select output folder:
47
+
48
+ $ flickrage -k some nice grapefruit -o ./tmp
49
+
50
+ Enter collage file_name:
51
+
52
+ $ flickrage -k some nice grapefruit --file-name some.jpg
53
+
54
+ Get collage of top 10 images:
55
+
56
+ $ flickrage -k some nice grapefruit --max 10
57
+
58
+ Get collage of top 20 images:
59
+
60
+ $ flickrage -k some nice grapefruit --max 20
61
+
62
+ Get collage of top 10 images custom width & height:
63
+
64
+ $ flickrage -k some nice grapefruit --max 10 --width 160 --height 120
65
+
66
+ VERSION: #{Flickrage::VERSION}
67
+ LONGDESC
68
+ method_option :keywords,
69
+ type: :array,
70
+ required: true,
71
+ aliases: %w(-k),
72
+ banner: 'some nice grapefruit'
73
+ method_option :max,
74
+ type: :numeric,
75
+ enum: (1..20).to_a,
76
+ default: 10,
77
+ banner: '10',
78
+ desc: 'Select number of files.'
79
+ method_option :grid,
80
+ type: :numeric,
81
+ banner: '2',
82
+ desc: 'Select grid base number.'
83
+ method_option :width,
84
+ type: :numeric,
85
+ banner: '120',
86
+ desc: 'Set width for resize downloaded images.'
87
+ method_option :height,
88
+ type: :numeric,
89
+ banner: '80',
90
+ desc: 'Set height for resize downloaded images.'
91
+ method_option :log,
92
+ type: :string,
93
+ aliases: %w(-l),
94
+ banner: '/Users/someone/.flickrage/main.log',
95
+ desc: 'Log file path. By default logging is disabled.'
96
+ method_option :output,
97
+ type: :string,
98
+ aliases: %w(-o),
99
+ banner: './tmp',
100
+ desc: 'Output directory, where all data will be stored.'
101
+ method_option :file_name,
102
+ type: :string,
103
+ banner: './some.png',
104
+ desc: 'Name for file with collage.'
105
+ method_option :dict_path,
106
+ type: :string,
107
+ banner: '/usr/share/dict/words',
108
+ desc: 'Path to file with multiline words (dictionary).'
109
+ method_option :cleanup,
110
+ default: false,
111
+ type: :boolean,
112
+ aliases: %w(-c),
113
+ desc: 'Cleanup files before collage.'
114
+ method_option :verbose,
115
+ type: :boolean,
116
+ aliases: %w(-v),
117
+ desc: 'Verbose mode.'
118
+ method_option :quiet,
119
+ type: :boolean,
120
+ aliases: %w(-q),
121
+ desc: 'Quiet mode. If don\'t need any messages and in console.'
122
+
123
+ method_option :flickr_api_key,
124
+ type: :string,
125
+ banner: 'YOURLONGAPIKEY',
126
+ desc: 'FLICKR_API_KEY. if you can\'t use environment.'
127
+ method_option :flickr_shared_secret,
128
+ type: :string,
129
+ banner: 'YOURLONGSHAREDSECRET',
130
+ desc: 'FLICKR_SHARED_SECRET. if you can\'t use environment.'
131
+
132
+ def collage
133
+ cleanup if options['cleanup']
134
+ try_keys_first
135
+ init_flickraw
136
+
137
+ pipeline.run
138
+ rescue Flickrage::NoKeysError, Flickrage::SearchError, Flickrage::DownloadError,
139
+ Flickrage::NumberError, Flickrage::PathError => e
140
+ error_simple(e)
141
+ rescue => e
142
+ error_with_backtrace(e)
143
+ end
144
+
145
+ desc 'clean', 'Cleanup folder'
146
+ method_option :output,
147
+ type: :string,
148
+ required: true,
149
+ aliases: %w(-o),
150
+ banner: './some.png'
151
+ def clean
152
+ cleanup
153
+ end
154
+
155
+ desc 'version, -V', 'Shows the version of the currently installed Flickrage gem'
156
+ def version
157
+ puts Flickrage::VERSION
158
+ end
159
+
160
+ no_commands do
161
+ def error_simple(e)
162
+ logger.error e.message
163
+ raise e
164
+ end
165
+
166
+ def error_with_backtrace(e)
167
+ logger.error e.message
168
+ backtrace(e) if options.include? 'trace'
169
+ raise e
170
+ end
171
+
172
+ def pipeline
173
+ @pipeline ||= Pipeline.new(options)
174
+ end
175
+
176
+ def cleanup
177
+ return unless options['output']
178
+ return unless Dir.exist?(options['output'])
179
+ FileUtils.rm_rf("#{options['output']}/.", secure: true)
180
+ end
181
+
182
+ def setup_config
183
+ Flickrage.configure do |config|
184
+ config.logger = ::Logger.new(options['log']) if options['log']
185
+ config.verbose = options['verbose']
186
+ config.quiet = options['quiet']
187
+ config.logger_level = Logger::DEBUG if config.verbose
188
+
189
+ config.flickr_api_key = options['flickr_api_key'] || ENV['FLICKR_API_KEY']
190
+ config.flickr_shared_secret = options['flickr_shared_secret'] || ENV['FLICKR_SHARED_SECRET']
191
+
192
+ config.dict_path = options['dict_path'] if options['dict_path']
193
+
194
+ config.width = options['width'] if options['width']
195
+ config.height = options['height'] if options['height']
196
+
197
+ config.max = options['max'] if options['max']
198
+ config.grid = options['grid'] if options['grid']
199
+ config.output = options['output']
200
+ end
201
+ end
202
+
203
+ def set_logger
204
+ Flickrage.logger = Log.new(shell: shell)
205
+ end
206
+
207
+ def backtrace(e)
208
+ e.backtrace.each do |t|
209
+ logger.error t
210
+ end
211
+ end
212
+
213
+ def init_flickraw
214
+ FlickRaw.api_key = Flickrage.config.flickr_api_key
215
+ FlickRaw.shared_secret = Flickrage.config.flickr_shared_secret
216
+ end
217
+
218
+ # Check for Flickr API keys
219
+ def try_keys_first
220
+ logger.debug 'Checking Flickr Key\'s.'
221
+
222
+ return if Flickrage.api_keys?
223
+ logger.error Flickrage::NoKeysError.new('You must provide Flickr API credentials via --flickr-api-key, --flickr-shared-secret via options. or have it in the environment')
224
+ exit
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ module Flickrage
3
+ module Entity
4
+ autoload :Image, 'flickrage/entity/image'
5
+ autoload :ImageList, 'flickrage/entity/image_list'
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ module Flickrage
3
+ module Entity
4
+ class Image < Dry::Types::Struct
5
+ constructor_type(:schema)
6
+
7
+ attribute :id, Types::Coercible::Int
8
+ attribute :title, Types::Coercible::String
9
+ attribute :keyword, Types::Coercible::String
10
+ attribute :url, Types::Coercible::String
11
+ attribute :file_name, Types::Coercible::String
12
+ attribute :width, Types::Coercible::Int
13
+ attribute :height, Types::Coercible::Int
14
+
15
+ attribute :download, Types::Bool.default(false)
16
+ attribute :resize, Types::Bool.default(false)
17
+
18
+ alias downloaded? download
19
+ alias resized? resize
20
+
21
+ %w(download resize).each do |m|
22
+ define_method(:"finish_#{m}") do
23
+ instance_variable_set(:"@#{m}", true)
24
+ self
25
+ end
26
+ end
27
+
28
+ def local_path
29
+ File.absolute_path("#{Flickrage.config.output}/#{file_name}")
30
+ end
31
+
32
+ def resize_path
33
+ File.absolute_path("#{Flickrage.config.output}/#{Flickrage.config.resize_file_prefix}#{file_name}")
34
+ end
35
+
36
+ def file_name=(file_name)
37
+ @file_name = file_name
38
+ self
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ module Flickrage
3
+ module Entity
4
+ class ImageList < Dry::Types::Struct
5
+ constructor_type(:schema)
6
+
7
+ attribute :images, Types::Strict::Array.member(Flickrage::Entity::Image).default([])
8
+ attribute :not_founds, Types::Strict::Array.member(Types::Coercible::String).default([])
9
+ attribute :total, Types::Int.optional.default(0)
10
+ attribute :compose, Types::Bool.default(false)
11
+ attribute :collage_path, Types::Coercible::String
12
+
13
+ alias composed? compose
14
+
15
+ def downloaded
16
+ images.select(&:downloaded?)
17
+ end
18
+
19
+ def resized
20
+ images.select(&:resized?)
21
+ end
22
+
23
+ def finish_compose
24
+ @compose = true
25
+ self
26
+ end
27
+
28
+ def combine(image_list)
29
+ new_images = images + image_list
30
+ new_total = new_images.size
31
+ @images = new_images
32
+ @total = new_total
33
+ self
34
+ end
35
+
36
+ def clean
37
+ @images = images.compact
38
+ @total = images.size
39
+ self
40
+ end
41
+
42
+ def valid?
43
+ total == Flickrage.config.max
44
+ end
45
+
46
+ def merge_not_founds(new_not_founds)
47
+ @not_founds = not_founds + new_not_founds
48
+ self
49
+ end
50
+
51
+ def merge_images(new_images)
52
+ @images = images | new_images
53
+ self
54
+ end
55
+
56
+ def size
57
+ images.size
58
+ end
59
+
60
+ def collage_path=(file_name = nil)
61
+ file_name = "collage.#{Time.now.to_i}.jpg" if file_name.nil?
62
+ @collage_path = File.absolute_path("#{Flickrage.config.output}/#{file_name}")
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+ require 'tty-spinner'
3
+ require 'pastel'
4
+
5
+ module Flickrage
6
+ # Helpers for main class.
7
+ #
8
+ module Helpers
9
+ module Log
10
+ def logger
11
+ UniversalLogger
12
+ end
13
+
14
+ def speaker
15
+ UniversalSpeaker
16
+ end
17
+ end
18
+
19
+ module Dict
20
+ def sample_words(n = 1)
21
+ Flickrage.dict.sample(n).map(&:strip)
22
+ end
23
+
24
+ def sample_words_strict(n = 1, except: [])
25
+ return [] if Flickrage.dict.size < (n + except.size)
26
+ Flickrage.dict
27
+ .sample(n + except.size)
28
+ .map(&:strip)
29
+ .-(except)
30
+ .first(n)
31
+ end
32
+ end
33
+
34
+ module Tty
35
+ def spinner(message: '', format: :dots)
36
+ spin = TTY::Spinner.new('[:spinner] :title',
37
+ format: format,
38
+ interval: 20,
39
+ hide_cursor: true,
40
+ success_mark: color(color: :green, message: '+'),
41
+ error_mark: color(color: :red, message: 'x'))
42
+ spin.update(title: message)
43
+ spin.start
44
+
45
+ return spin unless block_given?
46
+
47
+ yield(spin)
48
+ end
49
+
50
+ private
51
+
52
+ def pastel
53
+ @pastel ||= Pastel.new
54
+ end
55
+
56
+ def color(color: :green, message: ' ')
57
+ pastel.send(color, message)
58
+ end
59
+ end
60
+
61
+ # UniversalLogger is a module to deal with singleton methods.
62
+ # Used to give classes access only for selected methods
63
+ #
64
+ module UniversalLogger
65
+ %w(debug info warn error fatal unknown).each do |name|
66
+ define_singleton_method(:"#{name}") { |*args, &block| Flickrage.logger.send(:"#{name}", *args, &block) }
67
+ end
68
+
69
+ def self.close
70
+ Flickrage.logger.close if Flickrage.logger
71
+ end
72
+ end
73
+
74
+ module UniversalSpeaker
75
+ %w(ask yes? no? print_table add_padding).each do |name|
76
+ define_singleton_method(:"#{name}") { |*args| Flickrage.logger.send(:"#{name}", *args) }
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+ require 'logger'
3
+
4
+ module Flickrage
5
+ # Shared logger
6
+ #
7
+ class Log
8
+ attr_reader :shell
9
+ attr_accessor :quiet, :verbose
10
+ attr_writer :buffer, :instance
11
+
12
+ def initialize(options = {})
13
+ @verbose = Flickrage.config.verbose
14
+ @quiet = Flickrage.config.quiet
15
+ options.each { |key, option| instance_variable_set(:"@#{key}", option) }
16
+ instance.level = Flickrage.config.logger_level if instance
17
+ end
18
+
19
+ def instance
20
+ @instance ||= Flickrage.config.logger
21
+ end
22
+
23
+ def buffer
24
+ @buffer ||= %w()
25
+ end
26
+
27
+ def shell=(shell)
28
+ @shell = shell unless quiet
29
+ end
30
+
31
+ def close
32
+ instance.close if instance
33
+ end
34
+
35
+ %w(debug info warn error fatal unknown).each_with_index do |name, severity|
36
+ define_method(:"#{name}") { |*args, &block| log severity, *args, &block }
37
+ end
38
+
39
+ %w(yes? no?).each do |name|
40
+ define_method(:"#{name}") do |statement, color = :green|
41
+ shell.send(:"#{name}", statement, color) if shell
42
+ end
43
+ end
44
+
45
+ def print_table(*args)
46
+ args[0]&.each { |row| instance.info(row) } if instance
47
+ shell.print_table(*args) if shell
48
+ end
49
+
50
+ def ask(statement, color: :green, path: false)
51
+ shell.ask(statement, color, path: path) if shell
52
+ end
53
+
54
+ def add_padding
55
+ shell.say_status('', '') if shell
56
+ end
57
+
58
+ def log(severity, message = nil, progname = nil, &block)
59
+ buffer << message
60
+ instance.add(severity, message, progname, &block) if instance.respond_to?(:add)
61
+
62
+ say(message, color(severity)) unless print?(severity)
63
+ end
64
+
65
+ protected
66
+
67
+ def print?(type)
68
+ (type == Logger::DEBUG && !verbose) || quiet
69
+ end
70
+
71
+ def say(message, color)
72
+ shell.say message, color if shell
73
+ end
74
+
75
+ def color(severity)
76
+ case severity
77
+ when 0
78
+ :white
79
+ when 3
80
+ :red
81
+ when 2
82
+ :yellow
83
+ else
84
+ :green
85
+ end
86
+ end
87
+ end
88
+ end