content_type 1.1.0
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.
- data/Rakefile +38 -0
- data/ext/content_type.c +117 -0
- data/ext/extconf.rb +8 -0
- data/spec/content_type_spec.rb +83 -0
- metadata +64 -0
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
require 'vendor/gems/environment'
|
6
|
+
|
7
|
+
task :default => [:clean, :make, :spec]
|
8
|
+
|
9
|
+
task :make do
|
10
|
+
Dir.chdir 'ext' do
|
11
|
+
puts `ruby extconf.rb`
|
12
|
+
puts `make`
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
17
|
+
t.spec_opts = %w(-fs -c)
|
18
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
19
|
+
end
|
20
|
+
|
21
|
+
task :clean do
|
22
|
+
`rm -r *.o *.so *.bundle conftest.dSYM 2>/dev/null`
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'jeweler'
|
26
|
+
Jeweler::Tasks.new do |s|
|
27
|
+
s.name = 'content_type'
|
28
|
+
s.summary = 'libmagic bindings to quickly determine content type of files'
|
29
|
+
s.email = 'dsturnbull@me.com'
|
30
|
+
s.homepage = 'http://github.com/dsturnbull/content_type'
|
31
|
+
s.description =<<-eod
|
32
|
+
Provides ContentType#content_type, File#content_type and
|
33
|
+
File::content_type methods to determine mime type
|
34
|
+
eod
|
35
|
+
s.executables = []
|
36
|
+
s.authors = ['David Turnbull']
|
37
|
+
s.files = ['Rakefile', 'ext/content_type.c', 'ext/extconf.rb']
|
38
|
+
end
|
data/ext/content_type.c
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
#include <magic.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
#include <sys/stat.h>
|
5
|
+
|
6
|
+
#define MAGIC_OPTIONS MAGIC_SYMLINK | MAGIC_MIME_TYPE | MAGIC_PRESERVE_ATIME
|
7
|
+
|
8
|
+
VALUE content_type = Qnil;
|
9
|
+
VALUE content_type_initialize(VALUE self, VALUE path);
|
10
|
+
VALUE content_type_content_type(VALUE self);
|
11
|
+
|
12
|
+
VALUE file_content_type_wrap(VALUE self, VALUE path);
|
13
|
+
VALUE file_content_type(VALUE self);
|
14
|
+
VALUE file_singleton_content_type(VALUE self, VALUE path);
|
15
|
+
|
16
|
+
void magic_fail(const char *error);
|
17
|
+
|
18
|
+
void
|
19
|
+
Init_content_type()
|
20
|
+
{
|
21
|
+
// class definition
|
22
|
+
content_type = rb_define_class("ContentType", rb_cObject);
|
23
|
+
|
24
|
+
// instance methods
|
25
|
+
rb_define_method(content_type, "initialize", content_type_initialize, 1);
|
26
|
+
rb_define_method(content_type, "content_type", content_type_content_type, 0);
|
27
|
+
|
28
|
+
// instance attributes
|
29
|
+
rb_define_attr(content_type, "filepath", 1, 0);
|
30
|
+
rb_define_attr(content_type, "processed", 1, 0);
|
31
|
+
|
32
|
+
// hax on File
|
33
|
+
rb_define_method(rb_cFile,
|
34
|
+
"content_type", file_content_type, 0);
|
35
|
+
rb_define_singleton_method(rb_cFile,
|
36
|
+
"content_type", file_singleton_content_type, 1);
|
37
|
+
}
|
38
|
+
|
39
|
+
VALUE
|
40
|
+
content_type_initialize(VALUE self, VALUE path)
|
41
|
+
{
|
42
|
+
struct stat st;
|
43
|
+
|
44
|
+
SafeStringValue(path);
|
45
|
+
|
46
|
+
if ((stat(RSTRING_PTR(path), &st)) != 0)
|
47
|
+
rb_raise(rb_const_get(rb_cObject, rb_intern("ArgumentError")),
|
48
|
+
"invalid file");
|
49
|
+
|
50
|
+
rb_iv_set(self, "@content_type", rb_str_new("", 0));
|
51
|
+
rb_iv_set(self, "@filepath", path);
|
52
|
+
rb_iv_set(self, "@processed", Qfalse);
|
53
|
+
|
54
|
+
return self;
|
55
|
+
}
|
56
|
+
|
57
|
+
VALUE
|
58
|
+
content_type_content_type(VALUE self)
|
59
|
+
{
|
60
|
+
VALUE ct;
|
61
|
+
struct magic_set *mh;
|
62
|
+
const char *mime;
|
63
|
+
|
64
|
+
if (rb_iv_get(self, "@processed"))
|
65
|
+
return rb_iv_get(self, "@content_type");
|
66
|
+
|
67
|
+
if (!(mh = magic_open(MAGIC_OPTIONS)))
|
68
|
+
magic_fail("open");
|
69
|
+
|
70
|
+
if ((magic_load(mh, NULL)) != 0)
|
71
|
+
magic_fail("load");
|
72
|
+
|
73
|
+
if (!(mime = magic_file(mh, RSTRING_PTR(rb_iv_get(self, "@filepath")))))
|
74
|
+
magic_fail("file");
|
75
|
+
|
76
|
+
ct = rb_str_new(mime, strlen(mime));
|
77
|
+
rb_iv_set(self, "@content_type", ct);
|
78
|
+
rb_iv_set(self, "@processed", Qtrue);
|
79
|
+
magic_close(mh);
|
80
|
+
|
81
|
+
return ct;
|
82
|
+
}
|
83
|
+
|
84
|
+
VALUE
|
85
|
+
file_content_type_wrap(VALUE self, VALUE path)
|
86
|
+
{
|
87
|
+
VALUE ct, mime, args[1];
|
88
|
+
|
89
|
+
SafeStringValue(path);
|
90
|
+
args[0] = path;
|
91
|
+
ct = rb_class_new_instance(1, args, content_type);
|
92
|
+
|
93
|
+
return rb_funcall(ct, rb_intern("content_type"), 0);
|
94
|
+
}
|
95
|
+
|
96
|
+
VALUE
|
97
|
+
file_content_type(VALUE self)
|
98
|
+
{
|
99
|
+
return file_content_type_wrap(self, rb_funcall(self, rb_intern("path"), 0));
|
100
|
+
}
|
101
|
+
|
102
|
+
VALUE
|
103
|
+
file_singleton_content_type(VALUE self, VALUE path)
|
104
|
+
{
|
105
|
+
return file_content_type_wrap(self, path);
|
106
|
+
}
|
107
|
+
|
108
|
+
void
|
109
|
+
magic_fail(const char *error)
|
110
|
+
{
|
111
|
+
const char *format = "magic_%s() error";
|
112
|
+
char *error_message;
|
113
|
+
|
114
|
+
error_message = malloc(sizeof(char *) * (strlen(format) + strlen(error)));
|
115
|
+
sprintf((char *)error_message, format, error);
|
116
|
+
rb_raise(rb_const_get(rb_cObject, rb_intern("RuntimeError")), error_message);
|
117
|
+
}
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'should_be_faster'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'ext/content_type'
|
5
|
+
|
6
|
+
describe ContentType do
|
7
|
+
before do
|
8
|
+
@img = 'spec/fixtures/grindewald.jpg'
|
9
|
+
@pdf = 'spec/fixtures/pdftest.pdf'
|
10
|
+
@lzm = 'spec/fixtures/compressed.jpg.lz'
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'initialising' do
|
14
|
+
it 'should take a file path' do
|
15
|
+
path = @img
|
16
|
+
ct = ContentType.new(path)
|
17
|
+
ct.filepath.should == path
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should check that the file exists' do
|
21
|
+
lambda {
|
22
|
+
ct = ContentType.new('poopstain')
|
23
|
+
}.should raise_error ArgumentError
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should memoize the content type' do
|
27
|
+
ct = ContentType.new(@img)
|
28
|
+
lambda {
|
29
|
+
ct.content_type.should == 'image/jpeg'
|
30
|
+
}.should change(ct, :processed).to(true)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'detecting mime type' do
|
35
|
+
it 'should detect images' do
|
36
|
+
ct = ContentType.new(@img)
|
37
|
+
ct.content_type.should == 'image/jpeg'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should detect pdfs' do
|
41
|
+
ct = ContentType.new(@pdf)
|
42
|
+
ct.content_type.should == 'application/pdf'
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should detect lzma files' do
|
46
|
+
ct = ContentType.new(@lzm)
|
47
|
+
ct.content_type.should == 'application/x-lzip'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'comparing with shelling out' do
|
52
|
+
it 'should be much faster' do
|
53
|
+
shell = lambda { `file -i #{@img}` }
|
54
|
+
ext = lambda { ContentType.new(@img).content_type }
|
55
|
+
ext.should be_at_least(5).times.faster_than(shell)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should slower to reinstantiate' do
|
59
|
+
@ct = ContentType.new(@img)
|
60
|
+
memo = lambda { @ct.content_type }
|
61
|
+
kgo = lambda { ContentType.new(@img).content_type }
|
62
|
+
kgo.should be_at_least(5).times.slower_than(memo, :iterations => 1000)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when using the file class methods' do
|
67
|
+
it 'should have File.content_type(path)' do
|
68
|
+
File.content_type(@img).should == 'image/jpeg'
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should have File#content_type' do
|
72
|
+
File.open(@img).content_type.should == 'image/jpeg'
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should make it into tempfile' do
|
76
|
+
t = Tempfile.new('hi')
|
77
|
+
t.content_type.should == 'application/x-empty'
|
78
|
+
t << File.read(@img)
|
79
|
+
t.content_type.should == 'image/jpeg'
|
80
|
+
File.unlink(t.path)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: content_type
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 1.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- David Turnbull
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-03-16 00:00:00 +11:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: " Provides ContentType#content_type, File#content_type and\n File::content_type methods to determine mime type\n"
|
22
|
+
email: dsturnbull@me.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions:
|
26
|
+
- ext/extconf.rb
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- Rakefile
|
31
|
+
- ext/content_type.c
|
32
|
+
- ext/extconf.rb
|
33
|
+
has_rdoc: true
|
34
|
+
homepage: http://github.com/dsturnbull/content_type
|
35
|
+
licenses: []
|
36
|
+
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options:
|
39
|
+
- --charset=UTF-8
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.3.6
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: libmagic bindings to quickly determine content type of files
|
63
|
+
test_files:
|
64
|
+
- spec/content_type_spec.rb
|