file_scheduler 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.
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