poleica 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +2 -0
  3. data/.travis.yml +15 -0
  4. data/CHANGELOG.md +16 -0
  5. data/Gemfile +19 -0
  6. data/Gemfile.lock +192 -0
  7. data/Guardfile +19 -0
  8. data/LICENSE +20 -0
  9. data/README.md +82 -0
  10. data/Rakefile +7 -0
  11. data/lib/poleica/converters/coercive.rb +68 -0
  12. data/lib/poleica/converters/convertible.rb +60 -0
  13. data/lib/poleica/converters/general.rb +18 -0
  14. data/lib/poleica/converters/graphics_magick.rb +110 -0
  15. data/lib/poleica/converters/libre_office.rb +103 -0
  16. data/lib/poleica/converters/null.rb +17 -0
  17. data/lib/poleica/converters/utils.rb +49 -0
  18. data/lib/poleica/pathable.rb +20 -0
  19. data/lib/poleica/polei.rb +24 -0
  20. data/lib/poleica/types/archive.rb +12 -0
  21. data/lib/poleica/types/document.rb +53 -0
  22. data/lib/poleica/types/image.rb +37 -0
  23. data/lib/poleica/types/null.rb +12 -0
  24. data/lib/poleica/types/pdf.rb +19 -0
  25. data/lib/poleica/types/typeable.rb +50 -0
  26. data/lib/poleica/version.rb +4 -0
  27. data/lib/poleica.rb +27 -0
  28. data/poleica.gemspec +18 -0
  29. data/test/poleica/converters/coercive_test.rb +77 -0
  30. data/test/poleica/converters/convertible_test.rb +44 -0
  31. data/test/poleica/converters/general_test.rb +11 -0
  32. data/test/poleica/converters/graphics_magick_test.rb +102 -0
  33. data/test/poleica/converters/libre_office_test.rb +55 -0
  34. data/test/poleica/converters/null_test.rb +18 -0
  35. data/test/poleica/converters/utils_test.rb +5 -0
  36. data/test/poleica/pathable_test.rb +11 -0
  37. data/test/poleica/polei_test.rb +15 -0
  38. data/test/poleica/types/typeable_test.rb +57 -0
  39. data/test/poleica_test.rb +10 -0
  40. data/test/support/files/1px.gif +0 -0
  41. data/test/support/files/example.doc +0 -0
  42. data/test/support/files/example.mp3 +0 -0
  43. data/test/support/files/example.pdf +0 -0
  44. data/test/support/files/example.png +0 -0
  45. data/test/test_helper.rb +38 -0
  46. metadata +134 -0
@@ -0,0 +1,103 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'fileutils'
3
+
4
+ module Poleica
5
+ module Converters
6
+ # The LibreOffice converter, use the 'soffice' command to convert documents
7
+ class LibreOffice
8
+ include Poleica::Converters::Utils
9
+
10
+ BIN_PATHS = {
11
+ linux: '/usr/bin/soffice',
12
+ osx: '/Applications/LibreOffice.app/Contents/MacOS/soffice'
13
+ }
14
+
15
+ COMPATIBLE_TYPES = [
16
+ Types::Document
17
+ ]
18
+
19
+ attr_reader :polei
20
+
21
+ def initialize(polei)
22
+ @polei = polei
23
+ end
24
+
25
+ def to_pdf(options = {})
26
+ opts_gen = OptionsGenerator.new(polei, options, :pdf)
27
+ cmd = "#{bin_path} #{opts_gen.generate}"
28
+ `#{cmd}`
29
+ expected_file_path = opts_gen[:path] || polei.path_with_md5(:pdf)
30
+ File.exists?(expected_file_path) ? expected_file_path : nil
31
+ ensure
32
+ temp_file_path = opts_gen.temp_path
33
+ File.delete(temp_file_path) if File.exists?(temp_file_path)
34
+ end
35
+
36
+ private
37
+
38
+ # Generate options for the soffice command
39
+ class OptionsGenerator
40
+ attr_reader :options, :format, :polei
41
+
42
+ def initialize(polei, options = {}, format = :pdf)
43
+ defaults = {}
44
+ @options = defaults.merge(options)
45
+ @polei = polei
46
+ @format = format
47
+ end
48
+
49
+ def generate
50
+ "#{default_options} #{format} #{output_options}"
51
+ end
52
+
53
+ def [](key)
54
+ options[key]
55
+ end
56
+
57
+ # Generate a temp path, and create the file this is needed in order to
58
+ # have the right filename, LibreOffice just copy the original filename
59
+ # in the choosen directory, it doesn't accept filename params.
60
+ # @return temp_path [String]
61
+ def temp_path
62
+ @temp_path ||= generate_temp_path
63
+ FileUtils.cp(polei.path, @temp_path) unless File.exists?(@temp_path)
64
+ @temp_path
65
+ end
66
+
67
+ private
68
+
69
+ def generate_temp_path
70
+ if options[:path]
71
+ if File.directory?(options[:path])
72
+ basename = File.basename(polei.path_with_md5)
73
+ return File.join(options[:path], basename)
74
+ end
75
+ extension = File.extname(polei.path)
76
+ return pathable_object.path_for_extension(extension[1..-1])
77
+ else
78
+ return polei.path_with_md5(polei.file_extension)
79
+ end
80
+ end
81
+
82
+ def pathable_object
83
+ pathable_object = Struct.new(:path).new(options[:path])
84
+ pathable_object.extend(Poleica::Pathable)
85
+ end
86
+
87
+ def default_options
88
+ '--headless --invisible --norestore --nolockcheck --convert-to'
89
+ end
90
+
91
+ def output_options
92
+ dir_path = File.dirname(temp_path)
93
+ "--outdir #{Utils.escape(dir_path)} #{Utils.escape(temp_path)}"
94
+ end
95
+
96
+ def pages_options
97
+ @pages_options ||= Array(options[:page]).
98
+ flatten.compact.uniq.sort.to_s
99
+ end
100
+ end # class OptionsGenerator
101
+ end # class LibreOffice
102
+ end # module Converters
103
+ end # module Poleica
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Poleica
3
+ module Converters
4
+ # Null Object Pattern, this is the converter returned if no compatible
5
+ # converters are found
6
+ class Null
7
+ attr_reader :polei
8
+ def initialize(polei)
9
+ @polei = polei
10
+ end
11
+
12
+ def method_missing(method, *args, &block)
13
+ super unless method =~ /^to_/
14
+ end
15
+ end # class Null
16
+ end # module Converters
17
+ end # module Poleica
@@ -0,0 +1,49 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'digest/md5'
3
+ require 'shellwords'
4
+
5
+ module Poleica
6
+ module Converters
7
+ # An Utility module for the converters needs to be include
8
+ module Utils
9
+ HOST_OS ||= (defined?('RbConfig') ? RbConfig : Config)::CONFIG['host_os']
10
+
11
+ def windows?
12
+ !!HOST_OS.match(/mswin|windows|cygwin/i)
13
+ end
14
+
15
+ def osx?
16
+ !!HOST_OS.match(/darwin/i)
17
+ end
18
+
19
+ def linux?
20
+ !!HOST_OS.match(/linux/i)
21
+ end
22
+
23
+ def host_os
24
+ [:windows, :osx, :linux].find { |os| self.send(:"#{os}?") }
25
+ end
26
+
27
+ def bin_path
28
+ bin_path_key = :BIN_PATHS # rubocop:disable SymbolName
29
+ bin_paths = self.class.const_get(bin_path_key)
30
+ path = bin_paths[host_os] || bin_paths[:linux]
31
+ raise "#{self.class} not found @ #{path}" unless File.exists?(path)
32
+ path
33
+ end
34
+
35
+ module_function
36
+
37
+ def extract_extension_and_options(method, args = [])
38
+ extension = method.to_s.split(/^to_(.*)/)[1]
39
+ options = args.last if args.last.is_a?(Hash)
40
+ options ||= {}
41
+ [extension, options]
42
+ end
43
+
44
+ def escape(string)
45
+ Shellwords.shellescape(string)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Poleica
3
+ # Path methods
4
+ module Pathable
5
+ def path_for_extension(extension)
6
+ "#{path_without_extension}.#{extension}"
7
+ end
8
+
9
+ def path_without_extension
10
+ File.join(File.dirname(self.path), File.basename(self.path, '.*'))
11
+ end
12
+
13
+ def path_with_md5(extension = self.extension)
14
+ data = File.read(self.path)
15
+ md5 = Digest::MD5.new
16
+ digest = md5.hexdigest(data)
17
+ "#{path_without_extension}-#{digest}.#{extension}"
18
+ end
19
+ end # class Pathable
20
+ end # module Poleica
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Poleica
3
+ # Strange name for a simple object, it represents a File
4
+ class Polei
5
+ include Poleica::Typeable
6
+ include Poleica::Pathable
7
+ include Poleica::Convertible
8
+
9
+ attr_reader :path, :name
10
+
11
+ def initialize(path)
12
+ @path = path.strip
13
+ raise "No file @ #{path}" unless File.exists?(path)
14
+ end
15
+
16
+ def name
17
+ File.basename(path, ".#{extension}")
18
+ end
19
+
20
+ def extension
21
+ File.extname(path)[1..-1]
22
+ end
23
+ end # class Polei
24
+ end # module Poleica
@@ -0,0 +1,12 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Poleica
3
+ module Types
4
+ # Archive Type
5
+ class Archive
6
+ COMPATIBLE_MIMETYPES = [
7
+ 'application/zip' # .zip or a range of docs like .key, .pptx or .docx
8
+ ]
9
+
10
+ end # class Archive
11
+ end # module Types
12
+ end # module Poleica
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Poleica
3
+ module Types
4
+ # Document Type
5
+ class Document
6
+ COMPATIBLE_MIMETYPES = [
7
+ 'applicationiapplication/vnd.oasis.opendocument.image', # .odi
8
+ 'application/vnd.oasis.opendocument.presentation', # .opd
9
+ 'application/vnd.oasis.opendocument.text-master', # .odm
10
+ 'application/vnd.oasis.opendocument.spreadsheet', # .ods
11
+ 'application/vnd.oasis.opendocument.graphics', # .odg
12
+ 'application/vnd.oasis.opendocument.formula', # .odf
13
+ 'application/vnd.oasis.opendocument.chart', # .odc
14
+ 'application/vnd.oasis.opendocument.text', # .odt
15
+ 'application/vnd.ms-office', # .doc
16
+ 'application/vnd.ms-excel', # .xls
17
+ 'application/vnd.ms-office', # .ppt, .pps
18
+ 'application/msword', # .doc
19
+ 'text/html', # .html, .htm
20
+ 'text/plain', # .txt
21
+ 'text/rtf' # .rft
22
+ ]
23
+
24
+ # Unsupported :( : key, pages
25
+ COMPATIBLE_EXTENSIONS = %w{
26
+ html
27
+ htm
28
+ odt
29
+ doc
30
+ ods
31
+ odp
32
+ odg
33
+ odc
34
+ odf
35
+ odb
36
+ odc
37
+ odm
38
+ docx
39
+ xls
40
+ xlsx
41
+ ppt
42
+ pptx
43
+ pps
44
+ txt
45
+ rft
46
+ }
47
+
48
+ def initialize(file_path)
49
+
50
+ end
51
+ end # class Document
52
+ end # module Types
53
+ end # module Poleica
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Poleica
3
+ module Types
4
+ # Image Type
5
+ class Image
6
+ COMPATIBLE_MIMETYPES = [
7
+ 'image/x-portable-pixmap', # .ppm, .pgm, .pbm, .pnm
8
+ 'image/x-portable-bitmap', # .pbm
9
+ 'image/x-ms-bmp', # .bmp
10
+ 'image/svg+xml', # .svg
11
+ 'image/jpeg', # .jpeg, .jpg
12
+ 'image/tiff', # .tiff, .tif
13
+ 'image/gif', # .gif
14
+ 'image/png' # .png
15
+ ]
16
+
17
+ COMPATIBLE_EXTENSIONS = %w{
18
+ tiff
19
+ jpeg
20
+ ppm
21
+ pgm
22
+ pnm
23
+ pbm
24
+ bmp
25
+ svg
26
+ jpg
27
+ tif
28
+ gif
29
+ png
30
+ }
31
+
32
+ def initialize(file_path)
33
+
34
+ end
35
+ end # class Image
36
+ end # module Types
37
+ end # module Poleica
@@ -0,0 +1,12 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Poleica
3
+ module Types
4
+ # Null Object Pattern Type, this is the type returned if no compatible
5
+ # types are found
6
+ class Null
7
+ def initialize(file_path)
8
+
9
+ end
10
+ end # class Null
11
+ end # module Types
12
+ end # module Poleica
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Poleica
3
+ module Types
4
+ # PDF Type
5
+ class PDF
6
+ COMPATIBLE_MIMETYPES = [
7
+ 'application/pdf' # pdf
8
+ ]
9
+
10
+ COMPATIBLE_EXTENSIONS = %w{
11
+ pdf
12
+ }
13
+
14
+ def initialize(file_path)
15
+
16
+ end
17
+ end # class PDF
18
+ end # module Types
19
+ end # module Poleica
@@ -0,0 +1,50 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Poleica
3
+ # Retrieve the mimetype, the extension and the type of a file, it needs a
4
+ # "path" method
5
+ module Typeable
6
+ def file_extension
7
+ @file_extension ||= extract_extension
8
+ end
9
+
10
+ def file_mimetype
11
+ @file_mimetype ||= extract_mimetype
12
+ end
13
+
14
+ def file_type
15
+ @file_type ||= (detect_type_with_extension ||
16
+ detect_type_with_mimetype ||
17
+ Types::Null).new(path)
18
+ end
19
+
20
+ private
21
+
22
+ MIMETYPE_EXTRACT_REGEX = /([^;:]+)/
23
+
24
+ TYPES = [
25
+ Types::Image,
26
+ Types::Document,
27
+ Types::PDF
28
+ ]
29
+
30
+ def extract_mimetype
31
+ `file -b --mime '#{path}'`.strip[MIMETYPE_EXTRACT_REGEX] || ''
32
+ end
33
+
34
+ def extract_extension
35
+ (::File.extname(path)[1..-1] || '').strip
36
+ end
37
+
38
+ def detect_type_with_extension
39
+ TYPES.find do |type|
40
+ type::COMPATIBLE_EXTENSIONS.include?(file_extension)
41
+ end
42
+ end
43
+
44
+ def detect_type_with_mimetype
45
+ TYPES.find do |type|
46
+ type::COMPATIBLE_MIMETYPES.include?(file_mimetype)
47
+ end
48
+ end
49
+ end # class Typeable
50
+ end # module Poleica
@@ -0,0 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Poleica
3
+ VERSION ||= '0.9.6'
4
+ end # module Poleica
data/lib/poleica.rb ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Poleica
3
+ def self.new(file_or_path)
4
+ file_or_path = file_or_path.path if file_or_path.respond_to?(:path)
5
+ Polei.new(file_or_path)
6
+ end
7
+ end # module Poleica
8
+
9
+ require 'poleica/version'
10
+
11
+ require 'poleica/types/pdf'
12
+ require 'poleica/types/null'
13
+ require 'poleica/types/image'
14
+ require 'poleica/types/document'
15
+ require 'poleica/types/typeable'
16
+
17
+ require 'poleica/converters/utils'
18
+ require 'poleica/converters/general'
19
+ require 'poleica/converters/null'
20
+ require 'poleica/converters/coercive'
21
+ require 'poleica/converters/graphics_magick'
22
+ require 'poleica/converters/libre_office'
23
+ require 'poleica/converters/convertible'
24
+
25
+ require 'poleica/pathable'
26
+
27
+ require 'poleica/polei'
data/poleica.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/poleica/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'poleica'
6
+ s.version = Poleica::VERSION
7
+ s.license = 'MIT'
8
+ s.summary = 'A general converter and thumbnail creator'
9
+ s.description = 'Poleica can convert docs and images to PDF and PNG, it can be extended to handle a lot of converters'
10
+ s.authors = ['Antoine Lyset']
11
+ s.email = 'antoinelyset@gmail.com'
12
+ s.homepage = 'https://github.com/antoinelyset/poleica'
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.add_development_dependency 'bundler', '~> 1.3'
17
+ s.add_development_dependency 'rake'
18
+ end
@@ -0,0 +1,77 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'test_helper'
3
+ # Test the Coercive Converter Module
4
+
5
+ class CoerciveTest < Minitest::Test
6
+ DOC_PATH = "#{Support.support_path}/files/example.doc"
7
+
8
+ def test_find_next_converter_by_method
9
+ conv_hash = coercive_conv.send(:find_next_converter_by_method, :to_png)
10
+ assert_equal({ to_pdf: Poleica::Converters::GraphicsMagick }, conv_hash)
11
+ end
12
+
13
+ def test_next_converters_by_method
14
+ next_convs = coercive_conv.send(:next_converters_by_method)
15
+ expected_convs = [{ to_pdf: Poleica::Converters::GraphicsMagick }]
16
+ assert_equal(expected_convs, next_convs)
17
+ end
18
+
19
+ def test_try_convert_intermediary_file_creation
20
+ coercive_conv.send(:coerce, :to_png, {})
21
+ assert(File.exists?(expected_pdf_path))
22
+ clean_png_file
23
+ clean_pdf_file
24
+ end
25
+
26
+ def test_try_convert_file_creation
27
+ coercive_conv.send(:try_convert, :to_png)
28
+ assert(File.exists?(find_png_path))
29
+ clean_png_file(find_png_path)
30
+ clean_pdf_file
31
+ end
32
+
33
+ def test_coercive_conversion
34
+ polei = Poleica.new(DOC_PATH)
35
+ returned_path = polei.to_png
36
+ assert(File.exists?(returned_path))
37
+ clean_png_file(find_png_path)
38
+ end
39
+
40
+ def test_coercive_null_return
41
+ polei = Poleica.new("#{Support.support_path}/files/example.mp3")
42
+ returned_path = polei.to_png
43
+ assert_equal(nil, returned_path)
44
+ end
45
+
46
+ private
47
+
48
+ def clean_pdf_file
49
+ File.exists?(expected_pdf_path) && File.delete(expected_pdf_path)
50
+ end
51
+
52
+ def clean_png_file(png_path = expected_png_path)
53
+ File.exists?(png_path) && File.delete(png_path)
54
+ end
55
+
56
+ def expected_pdf_path
57
+ Support.expected_converted_path(DOC_PATH, :pdf)
58
+ end
59
+
60
+ def expected_png_path
61
+ Support.expected_converted_path(expected_pdf_path, :png)
62
+ end
63
+
64
+ # Find a the path of the converted png. It should be used when we
65
+ # don't have the intermediate file since we need to calculate its
66
+ # md5
67
+ def find_png_path
68
+ files = Dir["#{Support.support_path}/files/*"]
69
+ start_path = Support.path_without_extension(expected_pdf_path)
70
+ files.grep(/#{start_path}-\w+\.png$/).first
71
+ end
72
+
73
+ def coercive_conv
74
+ doc_polei = Poleica::Polei.new("#{DOC_PATH}")
75
+ Poleica::Converters::Coercive.new(doc_polei)
76
+ end
77
+ end # class CoerciveTest
@@ -0,0 +1,44 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'test_helper'
3
+ # Test the Convertible Module
4
+ class ConvertibleTest < Minitest::Test
5
+ def test_that_it_extracts_converters
6
+ mp3_path = "#{Support.support_path}/files/example.mp3"
7
+ mp3_polei = Poleica::Polei.new(mp3_path)
8
+ # FIXME
9
+ expected_converters = [Poleica::Converters::General]
10
+ assert_equal(expected_converters, mp3_polei.send(:compatible_converters))
11
+ end
12
+
13
+ def test_that_it_extracts_converters_2
14
+ png_path = "#{Support.support_path}/files/example.png"
15
+ png_polei = Poleica::Polei.new(png_path)
16
+ expected_converters = [
17
+ Poleica::Converters::GraphicsMagick,
18
+ Poleica::Converters::General
19
+ ]
20
+ # FIXME
21
+ assert_equal(expected_converters, png_polei.send(:compatible_converters))
22
+ end
23
+
24
+ def test_convert_to_extension
25
+ png_path = "#{Support.support_path}/files/example.png"
26
+ png_polei = Poleica::Polei.new(png_path)
27
+ converter = png_polei.send(:converter_to_extension, :png)
28
+ assert_equal(Poleica::Converters::GraphicsMagick, converter)
29
+ end
30
+
31
+ def test_default_coercive_converter
32
+ png_path = "#{Support.support_path}/files/example.png"
33
+ png_polei = Poleica::Polei.new(png_path)
34
+ converter = png_polei.send(:converter_to_extension, :mp3)
35
+ assert_equal(Poleica::Converters::Coercive, converter)
36
+ end
37
+
38
+ def test_method_missing_raises_exception
39
+ method_name = :random
40
+ png_path = "#{Support.support_path}/files/example.png"
41
+ png_polei = Poleica::Polei.new(png_path)
42
+ assert_raises(NoMethodError) { png_polei.send(method_name) }
43
+ end
44
+ end # class ConvertibleTest
@@ -0,0 +1,11 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'test_helper'
3
+ # Test the General Converter Module
4
+ class GeneralTest < Minitest::Test
5
+ def test_to_bin
6
+ gif_path = "#{Support.support_path}/files/1px.gif"
7
+ gif_data = File.binread(gif_path)
8
+ gif_polei = Poleica::Polei.new(gif_path)
9
+ assert_equal(gif_data, gif_polei.to_bin)
10
+ end
11
+ end # class GeneralTest