dropcaster 0.0.4 → 0.0.5.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/dropcaster.gemspec CHANGED
@@ -1,97 +1,36 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in rakefile, and run 'rake gemspec'
4
- # -*- encoding: utf-8 -*-
5
-
6
- Gem::Specification.new do |s|
7
- s.name = %q{dropcaster}
8
- s.version = "0.0.4"
9
-
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["nerab"]
12
- s.date = %q{2011-10-24}
13
- s.description = %q{Dropcaster is a podcast feed generator for the command line. It is most simple to use with Dropbox, but works equally well with any other hoster.}
14
- s.email = %q{nerab@gmx.at}
15
- s.executables = ["lstags", "dropcaster", "dropcaster", "lstags"]
16
- s.extra_rdoc_files = [
17
- "LICENSE.txt",
18
- "README.md",
19
- "TODO"
20
- ]
21
- s.files = [
22
- ".document",
23
- "Gemfile",
24
- "Gemfile.lock",
25
- "LICENSE.txt",
26
- "README.md",
27
- "Rakefile",
28
- "TODO",
29
- "VERSION",
30
- "bin/dropcaster",
31
- "bin/lstags",
32
- "doc/infoPane.png",
33
- "doc/lyricsPane.png",
34
- "doc/sample-channel.yml",
35
- "doc/sample-sidecar.yml",
36
- "doc/videoPane.png",
37
- "dropcaster.gemspec",
38
- "lib/dropcaster.rb",
39
- "lib/dropcaster/channel.rb",
40
- "lib/dropcaster/channel_file_locator.rb",
41
- "lib/dropcaster/errors.rb",
42
- "lib/dropcaster/hashkeys.rb",
43
- "lib/dropcaster/item.rb",
44
- "lib/dropcaster/log_formatter.rb",
45
- "templates/channel.html.erb",
46
- "templates/channel.rss.erb",
47
- "test/extensions/windows.rb",
48
- "test/fixtures/channel.yml",
49
- "test/fixtures/iTunes.mp3",
50
- "test/fixtures/test_template.json.erb",
51
- "test/helper.rb",
52
- "test/unit/test_app.rb",
53
- "test/unit/test_channel.rb",
54
- "test/unit/test_channel_locator.rb",
55
- "test/unit/test_channel_xml.rb",
56
- "test/unit/test_item.rb"
57
- ]
58
- s.homepage = %q{http://nerab.github.com/dropcaster}
59
- s.licenses = ["MIT"]
60
- s.require_paths = ["lib"]
61
- s.rubygems_version = %q{1.6.1}
62
- s.summary = %q{Simple Podcast Publishing with Dropbox}
63
-
64
- if s.respond_to? :specification_version then
65
- s.specification_version = 3
66
-
67
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
68
- s.add_runtime_dependency(%q<ruby-mp3info>, [">= 0"])
69
- s.add_runtime_dependency(%q<activesupport>, [">= 0"])
70
- s.add_development_dependency(%q<rake>, ["= 0.8.7"])
71
- s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
72
- s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
73
- s.add_development_dependency(%q<libxml-ruby>, [">= 0"])
74
- s.add_development_dependency(%q<rdoc>, [">= 0"])
75
- s.add_development_dependency(%q<json>, [">= 0"])
76
- else
77
- s.add_dependency(%q<ruby-mp3info>, [">= 0"])
78
- s.add_dependency(%q<activesupport>, [">= 0"])
79
- s.add_dependency(%q<rake>, ["= 0.8.7"])
80
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
81
- s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
82
- s.add_dependency(%q<libxml-ruby>, [">= 0"])
83
- s.add_dependency(%q<rdoc>, [">= 0"])
84
- s.add_dependency(%q<json>, [">= 0"])
85
- end
86
- else
87
- s.add_dependency(%q<ruby-mp3info>, [">= 0"])
88
- s.add_dependency(%q<activesupport>, [">= 0"])
89
- s.add_dependency(%q<rake>, ["= 0.8.7"])
90
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
91
- s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
92
- s.add_dependency(%q<libxml-ruby>, [">= 0"])
93
- s.add_dependency(%q<rdoc>, [">= 0"])
94
- s.add_dependency(%q<json>, [">= 0"])
95
- end
96
- end
97
-
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dropcaster/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dropcaster"
8
+ spec.version = Dropcaster::VERSION
9
+ spec.authors = ["Nicholas E. Rabenau"]
10
+ spec.email = ["nerab@gmx.at"]
11
+ spec.summary = %q{Simple Podcast Publishing with Dropbox}
12
+ spec.description = %q{Dropcaster is a podcast feed generator for the command line. It is most simple to use with Dropbox, but works equally well with any other hoster.}
13
+ spec.homepage = "https://github.com/nerab/dropcaster"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'ruby-mp3info', '~> 0.8'
22
+ spec.add_dependency 'activesupport', '~> 3.2'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.5'
25
+ spec.add_development_dependency 'rake', '~> 10.1'
26
+ spec.add_development_dependency 'libxml-ruby', '~> 2.7'
27
+ spec.add_development_dependency 'guard-minitest', '~> 2.2'
28
+ spec.add_development_dependency 'guard-bundler', '~> 2.0'
29
+ spec.add_development_dependency 'libnotify', '~> 0.8'
30
+ spec.add_development_dependency 'rb-inotify', '~> 0.9'
31
+ spec.add_development_dependency 'rb-fsevent', '~> 0.9'
32
+ spec.add_development_dependency 'pry', '~> 0.9'
33
+ spec.add_development_dependency 'pry-nav', '~> 0.2'
34
+ spec.add_development_dependency 'pry-stack_explorer', '~> 0.4'
35
+ spec.add_development_dependency 'rb-readline', '~> 0.5'
36
+ end
@@ -1,159 +1,195 @@
1
- require 'erb'
2
- require 'uri'
3
-
4
- module Dropcaster
5
- #
6
- # Represents a podcast feed in the RSS 2.0 format
7
- #
8
- class Channel < DelegateClass(Hash)
9
- include ERB::Util # for h() in the ERB template
10
- include HashKeys
11
-
12
- STORAGE_UNITS = %w(Byte KB MB GB TB)
13
-
14
- # Instantiate a new Channel object. +sources+ must be present and can be a String or Array
15
- # of Strings, pointing to a one or more directories or MP3 files.
16
- #
17
- # +attributes+ is a hash with all attributes for the channel. The following attributes are
18
- # mandatory when a new channel is created:
19
- #
20
- # * <tt>:title</tt> - Title (name) of the podcast
21
- # * <tt>:url</tt> - URL to the podcast
22
- # * <tt>:description</tt> - Short description of the podcast (a few words)
23
- #
24
- def initialize(sources, attributes)
25
- super(Hash.new)
26
-
27
- # Assert mandatory attributes
28
- [:title, :url, :description].each{|attr|
29
- raise MissingAttributeError.new(attr) if attributes[attr].blank?
30
- }
31
-
32
- self.merge!(attributes)
33
- self.explicit = yes_no_or_input(attributes[:explicit])
34
- @source_files = Array.new
35
-
36
- if (sources.respond_to?(:each)) # array
37
- sources.each{|src|
38
- add_files(src)
39
- }
40
- else
41
- # single file or directory
42
- add_files(src)
43
- end
44
-
45
- # Prepend the image URL with the channel's base to make an absolute URL
46
- unless self.image_url.blank? || self.image_url =~ /^https?:/
47
- Dropcaster.logger.info("Channel image URL '#{self.image_url}' is relative, so we prepend it with the channel URL '#{self.url}'")
48
- self.image_url = URI.join(self.url, self.image_url).to_s
49
- end
50
-
51
- # If enclosures_url is not given, take the channel URL as a base.
52
- if self.enclosures_url.blank?
53
- Dropcaster.logger.info("No enclosure URL given, using the channel's enclosure URL")
54
- self.enclosures_url = self.url
55
- end
56
-
57
- channel_template = self.channel_template || File.join(File.dirname(__FILE__), '..', '..', 'templates', 'channel.rss.erb')
58
-
59
- begin
60
- @erb_template = ERB.new(File.new(channel_template), 0, "%<>")
61
- rescue Errno::ENOENT => e
62
- raise TemplateNotFoundError.new(e.message)
63
- end
64
- end
65
-
66
- #
67
- # Returns this channel as an RSS representation. The actual rendering is done with the help
68
- # of an ERB template. By default, it is expected as ../../templates/channel.rss.erb (relative)
69
- # to channel.rb.
70
- #
71
- def to_rss
72
- @erb_template.result(binding)
73
- end
74
-
75
- #
76
- # Returns all items (episodes) of this channel, ordered by newest-first.
77
- #
78
- def items
79
- all_items = Array.new
80
- @source_files.each{|src|
81
- item = Item.new(src)
82
-
83
- Dropcaster.logger.debug("Adding new item from file #{src}")
84
-
85
- # set author and image_url from channel if empty
86
- if item.artist.blank?
87
- Dropcaster.logger.info("#{src} has no artist, using the channel's author")
88
- item.tag.artist = self.author
89
- end
90
-
91
- if item.image_url.blank?
92
- Dropcaster.logger.info("#{src} has no image URL set, using the channel's image URL")
93
- item.image_url = self.image_url
94
- end
95
-
96
- # construct absolute URL, based on the channel's enclosures_url attribute
97
- self.enclosures_url << '/' unless self.enclosures_url =~ /\/$/
98
- item.url = URI.join(URI.escape(self.enclosures_url), URI.escape(item.file_name))
99
-
100
- all_items << item
101
- }
102
-
103
- all_items.sort{|x, y| y.pub_date <=> x.pub_date}
104
- end
105
-
106
- # from http://stackoverflow.com/questions/4136248
107
- def humanize_time(secs)
108
- [[60, :s], [60, :m], [24, :h], [1000, :d]].map{ |count, name|
109
- if secs > 0
110
- secs, n = secs.divmod(count)
111
- "#{n.to_i}#{name}"
112
- end
113
- }.compact.reverse.join(' ')
114
- end
115
-
116
- # Fixed version of https://gist.github.com/260184
117
- def humanize_size(number)
118
- return nil if number.nil?
119
-
120
- storage_units_format = '%n %u'
121
-
122
- if number.to_i < 1024
123
- unit = number > 1 ? 'Bytes' : 'Byte'
124
- return storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
125
- else
126
- max_exp = STORAGE_UNITS.size - 1
127
- number = Float(number)
128
- exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
129
- exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
130
- number /= 1024 ** exponent
131
-
132
- unit = STORAGE_UNITS[exponent]
133
- return storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
134
- end
135
- end
136
-
137
- private
138
- def add_files(src)
139
- if File.directory?(src)
140
- @source_files.concat(Dir.glob(File.join(src, '*.mp3')))
141
- else
142
- @source_files << src
143
- end
144
- end
145
-
146
- #
147
- # Deal with Ruby's autoboxing of Yes, No, true, etc values in YAML
148
- #
149
- def yes_no_or_input(flag)
150
- case flag
151
- when nil : nil
152
- when true : 'Yes'
153
- when false : 'No'
154
- else
155
- flag
156
- end
157
- end
158
- end
159
- end
1
+ require 'erb'
2
+ require 'uri'
3
+
4
+ module Dropcaster
5
+ #
6
+ # Represents a podcast feed in the RSS 2.0 format
7
+ #
8
+ class Channel
9
+ include ERB::Util # for h() in the ERB template
10
+
11
+ STORAGE_UNITS = %w(Byte KB MB GB TB)
12
+ MAX_KEYWORD_COUNT = 12
13
+
14
+ # Instantiate a new Channel object. +sources+ must be present and can be a String or Array
15
+ # of Strings, pointing to a one or more directories or MP3 files.
16
+ #
17
+ # +attributes+ is a hash with all attributes for the channel. The following attributes are
18
+ # mandatory when a new channel is created:
19
+ #
20
+ # * <tt>:title</tt> - Title (name) of the podcast
21
+ # * <tt>:url</tt> - URL to the podcast
22
+ # * <tt>:description</tt> - Short description of the podcast (a few words)
23
+ #
24
+ def initialize(sources, attributes)
25
+ # Assert mandatory attributes
26
+ [:title, :url, :description].each{|attr|
27
+ raise MissingAttributeError.new(attr) if attributes[attr].blank?
28
+ }
29
+
30
+ @attributes = attributes
31
+ @attributes[:explicit] = yes_no_or_input(attributes[:explicit])
32
+ @source_files = Array.new
33
+
34
+ # if (sources.respond_to?(:each)) # array
35
+ if sources.is_a? Array
36
+ sources.each do |src|
37
+ add_files(src)
38
+ end
39
+ else
40
+ # single file or directory
41
+ add_files(sources)
42
+ end
43
+
44
+ # If not absolute, prepend the image URL with the channel's base to make an absolute URL
45
+ unless @attributes[:image_url].blank? || @attributes[:image_url] =~ /^https?:/
46
+ Dropcaster.logger.info("Channel image URL '#{@attributes[:image_url]}' is relative, so we prepend it with the channel URL '#{@attributes[:url]}'")
47
+ @attributes[:image_url] = (URI.parse(@attributes[:url]) + @attributes[:image_url]).to_s
48
+ end
49
+
50
+ # If enclosures_url is not given, take the channel URL as a base.
51
+ if @attributes[:enclosures_url].blank?
52
+ Dropcaster.logger.info("No enclosure URL given, using the channel's enclosure URL")
53
+ @attributes[:enclosures_url] = @attributes[:url]
54
+ end
55
+
56
+ # Warn if keyword count is larger than recommended
57
+ assert_keyword_count(@attributes[:keywords])
58
+
59
+ channel_template = @attributes[:channel_template] || File.join(File.dirname(__FILE__), '..', '..', 'templates', 'channel.rss.erb')
60
+
61
+ begin
62
+ @erb_template = ERB.new(IO.read(channel_template), 0, "%<>")
63
+ rescue Errno::ENOENT => e
64
+ raise TemplateNotFoundError.new(e.message)
65
+ end
66
+ end
67
+
68
+ #
69
+ # Returns this channel as an RSS representation. The actual rendering is done with the help
70
+ # of an ERB template. By default, it is expected as ../../templates/channel.rss.erb (relative)
71
+ # to channel.rb.
72
+ #
73
+ def to_rss
74
+ @erb_template.result(binding)
75
+ end
76
+
77
+ #
78
+ # Returns all items (episodes) of this channel, ordered by newest-first.
79
+ #
80
+ def items
81
+ all_items = Array.new
82
+ @source_files.each{|src|
83
+
84
+ item = Item.new(src)
85
+
86
+ Dropcaster.logger.debug("Adding new item from file #{src}")
87
+
88
+ # set author and image_url from channel if empty
89
+ if item.artist.blank?
90
+ Dropcaster.logger.info("#{src} has no artist, using the channel's author")
91
+ item.tag.artist = @attributes[:author]
92
+ end
93
+
94
+ if item.image_url.blank?
95
+ Dropcaster.logger.info("#{src} has no image URL set, using the channel's image URL")
96
+ item.image_url = @attributes[:image_url]
97
+ end
98
+
99
+ # construct absolute URL, based on the channel's enclosures_url attribute
100
+ item.url = (URI.parse(enclosures_url) + item.file_name)
101
+
102
+ # Warn if keyword count is larger than recommended
103
+ assert_keyword_count(item.keywords)
104
+
105
+ all_items << item
106
+ }
107
+
108
+ all_items.sort{|x, y| y.pub_date <=> x.pub_date}
109
+ end
110
+
111
+ # from http://stackoverflow.com/questions/4136248
112
+ def humanize_time(secs)
113
+ [[60, :s], [60, :m], [24, :h], [1000, :d]].map{ |count, name|
114
+ if secs > 0
115
+ secs, n = secs.divmod(count)
116
+ "#{n.to_i}#{name}"
117
+ end
118
+ }.compact.reverse.join(' ')
119
+ end
120
+
121
+ # Fixed version of https://gist.github.com/260184
122
+ def humanize_size(number)
123
+ return nil if number.nil?
124
+
125
+ storage_units_format = '%n %u'
126
+
127
+ if number.to_i < 1024
128
+ unit = number > 1 ? 'Bytes' : 'Byte'
129
+ return storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
130
+ else
131
+ max_exp = STORAGE_UNITS.size - 1
132
+ number = Float(number)
133
+ exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
134
+ exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
135
+ number /= 1024 ** exponent
136
+
137
+ unit = STORAGE_UNITS[exponent]
138
+ return storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
139
+ end
140
+ end
141
+
142
+ # delegate all unknown methods to @attributes
143
+ def method_missing(meth, *args)
144
+ m = meth.id2name
145
+ if /=$/ =~ m
146
+ @attributes[m.chop.to_sym] = (args.length < 2 ? args[0] : args)
147
+ else
148
+ @attributes[m.to_sym]
149
+ end
150
+ end
151
+
152
+ private
153
+ def add_files(src)
154
+ if File.directory?(src)
155
+ @source_files.concat(Dir.glob(File.join(src, '*.mp3')))
156
+ else
157
+ @source_files << src
158
+ end
159
+ end
160
+
161
+ #
162
+ # Deal with Ruby's autoboxing of Yes, No, true, etc values in YAML
163
+ #
164
+ def yes_no_or_input(flag)
165
+ case flag
166
+ when nil
167
+ nil
168
+ when true
169
+ 'Yes'
170
+ when false
171
+ 'No'
172
+ else
173
+ flag
174
+ end
175
+ end
176
+
177
+ #
178
+ # http://snippets.dzone.com/posts/show/4578
179
+ #
180
+ def truncate(string, count = 30)
181
+ if string.length >= count
182
+ shortened = string[0, count]
183
+ splitted = shortened.split(/\s/)
184
+ words = splitted.length
185
+ splitted[0, words - 1].join(' ') + '...'
186
+ else
187
+ string
188
+ end
189
+ end
190
+
191
+ def assert_keyword_count(keywords)
192
+ Dropcaster.logger.info("The list of keywords has #{keywords.size} entries, which exceeds the recommended maximum of #{MAX_KEYWORD_COUNT}.") if keywords && MAX_KEYWORD_COUNT < keywords.size
193
+ end
194
+ end
195
+ end
@@ -1,57 +1,57 @@
1
- module Dropcaster
2
- #
3
- # Encapsulates the strategy how to find the channel definition file
4
- #
5
- class ChannelFileLocator
6
- class << self
7
- #
8
- # Locates the channel definition file based on the <tt>sources</tt> directory.
9
- #
10
- # * If <tt>sources</tt> is a single file name, the channel definition file is expected
11
- # as channel.yml in the same directory.
12
- #
13
- # * If <tt>sources</tt> is a single directory name, the channel definition file is
14
- # expected as channel.yml in that directory.
15
- #
16
- # * If <tt>sources</tt> is an array of file names, the channel definition file is
17
- # expected as channel.yml in the directory common to all files. If the files are
18
- # located in more than one directory, an AmbiguousSourcesError is raised. In that
19
- # case, the caller should specify the channel.yml as command line parameter.
20
- #
21
- # * If <tt>sources</tt> is an array with more than a single directory name, an
22
- # AmbiguousSourcesError is raised. In that case, the caller should specify the
23
- # channel.yml as command line parameter.
24
- #
25
- def locate(sources)
26
- channel_source_dir = nil
27
-
28
- if sources.respond_to?(:at)
29
- # More than one source given. Check that they are all in the same directory.
30
- distinct_dirs = sources.collect{|source| dir_or_self(source)}.uniq
31
-
32
- if 1 == distinct_dirs.size
33
- # If all are the in same directory, use that as source directory where channel.yml is expected.
34
- channel_source_dir = distinct_dirs.first
35
- else
36
- # Since no channel_file was specified at the command line, throw and quit
37
- raise AmbiguousSourcesError.new(sources)
38
- end
39
- else
40
- # If a single file or directory is given, use that as source directory where channel.yml is expected.
41
- channel_source_dir = dir_or_self(sources)
42
- end
43
-
44
- File.join(channel_source_dir, CHANNEL_YML)
45
- end
46
-
47
- private
48
- def dir_or_self(source)
49
- if File.directory?(source)
50
- source
51
- else
52
- File.dirname(source)
53
- end
54
- end
55
- end
56
- end
57
- end
1
+ module Dropcaster
2
+ #
3
+ # Encapsulates the strategy how to find the channel definition file
4
+ #
5
+ class ChannelFileLocator
6
+ class << self
7
+ #
8
+ # Locates the channel definition file based on the <tt>sources</tt> directory.
9
+ #
10
+ # * If <tt>sources</tt> is a single file name, the channel definition file is expected
11
+ # as channel.yml in the same directory.
12
+ #
13
+ # * If <tt>sources</tt> is a single directory name, the channel definition file is
14
+ # expected as channel.yml in that directory.
15
+ #
16
+ # * If <tt>sources</tt> is an array of file names, the channel definition file is
17
+ # expected as channel.yml in the directory common to all files. If the files are
18
+ # located in more than one directory, an AmbiguousSourcesError is raised. In that
19
+ # case, the caller should specify the channel.yml as command line parameter.
20
+ #
21
+ # * If <tt>sources</tt> is an array with more than a single directory name, an
22
+ # AmbiguousSourcesError is raised. In that case, the caller should specify the
23
+ # channel.yml as command line parameter.
24
+ #
25
+ def locate(sources)
26
+ channel_source_dir = nil
27
+
28
+ if sources.respond_to?(:at)
29
+ # More than one source given. Check that they are all in the same directory.
30
+ distinct_dirs = sources.collect{|source| dir_or_self(source)}.uniq
31
+
32
+ if 1 == distinct_dirs.size
33
+ # If all are the in same directory, use that as source directory where channel.yml is expected.
34
+ channel_source_dir = distinct_dirs.first
35
+ else
36
+ # Since no channel_file was specified at the command line, throw and quit
37
+ raise AmbiguousSourcesError.new(sources)
38
+ end
39
+ else
40
+ # If a single file or directory is given, use that as source directory where channel.yml is expected.
41
+ channel_source_dir = dir_or_self(sources)
42
+ end
43
+
44
+ File.join(channel_source_dir, CHANNEL_YML)
45
+ end
46
+
47
+ private
48
+ def dir_or_self(source)
49
+ if File.directory?(source)
50
+ source
51
+ else
52
+ File.dirname(source)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,25 +1,25 @@
1
- module Dropcaster
2
- class ConfigurationError < StandardError
3
- def initialize(msg)
4
- super(msg)
5
- end
6
- end
7
-
8
- class MissingAttributeError < ConfigurationError
9
- def initialize(missingAttribute)
10
- super("#{missingAttribute} is a mandatory channel attribute, but it is missing.")
11
- end
12
- end
13
-
14
- class AmbiguousSourcesError < ConfigurationError
15
- def initialize(ambiguousSources)
16
- super("The list of sources is ambiguous. Can't derive common directory from these: #{ambiguousSources.inspect}")
17
- end
18
- end
19
-
20
- class TemplateNotFoundError < ConfigurationError
21
- def initialize(message)
22
- super("Unable to load template file: #{message}")
23
- end
24
- end
25
- end
1
+ module Dropcaster
2
+ class ConfigurationError < StandardError
3
+ def initialize(msg)
4
+ super(msg)
5
+ end
6
+ end
7
+
8
+ class MissingAttributeError < ConfigurationError
9
+ def initialize(missingAttribute)
10
+ super("#{missingAttribute} is a mandatory channel attribute, but it is missing.")
11
+ end
12
+ end
13
+
14
+ class AmbiguousSourcesError < ConfigurationError
15
+ def initialize(ambiguousSources)
16
+ super("The list of sources is ambiguous. Can't derive common directory from these: #{ambiguousSources.inspect}")
17
+ end
18
+ end
19
+
20
+ class TemplateNotFoundError < ConfigurationError
21
+ def initialize(message)
22
+ super("Unable to load template file: #{message}")
23
+ end
24
+ end
25
+ end