simple-images-downloader 1.0.3 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -1
  3. data/Gemfile.lock +1 -3
  4. data/README.md +14 -1
  5. data/lib/simple_images_downloader/client.rb +10 -0
  6. data/lib/simple_images_downloader/configuration.rb +53 -1
  7. data/lib/simple_images_downloader/dispenser.rb +15 -1
  8. data/lib/simple_images_downloader/downloader.rb +31 -6
  9. data/lib/simple_images_downloader/errors.rb +13 -3
  10. data/lib/simple_images_downloader/line.rb +14 -10
  11. data/lib/simple_images_downloader/runner.rb +7 -0
  12. data/lib/simple_images_downloader/source_file.rb +13 -1
  13. data/lib/simple_images_downloader/strategies/from_file_strategy.rb +24 -0
  14. data/lib/simple_images_downloader/strategies/from_url_strategy.rb +18 -0
  15. data/lib/simple_images_downloader/strategies/strategy.rb +14 -0
  16. data/lib/simple_images_downloader/stringio_to_tempfile.rb +9 -2
  17. data/lib/simple_images_downloader/validatable/destination_validator.rb +14 -3
  18. data/lib/simple_images_downloader/validatable/file_accessibility_validator.rb +11 -3
  19. data/lib/simple_images_downloader/validatable/file_persistance_validator.rb +11 -3
  20. data/lib/simple_images_downloader/validatable/mime_type_validator.rb +74 -0
  21. data/lib/simple_images_downloader/validatable/validator.rb +2 -0
  22. data/lib/simple_images_downloader/validatable.rb +19 -0
  23. data/lib/simple_images_downloader/version.rb +2 -1
  24. data/lib/simple_images_downloader.rb +38 -15
  25. data/rubocop.yml +3 -0
  26. data/simple_images_downloader.gemspec +0 -2
  27. metadata +5 -16
  28. data/lib/simple_images_downloader/validatable/image_path_validator.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d6712c3ea229e3cb09526ef0f0144f79fa9dcf1d7af6f20b23a83502ea34be1
4
- data.tar.gz: b1147429efdf91682067decf309aec22050f2ed835b0b0261560f93fd6a63aa6
3
+ metadata.gz: 0e7adf857ef1d9f8490a079e5828b6025d0b44a21ec098bfc0012124c61223a9
4
+ data.tar.gz: 50175489068f5c9c7587b940d0690d335f8787f4e78c3f7ede9b26982b13b1c5
5
5
  SHA512:
6
- metadata.gz: b3cf6db2633b3ebf0e094482079193fa2dbcb75e169c70ea3778e9c46b00abbdae4089378c36d8570630aa0698ca5343ee1a97e67f6e8e193c1d234c240bb35f
7
- data.tar.gz: cd9645e49d0a1658c74a6acde0a5608bd5a79fea67acda6235057c018066aaee7b5352ca150a5bf77d926189cd41dfb43950e62f0257edab98f31fc25f7a3f1b
6
+ metadata.gz: e205d6d7feb52617f2d526765dd3b3f82509d5cfc2343ca90dbf966966784fa8099cae222904b245ff338d46f5f38883a648d50914c39ec7d3b285af0c6e61a6
7
+ data.tar.gz: 2660f410b9d48c4876e48fd387c69ad12707b29fef9e02ccb453331fc557c42249d0311da6f0df9eafe50e894ce23fa50c7260d2a6b4f091664ab3247b8dbcbe
data/CHANGELOG.md CHANGED
@@ -1,6 +1,16 @@
1
+ ## 1.1.1 - 2023-09-30
2
+ - Fixed setting of mime types from configuration. It was using default values instead of the ones set in the configuration
3
+ - Added documentation to classes and methods
4
+
5
+ ## 1.1.0 - 2023-09-30
6
+ - Add validation of downloaded content over path validation
7
+ - Add ability to extend mime types from configuration
8
+ - Removed zeitwerk
9
+ - Refactored main interface
10
+
1
11
  ## 1.0.3 - 2023-09-27
2
12
  - Corrected destination validation in SimpleImagesDownloader::Dispenser
3
- - Corrected source file validation in SimpleImagesDownloader::SourceFiles
13
+ - Corrected source file validation in SimpleImagesDownloader::SourceFile
4
14
  - Refactored
5
15
  - Fixed error message when destination is invalid
6
16
 
data/Gemfile.lock CHANGED
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- simple-images-downloader (1.0.3)
5
- zeitwerk (~> 2.4.0)
4
+ simple-images-downloader (1.1.1)
6
5
 
7
6
  GEM
8
7
  remote: https://rubygems.org/
@@ -76,7 +75,6 @@ GEM
76
75
  addressable (>= 2.3.6)
77
76
  crack (>= 0.3.2)
78
77
  hashdiff (>= 0.4.0, < 2.0.0)
79
- zeitwerk (2.4.2)
80
78
 
81
79
  PLATFORMS
82
80
  ruby
data/README.md CHANGED
@@ -33,7 +33,7 @@ Or install it yourself as:
33
33
 
34
34
  The gem is provided with executable.
35
35
 
36
- You can either run plain commmand with file containing list of url:
36
+ You can either run plain command with file containing list of url:
37
37
 
38
38
  ```bash
39
39
  simple-images-downloader <path_to_file>
@@ -67,6 +67,7 @@ Downloading https://test-for-simple-images-downloader.s3.eu-central-1.amazonaws.
67
67
  Downloading is finished
68
68
  ```
69
69
 
70
+ ## Configuration
70
71
  **By default the images stored in a directory the script is run.**
71
72
  You can also configure the place where the images would be stored to:
72
73
 
@@ -78,6 +79,18 @@ You can also configure the place where the images would be stored to:
78
79
  [2] pry(main)* end
79
80
  ```
80
81
 
82
+ **By default the gem downloads only images with mime types: image/avif, image/gif, image/apng, image/jpg, image/jpeg, image/png, image/svg+xml, image/webp.**
83
+ You can also configure the mime types to download images with:
84
+
85
+
86
+ ```ruby
87
+ [1] pry(main)> require 'simple_images_downloader'
88
+
89
+ [2] pry(main)> SimpleImagesDownloader::Configuration.configure do |config|
90
+ [2] pry(main)* # You must use RFC 1341 - MIME (Multipurpose Internet Mail Extensions) format
91
+ [2] pry(main)* config.valid_mime_types = %w(image/png image/jpg image/jpeg)
92
+ [2] pry(main)* end
93
+ ```
81
94
  ## Development
82
95
 
83
96
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,11 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleImagesDownloader
4
+ # add documentation to this class using YARD
5
+
6
+ # Client class
7
+ # Responsible for opening the URI and handling errors
8
+ #
9
+ # @example
10
+ # SimpleImagesDownloader::Client.new.open(uri)
11
+ #
4
12
  class Client
5
13
  def initialize(options = Configuration::REQUEST_OPTIONS)
6
14
  @options = options
7
15
  end
8
16
 
17
+ # @param uri [URI] URI object
18
+ # @return [StringIO] StringIO object
9
19
  def open(uri)
10
20
  uri.open(@options)
11
21
  rescue OpenURI::HTTPRedirect
@@ -1,11 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleImagesDownloader
4
+ # Configuration class
5
+ # @example
6
+ # SimpleImagesDownloader::Configuration.configure do |config|
7
+ # config.destination = './images'
8
+ # config.valid_mime_types = ['image/jpeg', 'image/png']
9
+ # end
10
+ #
11
+ # SimpleImagesDownloader::Configuration.destination
12
+ # # => './images'
13
+ #
14
+ # SimpleImagesDownloader::Configuration.valid_mime_types
15
+ # # => ['image/jpeg', 'image/png']
16
+ #
4
17
  class Configuration
5
18
  include Singleton
6
19
 
7
20
  ACCESSORS = %i[
8
21
  destination
22
+ valid_mime_types
9
23
  ].freeze
10
24
 
11
25
  REQUEST_OPTIONS = {
@@ -15,12 +29,50 @@ module SimpleImagesDownloader
15
29
  read_timeout: 30
16
30
  }.freeze
17
31
 
32
+ # https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types#common_image_file_types
33
+ DEFAULT_VALID_MIME_TYPES = [
34
+ 'image/avif',
35
+ 'image/gif',
36
+ 'image/apng',
37
+ 'image/jpg',
38
+ 'image/jpeg',
39
+ 'image/png',
40
+ 'image/svg+xml',
41
+ 'image/webp'
42
+ ].freeze
43
+
44
+ DEFAULT_DESTINATION = './'
45
+
18
46
  attr_accessor(*ACCESSORS)
19
47
 
20
48
  def initialize
21
- @destination = './'
49
+ @destination = DEFAULT_DESTINATION
50
+ @valid_mime_types = DEFAULT_VALID_MIME_TYPES
51
+ end
52
+
53
+ # Allows to set valid_mime_types to check against allowed mime types
54
+ #
55
+ # @param value [Array] Array of valid mime types followed by
56
+ # RFC 1341 - MIME (Multipurpose Internet Mail Extensions) format
57
+ # @raise [BaseError] if value is not an Array
58
+ #
59
+ def valid_mime_types=(value)
60
+ raise Errors::BaseError, 'valid_mime_types must be an array' unless value.is_a?(Array)
61
+
62
+ @valid_mime_types = value
22
63
  end
23
64
 
65
+ #### Configurable options
66
+ #
67
+ # destination: String, default: './', description: 'Destination folder to put downloaded images'
68
+ # valid_mime_types: Array,
69
+ # default: [
70
+ # 'image/avif', 'image/gif', 'image/apng', 'image/jpg',
71
+ # 'image/jpeg', 'image/png', 'image/svg+xml', 'image/webp'
72
+ # ], description: 'Valid mime types to download'
73
+ #
74
+ # @yield [config] Yields the Configuration object to the block
75
+ #
24
76
  def self.configure
25
77
  yield instance
26
78
  end
@@ -1,20 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleImagesDownloader
4
+ # Dispenser class
5
+ # Responsible for moving tempfile to destination directory
6
+ #
7
+ # @example
8
+ # SimpleImagesDownloader::Dispenser.new(tempfile, 'https://example.com/image.jpg').place
9
+ #
4
10
  class Dispenser
5
11
  extend Forwardable
6
12
  include Validatable
7
13
 
8
14
  def_delegator 'SimpleImagesDownloader::Configuration', :destination, :destination_dir
9
15
 
16
+ # @param source [Tempfile] Tempfile object
17
+ # @param remote_path [String] original path of the image from input of SimpleImagesDownloader module
18
+ # @param validators [Array] array of validators for validating the destination directory.
19
+ # Default: [DestinationValidator.new]
10
20
  def initialize(source, remote_path, validators = [DestinationValidator.new])
11
21
  @source = source
12
22
  @remote_path = remote_path
13
23
  @validators = validators
14
24
  end
15
25
 
26
+ # Moves tempfile to destination directory
27
+ #
28
+ # @raise [Errors::BadDestination] if destination directory is not valid
29
+ # @see DestinationValidator
16
30
  def place
17
- validate!(destination_dir)
31
+ validate!({ path: destination_dir })
18
32
 
19
33
  FileUtils.mv @source, target
20
34
  end
@@ -1,23 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleImagesDownloader
4
+ # Downloader class
5
+ # Responsible for downloading images from URI and placing them to the destination folder
6
+ # allows to use custom client and validators for downloading
7
+ #
8
+ # @example
9
+ # SimpleImagesDownloader::Downloader.new(uri).download
10
+ #
4
11
  class Downloader
5
- def initialize(uri)
6
- @uri = uri
12
+ include Validatable
13
+
14
+ # @param uri [URI] URI object from which image will be downloaded
15
+ # @param client [Client] Client object for opening the URI. Default: Client.new
16
+ # @param validators [Array] array of validators for validating the response. Default: [MimeTypeValidator.new]
17
+ def initialize(uri, client = Client.new, validators = [MimeTypeValidator.new])
18
+ @uri = uri
19
+ @client = client
20
+ @validators = validators
7
21
  end
8
22
 
23
+ # Downloads image from URI and places it to the destination folder
24
+ #
25
+ # @raise [Errors::EmptyResponse] if response is empty
26
+ # @raise [Errors::BadMimeType] if response is not an image
27
+ # @see Errors module
28
+ # @see MimeTypeValidator
9
29
  def download
10
30
  puts "Downloading #{@uri}"
11
31
 
12
- io = Client.new.open(@uri)
32
+ io = @client.open(@uri)
33
+
34
+ raise Errors::EmptyResponse, @uri if io.nil?
35
+
36
+ validate!({ path: @uri.to_s, io: io })
13
37
 
14
- downloaded_file = StringioToTempfile.convert(io) unless io.nil?
38
+ tempfile = StringioToTempfile.convert(io)
15
39
 
16
- Dispenser.new(downloaded_file, @uri.path).place
40
+ Dispenser.new(tempfile, @uri.path).place
17
41
 
18
42
  puts 'Downloading is finished'
19
43
  ensure
20
- downloaded_file&.close
44
+ io&.close
45
+ tempfile&.close
21
46
  end
22
47
  end
23
48
  end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleImagesDownloader
4
+ # Errors module
5
+ # Responsible for storing all errors
6
+ #
4
7
  module Errors
5
8
  class BaseError < StandardError; end
6
9
 
@@ -25,9 +28,9 @@ module SimpleImagesDownloader
25
28
  end
26
29
  end
27
30
 
28
- class MissingImageInPath < BaseError
29
- def initialize(path)
30
- message = "The path doesn't contain image #{path}"
31
+ class BadMimeType < BaseError
32
+ def initialize(path, mime_type)
33
+ message = "The image with path: #{path} has wrong mime type #{mime_type}"
31
34
  super(message)
32
35
  end
33
36
  end
@@ -66,5 +69,12 @@ module SimpleImagesDownloader
66
69
  super(message)
67
70
  end
68
71
  end
72
+
73
+ class EmptyResponse < BaseError
74
+ def initialize(uri)
75
+ message = "Nothing returned from request #{uri}"
76
+ super(message)
77
+ end
78
+ end
69
79
  end
70
80
  end
@@ -1,20 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleImagesDownloader
4
+ # Line class
5
+ # Responsible for parsing the string into URI object
6
+ #
7
+ # @example
8
+ # SimpleImagesDownloader::Line.new('https://example.com/image.jpg').uri
9
+ # # => #<URI::HTTPS https://example.com/image.jpg>
10
+ #
4
11
  class Line
5
- include Validatable
6
-
7
- def initialize(string, validators = [ImagePathValidator.new])
8
- @string = string
9
- @validators = validators
12
+ def initialize(string)
13
+ @string = string
10
14
  end
11
15
 
16
+ # @return [URI] URI object
17
+ # @raise [Errors::BadUrl] if string is not a valid URI
18
+ # @see Errors::BadUrl
19
+ #
12
20
  def uri
13
- parsed_uri = URI.parse(@string)
14
-
15
- validate!(@string)
16
-
17
- parsed_uri
21
+ URI.parse(@string)
18
22
  rescue URI::Error
19
23
  raise Errors::BadUrl, @string
20
24
  end
@@ -1,7 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleImagesDownloader
4
+ # Runner class
5
+ # Responsible for invoking interface to download images from file
6
+ #
7
+ # @example
8
+ # SimpleImagesDownloader::Runner.invoke('./urls.txt')
9
+ #
4
10
  class Runner
11
+ # Allows to invoke interface to download images from file
5
12
  def self.invoke
6
13
  raise SimpleImagesDownloader::Errors::MissingFileArgumentError if ARGV.size.zero?
7
14
 
@@ -1,16 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleImagesDownloader
4
+ # SourceFile class
5
+ # Responsible for opening the file of URLs and validating it
6
+ #
7
+ # @example
8
+ # SimpleImagesDownloader::SourceFile.new('./urls.txt').each_line do |line|
9
+ # puts line
10
+ # end
11
+ #
4
12
  class SourceFile
5
13
  include Validatable
6
14
 
15
+ # @param path [String] path to file
16
+ # @param validators [Array] array of validators
7
17
  def initialize(path, validators = [FilePersistanceValidator.new, FileAccessibilityValidator.new])
8
18
  @path = path
9
19
  @validators = validators
10
20
  end
11
21
 
22
+ # @yield [line] passes each line of file to block
23
+ # @yieldparam line [String] line of file
12
24
  def each_line(&block)
13
- validate!(@path)
25
+ validate!({ path: @path })
14
26
 
15
27
  begin
16
28
  file.each(chomp: true, &block)
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleImagesDownloader
4
+ module Strategies
5
+ class FromFileStrategy < Strategy
6
+ def initialize(path)
7
+ super
8
+ @path = path
9
+ end
10
+
11
+ def process
12
+ source_file = SourceFile.new(@path)
13
+
14
+ source_file.each_line do |line|
15
+ uri = Line.new(line).uri
16
+ Downloader.new(uri).download
17
+ rescue Errors::BaseError => e
18
+ puts e.message
19
+ next
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleImagesDownloader
4
+ module Strategies
5
+ class FromUrlStrategy < Strategy
6
+ def initialize(url)
7
+ super
8
+ @url = url
9
+ end
10
+
11
+ def process
12
+ uri = Line.new(@url).uri
13
+
14
+ Downloader.new(uri).download
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleImagesDownloader
4
+ module Strategies
5
+ class Strategy
6
+ def initialize(_)
7
+ end
8
+
9
+ def process
10
+ raise NotImplementedError, 'must be implemented in subclass'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,16 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleImagesDownloader
4
+ # StringIOToTempfile module
5
+ # Responsible for converting StringIO to Tempfile
6
+ #
7
+ # @example
8
+ # SimpleImagesDownloader::StringioToTempfile.convert(stringio)
9
+ # # => #<Tempfile:0x00007f9b9c0b3a38>
10
+ #
4
11
  module StringioToTempfile
5
12
  module_function
6
13
 
14
+ # @param stringio [StringIO] StringIO object
15
+ # @return [Tempfile] Tempfile object
7
16
  def convert(stringio)
8
17
  tempfile = Tempfile.new(binmode: true)
9
18
 
10
19
  IO.copy_stream(stringio, tempfile)
11
20
 
12
- stringio.close
13
-
14
21
  OpenURI::Meta.init tempfile, stringio
15
22
 
16
23
  tempfile
@@ -2,10 +2,21 @@
2
2
 
3
3
  module SimpleImagesDownloader
4
4
  module Validatable
5
+ # DestinationValidator class
6
+ # Responsible for validating destination path
7
+ #
8
+ # @example
9
+ # SimpleImagesDownloader::DestinationValidator.new.validate({ path: './images' })
10
+ #
5
11
  class DestinationValidator < Validator
6
- def validate(destination_dir)
7
- raise Errors::DestinationIsNotDirectory, destination_dir unless File.directory?(destination_dir)
8
- raise Errors::DestinationIsNotWritable, destination_dir unless File.writable?(destination_dir)
12
+ # @param options [Hash] hash with path to destination directory
13
+ # @raise [Errors::DestinationIsNotDirectory] if destination is not directory
14
+ # @raise [Errors::DestinationIsNotWritable] if destination is not writable
15
+ def validate(options)
16
+ path = options[:path]
17
+
18
+ raise Errors::DestinationIsNotDirectory, path unless File.directory?(path)
19
+ raise Errors::DestinationIsNotWritable, path unless File.writable?(path)
9
20
  end
10
21
  end
11
22
  end
@@ -2,11 +2,19 @@
2
2
 
3
3
  module SimpleImagesDownloader
4
4
  module Validatable
5
+ # FileAccessibilityValidator class
6
+ # Responsible for validating file accessibility
7
+ #
8
+ # @example
9
+ # SimpleImagesDownloader::FileAccessibilityValidator.new.validate({ path: './urls.txt' })
10
+ #
5
11
  class FileAccessibilityValidator < Validator
6
- def validate(path)
7
- return if File.readable?(path)
12
+ # @param options [Hash] hash with path to file
13
+ # @raise [Errors::PermissionsError] if file is not readable
14
+ def validate(options)
15
+ return if File.readable?(options[:path])
8
16
 
9
- raise Errors::PermissionsError, path
17
+ raise Errors::PermissionsError, options[:path]
10
18
  end
11
19
  end
12
20
  end
@@ -2,11 +2,19 @@
2
2
 
3
3
  module SimpleImagesDownloader
4
4
  module Validatable
5
+ # FilePersistanceValidator class
6
+ # Responsible for validating file persistance
7
+ #
8
+ # @example
9
+ # SimpleImagesDownloader::FilePersistanceValidator.new.validate({ path: './urls.txt' })
10
+ #
5
11
  class FilePersistanceValidator < Validator
6
- def validate(path)
7
- return if File.exist?(path)
12
+ # @param options [Hash] hash with path to file
13
+ # @raise [Errors::MissingFileError] if file does not exist
14
+ def validate(options)
15
+ return if File.exist?(options[:path])
8
16
 
9
- raise Errors::MissingFileError, path
17
+ raise Errors::MissingFileError, options[:path]
10
18
  end
11
19
  end
12
20
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleImagesDownloader
4
+ module Validatable
5
+ # MimeTypeValidator class
6
+ # Responsible for validating mime type of file
7
+ #
8
+ # @example
9
+ # SimpleImagesDownloader::MimeTypeValidator.new.validate({ path: './image.jpg', io: StringIO.new })
10
+ #
11
+ class MimeTypeValidator < Validator
12
+ extend Forwardable
13
+
14
+ def_delegator 'SimpleImagesDownloader::Configuration', :valid_mime_types, :valid_mime_types
15
+
16
+ # Validates mime type of file. The mime types are taken from file Configuration
17
+ #
18
+ # @param options [Hash] hash with path to file and io object. Example: { path: './image.jpg', io: StringIO }
19
+ # @raise [Errors::BadMimeType] if mime type is not valid
20
+ # @see Configuration::DEFAULT_VALID_MIME_TYPES to see default valid mime types or add your own
21
+ def validate(options)
22
+ path = options[:path]
23
+ io = options[:io]
24
+
25
+ mime_type = mime_type_of(io)
26
+
27
+ return if SimpleImagesDownloader::Configuration.valid_mime_types.include?(mime_type)
28
+
29
+ raise Errors::BadMimeType.new(path, mime_type)
30
+ end
31
+
32
+ private
33
+
34
+ # Returns mime type of file. Uses UNIX file command
35
+ #
36
+ # @see https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/determine_mime_type.rb#L94
37
+ # @param io [IO] io object
38
+ # @return [String] mime type of file
39
+ def mime_type_of(io)
40
+ Open3.popen3('file --mime-type --brief -') do |stdin, stdout, stderr, thread|
41
+ copy_stream(from: io, to: stdin.binmode)
42
+
43
+ io.rewind
44
+ stdin.close
45
+
46
+ status = thread.value
47
+
48
+ validate_thread_status(status)
49
+ $stderr.print(stderr.read)
50
+
51
+ output = stdout.read.strip
52
+
53
+ validate_command_output(output)
54
+
55
+ output
56
+ end
57
+ end
58
+
59
+ def copy_stream(from:, to:)
60
+ IO.copy_stream(from, to)
61
+ rescue Errno::EPIPE # rubocop:disable Lint/SuppressedException
62
+ end
63
+
64
+ def validate_thread_status(status)
65
+ raise Errors::BaseError, "file command failed to spawn: #{stderr.read}" if status.nil?
66
+ raise Errors::BaseError, "file command failed: #{stderr.read}" unless status.success?
67
+ end
68
+
69
+ def validate_command_output(output)
70
+ raise Errors::BaseError, "file command failed: #{output}" if output.include?('cannot open')
71
+ end
72
+ end
73
+ end
74
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module SimpleImagesDownloader
4
4
  module Validatable
5
+ # Validator class
6
+ # Responsible for defining interface for validators
5
7
  class Validator
6
8
  def validate(_value)
7
9
  raise NotImplementedError, 'must be implemented in subclass'
@@ -1,7 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleImagesDownloader
4
+ # Validatable module
5
+ # Responsible for validating the object using array of validators
6
+ #
7
+ # @example
8
+ # class SourceFile
9
+ # include Validatable
10
+ #
11
+ # def initialize(path, validators = [FilePersistanceValidator.new, FileAccessibilityValidator.new])
12
+ # @path = path
13
+ # @validators = validators
14
+ # end
15
+ #
16
+ # def each_line(&block)
17
+ # validate!({ path: @path })
18
+ # # ...
19
+ # end
20
+ # end
4
21
  module Validatable
22
+ # @param value [Object] value to validate
23
+ # @raise [Errors::<Particular>Error] if value is not valid
5
24
  def validate!(value)
6
25
  (@validators ||= []).each { |validator| validator.validate(value) }
7
26
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleImagesDownloader
4
- VERSION = '1.0.3'
4
+ # gem version
5
+ VERSION = '1.1.1'
5
6
  end
@@ -4,29 +4,52 @@ require 'open-uri'
4
4
  require 'tempfile'
5
5
  require 'forwardable'
6
6
  require 'singleton'
7
- require 'zeitwerk'
8
-
9
- loader = Zeitwerk::Loader.for_gem
10
- loader.setup
7
+ require 'open3'
8
+ require_relative 'simple_images_downloader/version'
9
+ require_relative 'simple_images_downloader/errors'
10
+ require_relative 'simple_images_downloader/configuration'
11
+ require_relative 'simple_images_downloader/validatable'
12
+ require_relative 'simple_images_downloader/validatable/validator'
13
+ require_relative 'simple_images_downloader/validatable/destination_validator'
14
+ require_relative 'simple_images_downloader/validatable/file_accessibility_validator'
15
+ require_relative 'simple_images_downloader/validatable/file_persistance_validator'
16
+ require_relative 'simple_images_downloader/validatable/mime_type_validator'
17
+ require_relative 'simple_images_downloader/stringio_to_tempfile'
18
+ require_relative 'simple_images_downloader/client'
19
+ require_relative 'simple_images_downloader/source_file'
20
+ require_relative 'simple_images_downloader/runner'
21
+ require_relative 'simple_images_downloader/line'
22
+ require_relative 'simple_images_downloader/dispenser'
23
+ require_relative 'simple_images_downloader/strategies/strategy'
24
+ require_relative 'simple_images_downloader/downloader'
25
+ require_relative 'simple_images_downloader/strategies/from_file_strategy'
26
+ require_relative 'simple_images_downloader/strategies/from_url_strategy'
11
27
 
28
+ # SimpleImagesDownloader module
29
+ # It is a main module of the gem
30
+ # It is responsible for providing interface to download images from file or url
31
+ #
32
+ # @example
33
+ # SimpleImagesDownloader.from_file('./urls.txt')
34
+ # SimpleImagesDownloader.from_url('https://example.com/image.jpg')
35
+ #
12
36
  module SimpleImagesDownloader
37
+ # Downloads images from file by taking urls from it. Places images to the destination folder set in configuration
38
+ #
39
+ # @param path [String] path to file with urls
13
40
  def self.from_file(path)
14
- source_file = SourceFile.new(path)
15
-
16
- source_file.each_line do |line|
17
- uri = Line.new(line).uri
18
- Downloader.new(uri).download
19
- rescue Errors::BaseError => e
20
- puts e.message
21
- next
22
- end
41
+ SimpleImagesDownloader::Strategies::FromFileStrategy.new(path).process
23
42
  end
24
43
 
44
+ # Downloads image from url. Places image to the destination folder set in configuration
45
+ #
46
+ # @param url [String] url of image
25
47
  def self.from_url(url)
26
- uri = Line.new(url).uri
27
- Downloader.new(uri).download
48
+ SimpleImagesDownloader::Strategies::FromUrlStrategy.new(url).process
28
49
  end
29
50
 
51
+ # Returns root path of the gem. It is used for testing purposes
52
+ # @return [String] root path of the gem
30
53
  def self.root
31
54
  File.dirname __dir__
32
55
  end
data/rubocop.yml CHANGED
@@ -69,6 +69,9 @@ Layout/LineLength:
69
69
  Metrics/BlockLength:
70
70
  Enabled: false
71
71
 
72
+ Metrics/MethodLength:
73
+ Max: 11
74
+
72
75
  Layout/EndAlignment:
73
76
  EnforcedStyleAlignWith: variable
74
77
  SupportedStylesAlignWith:
@@ -30,8 +30,6 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ['lib']
32
32
 
33
- spec.add_runtime_dependency 'zeitwerk', '~> 2.4.0'
34
-
35
33
  spec.add_development_dependency 'faker', '~> 2.14'
36
34
  spec.add_development_dependency 'pry', '~> 0.13.1'
37
35
  spec.add_development_dependency 'rake', '~> 12.0'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple-images-downloader
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - IlkhamGaysin
@@ -10,20 +10,6 @@ bindir: exe
10
10
  cert_chain: []
11
11
  date: 2023-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: zeitwerk
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 2.4.0
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: 2.4.0
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: faker
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -214,12 +200,15 @@ files:
214
200
  - lib/simple_images_downloader/line.rb
215
201
  - lib/simple_images_downloader/runner.rb
216
202
  - lib/simple_images_downloader/source_file.rb
203
+ - lib/simple_images_downloader/strategies/from_file_strategy.rb
204
+ - lib/simple_images_downloader/strategies/from_url_strategy.rb
205
+ - lib/simple_images_downloader/strategies/strategy.rb
217
206
  - lib/simple_images_downloader/stringio_to_tempfile.rb
218
207
  - lib/simple_images_downloader/validatable.rb
219
208
  - lib/simple_images_downloader/validatable/destination_validator.rb
220
209
  - lib/simple_images_downloader/validatable/file_accessibility_validator.rb
221
210
  - lib/simple_images_downloader/validatable/file_persistance_validator.rb
222
- - lib/simple_images_downloader/validatable/image_path_validator.rb
211
+ - lib/simple_images_downloader/validatable/mime_type_validator.rb
223
212
  - lib/simple_images_downloader/validatable/validator.rb
224
213
  - lib/simple_images_downloader/version.rb
225
214
  - rubocop.yml
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SimpleImagesDownloader
4
- module Validatable
5
- class ImagePathValidator < Validator
6
- VALID_EXTENSIONS = %w[.png .jpg .gif .jpeg].freeze
7
-
8
- def validate(path)
9
- extension = File.extname(path)
10
-
11
- return if VALID_EXTENSIONS.include?(extension)
12
-
13
- raise Errors::MissingImageInPath, path
14
- end
15
- end
16
- end
17
- end