file_scheduler 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +9 -0
  4. data/Guardfile +8 -0
  5. data/Rakefile +10 -0
  6. data/bin/file-scheduler +19 -0
  7. data/file_scheduler.gemspec +24 -0
  8. data/lib/file_scheduler.rb +21 -0
  9. data/lib/file_scheduler/attributes_parser.rb +40 -0
  10. data/lib/file_scheduler/base.rb +62 -0
  11. data/lib/file_scheduler/content.rb +19 -0
  12. data/lib/file_scheduler/core_ext.rb +37 -0
  13. data/lib/file_scheduler/file.rb +80 -0
  14. data/lib/file_scheduler/log.rb +53 -0
  15. data/lib/file_scheduler/playlist.rb +36 -0
  16. data/lib/file_scheduler/scheduling.rb +59 -0
  17. data/lib/file_scheduler/time_chain.rb +19 -0
  18. data/lib/file_scheduler/time_interval.rb +38 -0
  19. data/lib/file_scheduler/time_mark.rb +66 -0
  20. data/lib/file_scheduler/time_parser.rb +37 -0
  21. data/lib/file_scheduler/url.rb +56 -0
  22. data/lib/file_scheduler/version.rb +3 -0
  23. data/spec/lib/file_scheduler/attributes_parser_spec.rb +33 -0
  24. data/spec/lib/file_scheduler/base_spec.rb +75 -0
  25. data/spec/lib/file_scheduler/content_shared_examples.rb +12 -0
  26. data/spec/lib/file_scheduler/file_spec.rb +104 -0
  27. data/spec/lib/file_scheduler/log_spec.rb +50 -0
  28. data/spec/lib/file_scheduler/playlist_spec.rb +17 -0
  29. data/spec/lib/file_scheduler/scheduling_spec.rb +114 -0
  30. data/spec/lib/file_scheduler/time_interval_spec.rb +46 -0
  31. data/spec/lib/file_scheduler/time_mark_spec.rb +51 -0
  32. data/spec/lib/file_scheduler/time_parser_spec.rb +33 -0
  33. data/spec/lib/file_scheduler/url_spec.rb +33 -0
  34. data/spec/lib/file_scheduler_spec.rb +117 -0
  35. data/spec/spec_helper.rb +12 -0
  36. data/spec/support/pathname.rb +23 -0
  37. data/spec/support/time.rb +19 -0
  38. data/spec/support/tmpdir.rb +1 -0
  39. metadata +118 -0
@@ -0,0 +1,59 @@
1
+ module FileScheduler
2
+ class Scheduling
3
+
4
+ attr_accessor :root, :time, :log
5
+
6
+ def initialize(root, time)
7
+ @root = root
8
+ @time = time
9
+ end
10
+
11
+ def log
12
+ @log ||= FileScheduler::Log.new
13
+ end
14
+
15
+ def schedulable?(content)
16
+ schedulable_by_time?(content) and
17
+ schedulable_by_repeat?(content)
18
+ end
19
+
20
+ def schedulable_by_time?(content)
21
+ content.time_constraints.nil? or
22
+ content.time_constraints.matches?(time)
23
+ end
24
+
25
+ def schedulable_by_repeat?(content)
26
+ content.repeat_constraints.nil? or
27
+ log.distance(content).nil? or
28
+ log.distance(content) >= content.repeat_constraints
29
+ end
30
+
31
+ def schedulable_contents
32
+ @schedulable_contents ||= root.contents.select do |content|
33
+ schedulable? content
34
+ end
35
+ end
36
+
37
+ def forced_contents
38
+ schedulable_contents.select &:forced_started_time?
39
+ end
40
+
41
+ def forced_next
42
+ forced_contents.first
43
+ end
44
+
45
+ def schedulable_next
46
+ schedulable_contents.shuffle.first
47
+ end
48
+
49
+ def next_without_log
50
+ forced_next or schedulable_next
51
+ end
52
+
53
+ def next_with_log
54
+ log.log(next_without_log)
55
+ end
56
+ alias_method :next, :next_with_log
57
+
58
+ end
59
+ end
@@ -0,0 +1,19 @@
1
+ module FileScheduler
2
+ class TimeChain
3
+
4
+ attr_reader :parts
5
+
6
+ def initialize(*parts)
7
+ @parts = parts.flatten
8
+ end
9
+
10
+ def ==(other)
11
+ other.respond_to?(:parts) and parts == other.parts
12
+ end
13
+
14
+ def to_s
15
+ parts.join('/')
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ require 'set'
2
+
3
+ module FileScheduler
4
+ class TimeInterval
5
+
6
+ attr_reader :from, :to
7
+
8
+ def initialize(from, to)
9
+ @from, @to = from, to
10
+ end
11
+
12
+ def include?(time)
13
+ if from > to
14
+ not time.between?(to, from)
15
+ else
16
+ time.between?(from, to)
17
+ end
18
+ end
19
+
20
+ alias_method :matches?, :include?
21
+
22
+ def ==(other)
23
+ [:from, :to].all? do |attribute|
24
+ other.respond_to?(attribute) and send(attribute) == other.send(attribute)
25
+ end
26
+ end
27
+
28
+ def to_s
29
+ "#{from}-#{to}"
30
+ end
31
+
32
+ def inspect
33
+ to_s
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,66 @@
1
+ class Time
2
+ alias_method :minute, :min
3
+ alias_method :week_day, :wday
4
+
5
+ alias_method :compare_to_without_time_mark_support, :<=>
6
+
7
+ def <=>(other)
8
+ if FileScheduler::TimeMark === other
9
+ -(other <=> self)
10
+ else
11
+ compare_to_without_time_mark_support other
12
+ end
13
+ end
14
+ end
15
+
16
+ module FileScheduler
17
+ class TimeMark
18
+
19
+ attr_reader :attributes
20
+
21
+ def initialize(attributes = {})
22
+ @attributes = attributes
23
+ end
24
+
25
+ def matches?(time)
26
+ attributes.all? do |attribute, value|
27
+ time.send(attribute) == value
28
+ end
29
+ end
30
+
31
+ def [](attribute)
32
+ attributes[attribute]
33
+ end
34
+
35
+ [:year, :month, :day, :hour, :minute, :week_day].each do |attribute|
36
+ define_method(attribute) do
37
+ attributes[attribute]
38
+ end
39
+ end
40
+
41
+ def <=>(time)
42
+ [:year, :month, :day, :week_day, :hour, :minute].each do |attribute|
43
+ value = attributes[attribute]
44
+ if value
45
+ comparaison = value <=> time.send(attribute)
46
+ return comparaison unless comparaison == 0
47
+ end
48
+ end
49
+
50
+ 0
51
+ end
52
+
53
+ include Comparable
54
+
55
+ def to_s
56
+ [:year, :month, :day, :week_day, :hour, :minute].collect do |attribute|
57
+ value = attributes[attribute]
58
+ if value
59
+ suffix = attribute == :month ? "M" : attribute.to_s.chars.first
60
+ "#{value}#{suffix}"
61
+ end
62
+ end.join
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,37 @@
1
+ module FileScheduler
2
+ class TimeParser
3
+
4
+ @@time_mark_fields =
5
+ [["y", 4], ["M", 2], ["d", 2], ["w", 1], ["h", 2], ["m", 2]]
6
+
7
+ def self.time_mark_format
8
+ @@time_mark_fields.collect do |field|
9
+ "(\\d{#{field[1]}}#{field[0]})?"
10
+ end.join
11
+ end
12
+
13
+ def parse(string)
14
+ parsed_fields = string.scan(/^T?#{self.class.time_mark_format}(-#{self.class.time_mark_format})?/).first.map do |field|
15
+ field.to_i if field
16
+ end
17
+
18
+ from = time_mark(parsed_fields.first(6))
19
+ to = time_mark(parsed_fields.last(6))
20
+
21
+ if to
22
+ TimeInterval.new from, to
23
+ else
24
+ from
25
+ end
26
+ end
27
+
28
+ def time_mark(parsed_fields)
29
+ unless parsed_fields.all?(&:nil?)
30
+ attributes = Hash[ [[:year, :month, :day, :week_day, :hour, :minute], parsed_fields].transpose ]
31
+ attributes.delete_if { |k,v| v.nil? }
32
+ TimeMark.new attributes
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,56 @@
1
+ module FileScheduler
2
+ class URL
3
+ include FileScheduler::Content
4
+
5
+ attr_accessor :path, :url
6
+
7
+ def initialize(attributes = {})
8
+ if String === attributes
9
+ attributes = { :path => attributes }
10
+ end
11
+
12
+ attributes.each { |k,v| send "#{k}=", v }
13
+ end
14
+
15
+ def ==(other)
16
+ other.respond_to?(:path) and path == other.path
17
+ end
18
+
19
+ def hidden?
20
+ path_parts.any? do |part|
21
+ part.start_with?("_")
22
+ end
23
+ end
24
+
25
+ def path_parts
26
+ @path_parts ||= path.split("/")
27
+ end
28
+
29
+ def name
30
+ @path_parts.last
31
+ end
32
+
33
+ def time_constraints
34
+ part_constraints = path_parts.collect do |part|
35
+ parser.parse(part)
36
+ end.compact
37
+
38
+ if part_constraints.size > 1
39
+ FileScheduler::TimeChain.new part_constraints
40
+ else
41
+ part_constraints.first
42
+ end
43
+ end
44
+
45
+ def to_s
46
+ (url and url.to_s) or path
47
+ end
48
+
49
+ def attributes
50
+ @attributes ||= path_parts.inject({}) do |attributes, part|
51
+ attributes.merge attributes_parser.parse(part)
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,3 @@
1
+ module FileScheduler
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe FileScheduler::AttributesParser do
4
+
5
+ it "should be empty without {}" do
6
+ subject.parse("dummy").should be_empty
7
+ end
8
+
9
+ it "should be empty when {} is empty" do
10
+ subject.parse("dummy{}").should be_empty
11
+ end
12
+
13
+ it "should use {...} at the end of name" do
14
+ subject.parse("dummy{key=value}").should == { :key => "value" }
15
+ end
16
+
17
+ it "should use {...} before file extension" do
18
+ subject.parse("dummy{key=value}.wav").should == { :key => "value" }
19
+ end
20
+
21
+ it "should not use {...} before a simple dot" do
22
+ subject.parse("dummy{key=value}.otherpart.wav").should be_empty
23
+ end
24
+
25
+ it "should support several attributes" do
26
+ subject.parse("dummy{key1=value1,key2=value2}").should == { :key1 => "value1", :key2 => "value2" }
27
+ end
28
+
29
+ it "should strip spaces" do
30
+ subject.parse("dummy{ key1 = value1 , key2 = value2 }").should == { :key1 => "value1", :key2 => "value2" }
31
+ end
32
+
33
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe FileScheduler::Base do
4
+
5
+ before(:each) do
6
+ subject.root = mock(:contents => mock)
7
+ end
8
+
9
+ let(:content) { mock }
10
+
11
+ describe "#contents" do
12
+
13
+ it "should return root contents" do
14
+ subject.contents.should == subject.root.contents
15
+ end
16
+
17
+ end
18
+
19
+ describe "scheduling" do
20
+
21
+ let(:time) { Time.now }
22
+
23
+ it "should return a Scheduling with specified time" do
24
+ subject.scheduling(time).time.should == time
25
+ end
26
+
27
+ it "should return a Scheduling with the same root" do
28
+ subject.scheduling.root.should == subject.root
29
+ end
30
+
31
+ it "should return a Scheduling with the same log" do
32
+ subject.scheduling.log.should == subject.log
33
+ end
34
+
35
+ end
36
+
37
+ describe "next" do
38
+
39
+ let(:next_content) { content }
40
+
41
+ before(:each) do
42
+ subject.stub :scheduling => mock(:next => content)
43
+ end
44
+
45
+ it "should return the next content choosed by scheduling" do
46
+ subject.next.should == next_content
47
+ end
48
+
49
+ it "should invoke after_next with selected content" do
50
+ subject.should_receive(:after_next).with(next_content)
51
+ subject.next
52
+ end
53
+
54
+ end
55
+
56
+ describe "forced_next" do
57
+
58
+ let(:forced_content) { content }
59
+
60
+ before(:each) do
61
+ subject.stub :scheduling => mock(:forced_next => content)
62
+ end
63
+
64
+ it "should return the forced content choosed by scheduling" do
65
+ subject.forced_next.should == forced_content
66
+ end
67
+
68
+ it "should invoke after_next with selected content" do
69
+ subject.should_receive(:after_next).with(forced_content)
70
+ subject.forced_next
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,12 @@
1
+ shared_examples "a content" do
2
+
3
+ describe "#repeat_constraints" do
4
+
5
+ it "should return the number defined by 'repeat' attribute" do
6
+ subject.stub :attributes => { :repeat => "5" }
7
+ subject.repeat_constraints.should == 5
8
+ end
9
+
10
+ end
11
+
12
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ describe FileScheduler::File do
4
+
5
+ subject { file "dummy" }
6
+
7
+ def file(file, parent = nil)
8
+ FileScheduler::File.new file, parent
9
+ end
10
+
11
+ describe "#name" do
12
+
13
+ it "should use path basename" do
14
+ file("/path/to/basename").name.should == "basename"
15
+ end
16
+
17
+ end
18
+
19
+ describe "#local_time_constraints" do
20
+
21
+ it "should parse the time specifications in the name" do
22
+ file("/path/to/12h30m-dummy",mock).local_time_constraints.should == FileScheduler::TimeMark.new(:hour => 12, :minute => 30)
23
+ end
24
+
25
+ it "should be nil when parent isn't defined (root directory)" do
26
+ file("/path/to/12h30m-dummy").local_time_constraints.should be_nil
27
+ end
28
+
29
+ end
30
+
31
+ describe "#time_constraints" do
32
+
33
+ it "should concat with parent constraints" do
34
+ parent = mock(:time_constraints => FileScheduler::TimeMark.new(:week_day => 1))
35
+ file("/path/1w/12h30m-dummy", parent).time_constraints.should == FileScheduler::TimeChain.new(parent.time_constraints, FileScheduler::TimeMark.new(:hour => 12, :minute => 30))
36
+ end
37
+
38
+ end
39
+
40
+ describe "file_system_children" do
41
+
42
+ it "should return pathnames of files and directories present under file" do
43
+ Dir.mktmpdir do |base|
44
+ base = Pathname.new(base)
45
+
46
+ file = base.touch "file"
47
+ directory = base.mkpath "directory"
48
+
49
+ file(base).file_system_children.should include(file, directory)
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ describe "children" do
56
+
57
+ it "should return children created from files and directories present under path" do
58
+ Dir.mktmpdir do |base|
59
+ base = Pathname.new(base)
60
+
61
+ file = base.touch "file"
62
+ directory = base.mkpath "directory"
63
+
64
+ parent = file(base)
65
+ parent.children.should include(file(file, parent))
66
+ parent.children.should include(file(directory, parent))
67
+ end
68
+ end
69
+
70
+ it "should ignore files prefixed with underscore" do
71
+ Dir.mktmpdir do |base|
72
+ base = Pathname.new(base)
73
+
74
+ file = base.touch "_ignored_file"
75
+ directory = base.mkpath "_ignored_directory"
76
+
77
+ file(base).children.should be_empty
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ describe "local_attributes" do
84
+
85
+ it "should use attributes found in name" do
86
+ file("dummy{key=value}.wav").local_attributes.should == { :key => "value" }
87
+ end
88
+
89
+ end
90
+
91
+ describe "#attributes" do
92
+
93
+ it "should merge parent attributes with local attributes" do
94
+ parent = mock(:attributes => {:key1 => "parent_value1", :key2 => "value2"})
95
+ file("dummy{key1=value1}.wav",parent).attributes.should == { :key1 => "value1", :key2 => "value2" }
96
+ end
97
+
98
+ end
99
+
100
+ require File.expand_path("../content_shared_examples", __FILE__)
101
+
102
+ it_behaves_like "a content"
103
+
104
+ end