dexter 0.0.2

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,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ tmp/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format documentation
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create use 1.9.2@dexter
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dexter.gemspec
4
+ gemspec
@@ -0,0 +1,57 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dexter (0.0.1)
5
+ trollop
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ aruba (0.2.3)
11
+ background_process
12
+ cucumber (~> 0.9.0)
13
+ background_process (1.2)
14
+ builder (2.1.2)
15
+ cucumber (0.9.3)
16
+ builder (~> 2.1.2)
17
+ diff-lcs (~> 1.1.2)
18
+ gherkin (~> 2.2.9)
19
+ json (~> 1.4.6)
20
+ term-ansicolor (~> 1.0.5)
21
+ diff-lcs (1.1.2)
22
+ gemcutter (0.6.1)
23
+ gherkin (2.2.9)
24
+ json (~> 1.4.6)
25
+ term-ansicolor (~> 1.0.5)
26
+ git (1.2.5)
27
+ jeweler (1.4.0)
28
+ gemcutter (>= 0.1.0)
29
+ git (>= 1.2.5)
30
+ rubyforge (>= 2.0.0)
31
+ json (1.4.6)
32
+ json_pure (1.4.6)
33
+ rspec (2.0.1)
34
+ rspec-core (~> 2.0.1)
35
+ rspec-expectations (~> 2.0.1)
36
+ rspec-mocks (~> 2.0.1)
37
+ rspec-core (2.0.1)
38
+ rspec-expectations (2.0.1)
39
+ diff-lcs (>= 1.1.2)
40
+ rspec-mocks (2.0.1)
41
+ rspec-core (~> 2.0.1)
42
+ rspec-expectations (~> 2.0.1)
43
+ rubyforge (2.0.4)
44
+ json_pure (>= 1.1.7)
45
+ term-ansicolor (1.0.5)
46
+ trollop (1.16.2)
47
+
48
+ PLATFORMS
49
+ ruby
50
+
51
+ DEPENDENCIES
52
+ aruba
53
+ cucumber
54
+ dexter!
55
+ jeweler
56
+ rspec
57
+ trollop
@@ -0,0 +1,13 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ require 'cucumber/rake/task'
6
+
7
+ desc "Run the specs under spec"
8
+ RSpec::Core::RakeTask.new do |t|
9
+ end
10
+
11
+ Cucumber::Rake::Task.new(:cucumber)
12
+
13
+ task :default => :spec
@@ -0,0 +1,38 @@
1
+ # dexter
2
+
3
+ Dexter helps you organize your tv series with well, you know... Dexterity}
4
+
5
+ It automatically detects all video files that seem like a series and moves them
6
+ into an appropriate path.
7
+
8
+ Currently in hard alpha but functional.
9
+
10
+ ## Usage
11
+
12
+ $ dexter
13
+
14
+ It organizes all the files within the current directory and its subdirectories and places them into appropriate folders
15
+
16
+ $ dexter --input <input> --output <output>
17
+
18
+ Checks all the directories and subdirectories in <input> and creates a folder structure moving the files in <output>
19
+
20
+ $ dexter --format ":name/S:season/:name S:seasonE:episode.:extension"
21
+
22
+ Moves the files using the format specified.
23
+
24
+ $ dexter --verbose false
25
+
26
+ Removes all output
27
+
28
+ ## Running the tests - development only
29
+
30
+ Dexter is tested with RSpec and cucumber.
31
+
32
+ $ bundle exec rspec spec
33
+
34
+ Runs the functional tests
35
+
36
+ $ bundle exec cucumber features
37
+
38
+ Runs the integration tests
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'trollop'
4
+ require 'dexter'
5
+
6
+ opts = Trollop::options do
7
+ opt :input, "Input directory", :default => "."
8
+ opt :output, "Output directory", :default => "."
9
+ opt :format, "Output directory format", :default => Dexter::Matchers::Video.output_format
10
+ opt :verbose, "Be verbose about what dexter is doing", :default => true
11
+ end
12
+
13
+ Dexter::Matchers::Video.output_format = opts[:format]
14
+ Dexter.verbose = opts[:verbose]
15
+ Dexter.organize!(opts[:input],opts[:output])
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "dexter/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dexter"
7
+ s.version = Dexter::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = "Josep Jaume Rey Peroy"
10
+ s.email = "josepjaume@gmail.com"
11
+ s.homepage = "http://github.com/josepjaume/dexter"
12
+ s.summary = %q{Dexter helps you organize your tv series with well, you know... Dexterity}
13
+ s.description = %q{Dexter helps you organize your tv series with well, you know... Dexterity}
14
+
15
+ s.rubyforge_project = "dexter"
16
+
17
+ s.add_development_dependency 'rspec'
18
+ s.add_development_dependency 'cucumber'
19
+ s.add_development_dependency 'aruba'
20
+ s.add_development_dependency 'jeweler'
21
+
22
+ s.add_runtime_dependency 'trollop'
23
+
24
+ s.files = `git ls-files`.split("\n")
25
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
26
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
27
+ s.default_executable = 'dexter'
28
+ s.require_paths = ["lib"]
29
+ end
@@ -0,0 +1,54 @@
1
+ Feature: Dexter organises series
2
+
3
+ In order to order my TV series in a clean and neat manner
4
+ As a great series freak
5
+ Dexter should be able to get its name and season & episode numbers and move them to its appropiate paths
6
+
7
+ @announce
8
+ Scenario: Dexter organizes only one file
9
+ Given a directory named "Downloads"
10
+ And a directory named "Downloads/mess"
11
+ And a directory named "Video"
12
+ And an empty file named "Downloads/dexter s01e09.avi"
13
+ When I run "dexter --input \"Downloads/dexter s01e09.avi\" --output Video"
14
+ Then the following directories should exist:
15
+ | Video/Dexter/S01 |
16
+
17
+ @announce
18
+ Scenario: Dexter organises using a custom format
19
+ Given a directory named "Downloads"
20
+ And a directory named "Downloads/mess"
21
+ And a directory named "Video"
22
+ And an empty file named "Downloads/dexter s01e09.avi"
23
+ When I run "dexter --input \"Downloads/dexter s01e09.avi\" --output Video --format \":name (Season :season)/:name S:seasonE:episode.:extension\""
24
+ Then the following directories should exist:
25
+ | Video/Dexter (Season 01) |
26
+
27
+ @announce
28
+ Scenario: Dexter isn't verbose
29
+ Given a directory named "Downloads"
30
+ And a directory named "Downloads/mess"
31
+ And a directory named "Video"
32
+ And an empty file named "Downloads/dexter s01e09.avi"
33
+ When I run "dexter --input \"Downloads/dexter s01e09.avi\" --output Video --verbose false"
34
+ Then the following directories should exist:
35
+ | Video/Dexter/S01 |
36
+ And the output should contain exactly ""
37
+
38
+ @announce
39
+ Scenario: Dexter organizes a whole directory
40
+ Given a directory named "Downloads"
41
+ And a directory named "Downloads/mess"
42
+ And a directory named "Video"
43
+ And an empty file named "Downloads/dexter s01e09.avi"
44
+ And an empty file named "Downloads/Fringe.1x03.HDTV.avi"
45
+ And an empty file named "Downloads/The.big.bang.theory.s01e09.avi"
46
+ And an empty file named "Downloads/mess/MY NAME IS EARL - s08e04 HDTV.avi"
47
+ And an empty file named "Downloads/Treme.S01E04.HDTV.XviD-NoTV.avi"
48
+ When I run "dexter --input Downloads --output Video"
49
+ Then the following directories should exist:
50
+ | Video/Dexter/S01 |
51
+ | Video/The Big Bang Theory/S01 |
52
+ | Video/Fringe/S01 |
53
+ | Video/My Name Is Earl/S08 |
54
+ | Video/Treme/S01 |
@@ -0,0 +1 @@
1
+ require 'aruba'
@@ -0,0 +1,140 @@
1
+ require 'fileutils'
2
+
3
+ module Dexter
4
+
5
+ def self.verbose; @verbose; end
6
+ def self.verbose=(level);@verbose=level;end
7
+
8
+ def self.organize!(input_path, output_path)
9
+
10
+ if File.file?(input_path)
11
+ files = self.load_files([input_path])
12
+ else
13
+ files = self.load_from_directory(input_path)
14
+ end
15
+
16
+ files.each do |file|
17
+ output_file = file.organize!(output_path)
18
+ puts "Organizing: \"#{file.filename}\" ~> \"#{output_file}\"" if Dexter.verbose
19
+ end
20
+
21
+ end
22
+
23
+ def self.load_from_directory(path)
24
+ self.load_files self.list_all_files_within_directory(path)
25
+ end
26
+
27
+ def self.load_files(files)
28
+ result = []
29
+ files.each do |file|
30
+ matchers.each do |matcher|
31
+ result << matcher.new(file) if matcher.allowed?(file)
32
+ end
33
+ end
34
+ return result.compact
35
+ end
36
+
37
+ def self.matchers
38
+ [Matchers::Video]
39
+ end
40
+
41
+ def self.list_all_files_within_directory(path)
42
+ expression = "#{path}/**/*.{#{Dexter.matchers.collect{|m| m::EXTENSIONS}.flatten.join(',')}}"
43
+ Dir.glob(expression)
44
+ end
45
+
46
+
47
+ module Matchers
48
+ class AbstractMatcher
49
+
50
+ EXTENSIONS = []
51
+
52
+ def self.allowed?(filename)
53
+ self::EXTENSIONS.include? File.extname(filename).gsub(/^\./,"")
54
+ end
55
+
56
+ def initialize(filename)
57
+ raise "Not an allowed format!" unless self.class.allowed?(filename)
58
+ @filename = filename
59
+ end
60
+
61
+ def organize!
62
+ raise "It's up to the subclass to implement this"
63
+ end
64
+
65
+ def extension
66
+ File.extname(@filename).gsub(/^\./,"")
67
+ end
68
+
69
+ def filename
70
+ @filename
71
+ end
72
+ end
73
+
74
+ class Video < AbstractMatcher
75
+
76
+ EXTENSIONS = ['avi', 'mkv']
77
+
78
+ def self.output_format
79
+ @output ||= ':name/S:season/:name S:seasonE:episode.:extension'
80
+ end
81
+
82
+ def self.allowed?(filename)
83
+ if !super(filename)
84
+ return false
85
+ end
86
+ filename.downcase =~ /([0-9A-z\s\.]+)\s?\.?-?\s?\.?(s[0-9]+e[0-9]+|[0-9]+x[0-9]+).*/
87
+ return !!$1
88
+ end
89
+
90
+ def self.output_format=(options)
91
+ @output = options
92
+ end
93
+
94
+ def name
95
+ filename.downcase =~ /([0-9A-z\s\.]+)\s?\.?-?\s?\.?(s[0-9]+e[0-9]+|[0-9]+x[0-9]+).*/
96
+ return nil if $1.nil?
97
+ return $1.gsub('.', ' ') \
98
+ .split(' ') \
99
+ .collect(&:capitalize) \
100
+ .join(' ') \
101
+ .strip
102
+ end
103
+
104
+ def season
105
+ filename.downcase =~ /[\s\.]?s([0-9]{2})e[0-9]{2}/
106
+ filename.downcase =~ /[\s\.]?([0-9]+)x[0-9]+/ if $1.nil?
107
+ return nil if $1.nil?
108
+ $1.to_i
109
+ end
110
+
111
+ def episode
112
+ filename.downcase =~ /[\s\.]?s[0-9]{2}e([0-9]{2})/
113
+ filename.downcase =~ /[\s\.]?[0-9]+x([0-9]+)/ if $1.nil?
114
+ return nil if $1.nil?
115
+ $1.to_i
116
+ end
117
+
118
+ def output(path)
119
+ options = {
120
+ :season => season.to_s.rjust(2,'0'),
121
+ :episode => episode.to_s.rjust(2,'0'),
122
+ :name => name,
123
+ :path => path,
124
+ :extension => extension
125
+ }
126
+ options.inject(':path/' + self.class.output_format){ |output, object|
127
+ output = (output.gsub(":#{object[0]}", object[1]) || output)
128
+ }
129
+ end
130
+
131
+ def organize!(path)
132
+ output_path = File.dirname(output(path))
133
+ FileUtils.mkdir_p(output_path)
134
+ FileUtils.mv(@filename, output(path))
135
+ return output(path)
136
+ end
137
+
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,3 @@
1
+ module Dexter
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,131 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'dexter'
3
+
4
+ describe Dexter do
5
+ describe 'self.load_files' do
6
+ it "creates several instances of Video from an array" do
7
+ list = [
8
+ "dexter s01e02 HDTV.avi",
9
+ "the.big.bang.theory.1x12.mkv",
10
+ "the.big.bang.theory.1x12.sub",
11
+ "the.big.bang.theory.1x12.srt"
12
+ ]
13
+ Dexter::Matchers::Video.should_receive(:new).exactly(2).times
14
+ subject.load_files(list)
15
+ end
16
+ end
17
+
18
+ describe 'self.organize!' do
19
+ it "organizes all the files within a directory and subdirectories" do
20
+ videos = (1..3).collect{Dexter::Matchers::Video.new('dexter s01e01.avi')}
21
+ videos.each { |video|
22
+ video.should_receive(:organize!).exactly(1).times
23
+ }
24
+ Dexter.should_receive(:load_from_directory)\
25
+ .and_return(videos)
26
+
27
+ File.should_receive(:file?).times.and_return(false)
28
+
29
+ Dexter.organize!('.', '.')
30
+ end
31
+ end
32
+
33
+ describe 'self.load_from_directory' do
34
+ it "creates an instance of the corresponding type from every file" do
35
+ Dexter.should_receive(:list_all_files_within_directory)\
36
+ .and_return(["../family guy s04e08.avi","../fringe 1x18.mkv"])
37
+ Dexter.load_from_directory('.').should have(2).videos
38
+ end
39
+ end
40
+
41
+ describe 'self.list_all_files_within_directory' do
42
+ it "gets a list of all files in a directory recursively" do
43
+ Dir.should_receive(:glob).and_return(["../dexter s01e02 HDTV.avi", "../modern family 1x02.mkv"])
44
+ Dexter.list_all_files_within_directory('/home/josepjaume').should == ["../dexter s01e02 HDTV.avi", "../modern family 1x02.mkv"]
45
+ end
46
+
47
+ end
48
+
49
+ describe Dexter::Matchers do
50
+ describe Dexter::Matchers::Video do
51
+ subject{Dexter::Matchers::Video}
52
+ describe 'self.allowed?' do
53
+ it "returns true if the file is allowed" do
54
+ subject.allowed?('dexter s01e01.avi').should be_true
55
+ end
56
+ end
57
+
58
+ describe 'extension' do
59
+ it "sorts the extension of a file out" do
60
+ video = subject.new('dexter s01e01.avi')
61
+ video.extension.should == 'avi'
62
+ end
63
+ end
64
+
65
+ describe 'name' do
66
+ it "returns the name of the tv series given a particular filename" do
67
+ examples = {
68
+ 'the big bang theory - s01e09.avi' => 'The Big Bang Theory',
69
+ 'DEXTER S01E09.avi' => 'Dexter',
70
+ 'Family.Guy.S10E08.avi' => 'Family Guy'
71
+ }
72
+ examples.each do |key,value|
73
+ video = subject.new('dexter s01e01.avi')
74
+ video.stub(:filename).and_return(key)
75
+ video.name.should == value
76
+ end
77
+ end
78
+ end
79
+ describe 'season' do
80
+ it "returns the season when given a particular filename" do
81
+ examples = {
82
+ 'the big bang theory - s01e09.avi' => 1,
83
+ 'DEXTER S05E09.avi' => 5,
84
+ 'Family.Guy.S10E08.avi' => 10,
85
+ 'The Show of cleveland - 1x2 .mkv' => 1
86
+ }
87
+ examples.each do |key,value|
88
+ video = subject.new('dexter s01e01.avi')
89
+ video.stub(:filename).and_return(key)
90
+ video.season.should == value
91
+ end
92
+ end
93
+ end
94
+ describe 'episode' do
95
+ it "returns the episode when given a particular filename" do
96
+ examples = {
97
+ 'the big bang theory - s01e09.avi' => 9,
98
+ 'DEXTER S05E27.avi' => 27,
99
+ 'Family.Guy.S10E08.avi' => 8,
100
+ 'The Show of cleveland - 1x2 .mkv' => 2
101
+ }
102
+ examples.each do |key,value|
103
+ video = subject.new('dexter s01e01.avi')
104
+ video.stub(:filename).and_return(key)
105
+ video.episode.should == value
106
+ end
107
+ end
108
+ end
109
+ describe "output" do
110
+ it "should return the output path of the video" do
111
+ video = Dexter::Matchers::Video.new('dexter s01e01.avi')
112
+ video.should_receive(:season).and_return(1)
113
+ video.should_receive(:episode).and_return(2)
114
+ video.should_receive(:name).and_return("Modern Family")
115
+ video.should_receive(:extension).and_return('avi')
116
+ video.output(".").should == "./Modern Family/S01/Modern Family S01E02.avi"
117
+ end
118
+ end
119
+ describe :organize! do
120
+ it "should move the video to its corresponding path" do
121
+ video = Dexter::Matchers::Video.new('dexter s01e01.avi')
122
+ video.stub(:output).and_return('./Modern Family/S01/Modern Family S01E02.avi')
123
+ video.stub(:filename).and_return('dexter s01e01.avi')
124
+ FileUtils.should_receive(:mkdir_p).with('./Modern Family/S01')
125
+ FileUtils.should_receive(:mv).with('dexter s01e01.avi','./Modern Family/S01/Modern Family S01E02.avi')
126
+ video.organize!('./')
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,6 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'rubygems'
5
+ require 'rspec'
6
+ require 'rspec/autorun'
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dexter
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 2
9
+ version: 0.0.2
10
+ platform: ruby
11
+ authors:
12
+ - Josep Jaume Rey Peroy
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-10-29 00:00:00 +02:00
18
+ default_executable: dexter
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: cucumber
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :development
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: aruba
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :development
58
+ version_requirements: *id003
59
+ - !ruby/object:Gem::Dependency
60
+ name: jeweler
61
+ prerelease: false
62
+ requirement: &id004 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ type: :development
71
+ version_requirements: *id004
72
+ - !ruby/object:Gem::Dependency
73
+ name: trollop
74
+ prerelease: false
75
+ requirement: &id005 !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ type: :runtime
84
+ version_requirements: *id005
85
+ description: Dexter helps you organize your tv series with well, you know... Dexterity
86
+ email: josepjaume@gmail.com
87
+ executables:
88
+ - dexter
89
+ extensions: []
90
+
91
+ extra_rdoc_files: []
92
+
93
+ files:
94
+ - .gitignore
95
+ - .rspec
96
+ - .rvmrc
97
+ - Gemfile
98
+ - Gemfile.lock
99
+ - Rakefile
100
+ - Readme.md
101
+ - bin/dexter
102
+ - dexter.gemspec
103
+ - features/dexter_organises_series.feature
104
+ - features/support/env.rb
105
+ - lib/dexter.rb
106
+ - lib/dexter/version.rb
107
+ - spec/dexter_spec.rb
108
+ - spec/spec_helper.rb
109
+ has_rdoc: true
110
+ homepage: http://github.com/josepjaume/dexter
111
+ licenses: []
112
+
113
+ post_install_message:
114
+ rdoc_options: []
115
+
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ segments:
124
+ - 0
125
+ version: "0"
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ segments:
132
+ - 0
133
+ version: "0"
134
+ requirements: []
135
+
136
+ rubyforge_project: dexter
137
+ rubygems_version: 1.3.7
138
+ signing_key:
139
+ specification_version: 3
140
+ summary: Dexter helps you organize your tv series with well, you know... Dexterity
141
+ test_files: []
142
+