flickrage 0.1.1

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.
@@ -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