dropcaster 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -4,8 +4,10 @@ gem 'ruby-mp3info'
4
4
  gem 'activesupport'
5
5
 
6
6
  group :development do
7
+ gem "rake", "0.8.7" # http://stackoverflow.com/questions/6085610
7
8
  gem "bundler", "~> 1.0.0"
8
9
  gem "jeweler", "~> 1.6.4"
9
10
  gem 'libxml-ruby'
10
11
  gem 'rdoc'
12
+ gem 'json'
11
13
  end
data/Gemfile.lock CHANGED
@@ -7,9 +7,10 @@ GEM
7
7
  bundler (~> 1.0)
8
8
  git (>= 1.2.5)
9
9
  rake
10
+ json (1.5.3)
10
11
  libxml-ruby (2.2.1)
11
12
  libxml-ruby (2.2.1-x86-mingw32)
12
- rake (0.9.2)
13
+ rake (0.8.7)
13
14
  rdoc (3.9.3)
14
15
  ruby-mp3info (0.6.15)
15
16
 
@@ -21,6 +22,8 @@ DEPENDENCIES
21
22
  activesupport
22
23
  bundler (~> 1.0.0)
23
24
  jeweler (~> 1.6.4)
25
+ json
24
26
  libxml-ruby
27
+ rake (= 0.8.7)
25
28
  rdoc
26
29
  ruby-mp3info
data/README.md CHANGED
@@ -110,7 +110,7 @@ Advanced features
110
110
  =================
111
111
  Overriding defaults
112
112
  -------------------
113
- Dropcaster is opinionated software. That means, it makes a number of assumptions about names, files, and directory strictures. Dropcaster will be most easy to use if these assumptions and opinions apply to your way of using the program.
113
+ Dropcaster is opinionated software. That means, it makes a number of assumptions about names, files, and directory structures. Dropcaster will be most simple to use if these assumptions and opinions apply to your way of using the program.
114
114
 
115
115
  However, it is still possible to override Dropcaster's behavior in many ways. You can, for instance, host your episode files on a different URL than the channel. Instead of writing title, subtitle, etc. to a channel.yml, you may also spedify them on the command line.
116
116
 
@@ -118,6 +118,10 @@ In order to find out about all the options, simply run
118
118
 
119
119
  $ dropcaster --help
120
120
 
121
+ Using custom channel templates
122
+ ------------------------------
123
+ Dropcaster generates a feed that is suitable for most podcast clients, especially iTunes. It is also possible to customize the channel by supplying an alternative channel template on the command line. Start your own template by copying the default template, or look at the test directory of the dropcaster gem. You can get there by running `gem open dropcaster`.
124
+
121
125
  Sidecar files
122
126
  -------------
123
127
  You may override the meta data for any episode by providing a YAML file with the same name as the mp3 file, but with an extension of yml or yaml (ususally refered to as [sidecar file](http://en.wikipedia.org/wiki/Sidecar_file)). Any attributes specified in this file override the ID tags in the mp3 file.
@@ -134,10 +138,22 @@ The whole concept of Dropcaster works perfectly fine without Dropbox. Just run t
134
138
 
135
139
  Episode Identifier (uuid)
136
140
  -------------------------
137
- Dropcaster uses a rather simple approach to uniquely identify the episodes. It simply generates a SHA1 hash of the mp3 file. If it changes, for whatever reason (even if only a tag was changes), the episode will get a new UUID, and any podcatcher will fetch the episode again (which is what you want, in most cases).
141
+ Dropcaster uses a rather simple approach to uniquely identify the episodes. It simply generates a SHA1 hash of the mp3 file. If it changes, for whatever reason (even if only a tag was changed), the episode will get a new UUID, and any podcatcher will fetch the episode again (which is what you want, in most cases).
138
142
 
139
143
  Modifying the sidecar file does not change the UUID, because it only affects the feed and not the episode itself.
140
144
 
145
+ I Don't Like the Output Format that Dropcaster produces
146
+ -------------------------------------------------------
147
+ Dropcaster uses an ERB template to generate the XML feed. The template was written so that it is easy to understand, but not necessarily in a way that would make the output rather nice-looking. That should not be an issue, as long as the XML is correct.
148
+
149
+ It you prefer a more aesthetically pleasing output, just pipe the output of Dropcaster through `xmllint`, which is part of [libxml](http://xmlsoft.org/), which in turn is one of the prerequisites of the Dropcaster gem, and, as such, installed with Dropcaster):
150
+
151
+ dropcaster | xmllint --format -
152
+
153
+ For writing the output to a file, just redirect the ouput of the above command:
154
+
155
+ dropcaster | xmllint --format - > index.rss
156
+
141
157
  Contributing to Dropcaster
142
158
  ==========================
143
159
  Dropcaster is hosted at [Github](http://github.com/nerab/dropcaster):
data/TODO CHANGED
@@ -1,9 +1,6 @@
1
1
  * Handle channel URLs not ending with a slash (may be required for hosts not discovering an index.html, like Dropbox)
2
- * In verbose mode, log warnings to STDERR for all the opinionated decisions and fallbacks that Dropcaster takes
3
- * Allow overriding the path to the ERB template (and document the available template variables)
4
- * Support a tree of iTunes categories: <itunes:category text="Technology"><itunes:category text="Gadgets"/></itunes:category>
5
- * If an index.html.erb is present, generate an index.html from it
6
- ** In addition to that, if an item.html.erb is present, generate individual html for each item from it and link to it from the index.html
2
+ * Support subtitle and keywords in channel and items
3
+ * If the option for the item template is present (e.g. --item-template templates/item.html.erb), generate individual html pages for each item from it and link to it from the index.html. Leave it off by default.
7
4
  * Print warnings when one of the specs from http://www.apple.com/itunes/podcasts/specs.html is violated
8
5
  * Allow muting of the iTunes warnings (see above) using a commandline switch
9
6
  * Implement sidecar files
@@ -12,3 +9,4 @@
12
9
  * Potential optimization for large numbers of mp3 files: If there is an existing index.rss file, do not re-read the information from those files that are still there and still have the same hash value.
13
10
  * It feels pretty awkward to refer to the ID3v2 frame names all the time. After all, this is an implementation detail. Maybe we can wrap the frames with a nicer name, but still allow access to the underlying mp3info library? Maybe all we need is some aliasing and / or delegation?
14
11
  * Potentially replace the ERB template with Builder::XmlMarkup, see http://builder.rubyforge.org/ (the same that Rails uses)
12
+ * Expose strip_itunes_private as commandline parameter
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.0.4
data/bin/dropcaster CHANGED
@@ -18,7 +18,6 @@ Basic Usage:
18
18
  dropcaster [DIR]... Prints a podcast feed document for the mp3 files in DIR
19
19
 
20
20
  Options:
21
-
22
21
  HELP
23
22
 
24
23
  def usage
@@ -29,55 +28,56 @@ require 'optparse'
29
28
  require 'dropcaster'
30
29
 
31
30
  options = Hash.new
32
- options[:verbose] = false
33
31
  options[:auto_detect_channel_file] = true
34
32
 
35
33
  opts = OptionParser.new do |opts|
36
34
  opts.banner = help
37
35
 
38
36
  opts.on("--verbose", "Verbose mode - displays additional diagnostic information") do |file|
39
- options[:verbose] = true
37
+ Dropcaster.logger = Logger.new(STDERR)
38
+ Dropcaster.logger.formatter = Dropcaster::LogFormatter.new
39
+ Dropcaster.logger.level = Logger::INFO
40
40
  end
41
41
 
42
42
  opts.on("--channel FILE", "Read the channel definition from FILE instead of channel.yml in the current directory.") do |file|
43
43
  begin
44
- STDERR.puts "Reading channel definition from #{file}" if options[:verbose]
44
+ Dropcaster.logger.info "Reading channel definition from #{file}"
45
45
  options = YAML.load_file(file).merge(options)
46
46
  options[:auto_detect_channel_file] = false
47
47
  rescue
48
- STDERR.puts "Error loading channel definition: #{$!.message}"
49
- STDERR.puts $!.backtrace if options[:verbose]
48
+ Dropcaster.logger.error "Could not load channel definition. #{$!.message}"
49
+ Dropcaster.logger.info $!.backtrace
50
50
  exit(1)
51
51
  end
52
52
  end
53
53
 
54
54
  opts.on("--title STRING", "Use STRING as the channel's title. Overrides settings read from channel definition file.") do |title|
55
- STDERR.puts "Setting channel title to '#{title}' via command line" if options[:verbose]
55
+ Dropcaster.logger.info "Setting channel title to '#{title}' via command line"
56
56
  options[:title] = title
57
57
  end
58
58
 
59
59
  opts.on("--url URL", "Use URL as the channel's url. Overrides settings read from channel definition file.") do |url|
60
- STDERR.puts "Setting channel URL to '#{url}' via command line" if options[:verbose]
60
+ Dropcaster.logger.info "Setting channel URL to '#{url}' via command line"
61
61
  options[:url] = url
62
62
  end
63
63
 
64
64
  opts.on("--description STRING", "Use STRING as the channel's description. Overrides settings read from channel definition file.") do |description|
65
- STDERR.puts "Setting channel description to '#{description}' via command line" if options[:verbose]
65
+ Dropcaster.logger.info "Setting channel description to '#{description}' via command line"
66
66
  options[:description] = description
67
67
  end
68
68
 
69
69
  opts.on("--enclosures URL", "Use URL as the base URL for the channel's enclosures. Overrides settings read from channel definition file.") do |enclosures_url|
70
- STDERR.puts "Setting enclosures base URL to '#{enclosures_url}' via command line" if options[:verbose]
70
+ Dropcaster.logger.info "Setting enclosures base URL to '#{enclosures_url}' via command line"
71
71
  options[:enclosures_url] = enclosures_url
72
72
  end
73
73
 
74
74
  opts.on("--image URL", "Use URL as the channel's image URL. Overrides settings read from channel definition file.") do |image_url|
75
- STDERR.puts "Setting image URL to '#{image_url}' via command line" if options[:verbose]
75
+ Dropcaster.logger.info "Setting image URL to '#{image_url}' via command line"
76
76
  options[:image_url] = image_url
77
77
  end
78
78
 
79
79
  opts.on("--channel-template FILE", "Use FILE as template for generating the channel feed. Overrides the default that comes with Dropcaster.") do |file|
80
- STDERR.puts "Using'#{file}' as channel template file" if options[:verbose]
80
+ Dropcaster.logger.info "Using'#{file}' as channel template file"
81
81
  options[:channel_template] = file
82
82
  end
83
83
 
@@ -95,23 +95,23 @@ if options[:auto_detect_channel_file]
95
95
  channel_file = Dropcaster::ChannelFileLocator.locate(sources)
96
96
 
97
97
  if File.exists?(channel_file)
98
- STDERR.puts "Auto-detected channel file at #{channel_file}" if options[:verbose]
98
+ Dropcaster.logger.info "Auto-detected channel file at #{channel_file}"
99
99
  options_from_yaml = YAML.load_file(channel_file)
100
100
  options = options_from_yaml.merge(options)
101
101
  else
102
- STDERR.puts "No #{channel_file} found."
103
- STDERR.puts usage
102
+ Dropcaster.logger.error "No channel file found at #{channel_file})"
103
+ Dropcaster.logger.info usage
104
104
  exit(1) # No way to continue without a channel definition
105
105
  end
106
106
  end
107
107
 
108
- STDERR.puts "Generating the channel with these options: #{options.inspect}" if options[:verbose]
108
+ Dropcaster.logger.info "Generating the channel with these options: #{options.inspect}"
109
109
 
110
110
  begin
111
111
  puts Dropcaster::Channel.new(sources, options).to_rss
112
112
  rescue
113
- STDERR.puts $!.message
114
- STDERR.puts usage
115
- STDERR.puts $!.backtrace if options[:verbose]
113
+ Dropcaster.logger.error $!.message
114
+ Dropcaster.logger.debug $!.backtrace
115
+ Dropcaster.logger.info usage
116
116
  exit(1)
117
117
  end
@@ -69,11 +69,13 @@
69
69
  # Examples:
70
70
  # :categories: 'Technology'
71
71
  # :categories: ['Technology', 'Gadgets']
72
- # :categories: ['TV &amp; Film', ['Technology', 'Gadgets']]
73
-
72
+ # :categories: ['TV & Film', ['Technology', 'Gadgets']]
73
+ #
74
+ # Don't HTML-escape the categories, this will be taken care of by Dropcaster
75
+ #
74
76
  :categories: ['Technology', 'Gadgets']
75
77
 
76
78
  #
77
79
  # Yes, No or Clean, see http://www.apple.com/itunes/podcasts/specs.html#explicit
78
80
  #
79
- :explicit: 'No'
81
+ :explicit: No
data/dropcaster.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{dropcaster}
8
- s.version = "0.0.3"
8
+ s.version = "0.0.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["nerab"]
12
- s.date = %q{2011-10-13}
12
+ s.date = %q{2011-10-24}
13
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
14
  s.email = %q{nerab@gmx.at}
15
15
  s.executables = ["lstags", "dropcaster", "dropcaster", "lstags"]
@@ -41,10 +41,13 @@ Gem::Specification.new do |s|
41
41
  "lib/dropcaster/errors.rb",
42
42
  "lib/dropcaster/hashkeys.rb",
43
43
  "lib/dropcaster/item.rb",
44
- "templates/iTunes.rss.erb",
44
+ "lib/dropcaster/log_formatter.rb",
45
+ "templates/channel.html.erb",
46
+ "templates/channel.rss.erb",
45
47
  "test/extensions/windows.rb",
46
48
  "test/fixtures/channel.yml",
47
49
  "test/fixtures/iTunes.mp3",
50
+ "test/fixtures/test_template.json.erb",
48
51
  "test/helper.rb",
49
52
  "test/unit/test_app.rb",
50
53
  "test/unit/test_channel.rb",
@@ -64,25 +67,31 @@ Gem::Specification.new do |s|
64
67
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
65
68
  s.add_runtime_dependency(%q<ruby-mp3info>, [">= 0"])
66
69
  s.add_runtime_dependency(%q<activesupport>, [">= 0"])
70
+ s.add_development_dependency(%q<rake>, ["= 0.8.7"])
67
71
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
68
72
  s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
69
73
  s.add_development_dependency(%q<libxml-ruby>, [">= 0"])
70
74
  s.add_development_dependency(%q<rdoc>, [">= 0"])
75
+ s.add_development_dependency(%q<json>, [">= 0"])
71
76
  else
72
77
  s.add_dependency(%q<ruby-mp3info>, [">= 0"])
73
78
  s.add_dependency(%q<activesupport>, [">= 0"])
79
+ s.add_dependency(%q<rake>, ["= 0.8.7"])
74
80
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
75
81
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
76
82
  s.add_dependency(%q<libxml-ruby>, [">= 0"])
77
83
  s.add_dependency(%q<rdoc>, [">= 0"])
84
+ s.add_dependency(%q<json>, [">= 0"])
78
85
  end
79
86
  else
80
87
  s.add_dependency(%q<ruby-mp3info>, [">= 0"])
81
88
  s.add_dependency(%q<activesupport>, [">= 0"])
89
+ s.add_dependency(%q<rake>, ["= 0.8.7"])
82
90
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
83
91
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
84
92
  s.add_dependency(%q<libxml-ruby>, [">= 0"])
85
93
  s.add_dependency(%q<rdoc>, [">= 0"])
94
+ s.add_dependency(%q<json>, [">= 0"])
86
95
  end
87
96
  end
88
97
 
@@ -6,28 +6,31 @@ module Dropcaster
6
6
  # Represents a podcast feed in the RSS 2.0 format
7
7
  #
8
8
  class Channel < DelegateClass(Hash)
9
+ include ERB::Util # for h() in the ERB template
9
10
  include HashKeys
10
11
 
12
+ STORAGE_UNITS = %w(Byte KB MB GB TB)
13
+
11
14
  # Instantiate a new Channel object. +sources+ must be present and can be a String or Array
12
15
  # of Strings, pointing to a one or more directories or MP3 files.
13
16
  #
14
- # +options+ is a hash with all attributes for the channel. The following attributes are
17
+ # +attributes+ is a hash with all attributes for the channel. The following attributes are
15
18
  # mandatory when a new channel is created:
16
19
  #
17
20
  # * <tt>:title</tt> - Title (name) of the podcast
18
21
  # * <tt>:url</tt> - URL to the podcast
19
22
  # * <tt>:description</tt> - Short description of the podcast (a few words)
20
23
  #
21
- def initialize(sources, options)
24
+ def initialize(sources, attributes)
22
25
  super(Hash.new)
23
26
 
24
- # Assert mandatory options
27
+ # Assert mandatory attributes
25
28
  [:title, :url, :description].each{|attr|
26
- raise MissingAttributeError.new(attr) if options[attr].blank?
29
+ raise MissingAttributeError.new(attr) if attributes[attr].blank?
27
30
  }
28
31
 
29
- self.merge!(options)
30
- self.categories = Array.new
32
+ self.merge!(attributes)
33
+ self.explicit = yes_no_or_input(attributes[:explicit])
31
34
  @source_files = Array.new
32
35
 
33
36
  if (sources.respond_to?(:each)) # array
@@ -40,9 +43,18 @@ module Dropcaster
40
43
  end
41
44
 
42
45
  # Prepend the image URL with the channel's base to make an absolute URL
43
- self.image_url = URI.join(self.url, self.image_url).to_s unless self.image_url.blank? || self.image_url =~ /^https?:/
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
44
56
 
45
- channel_template = self.channel_template || File.join(File.dirname(__FILE__), '..', '..', 'templates', 'iTunes.rss.erb')
57
+ channel_template = self.channel_template || File.join(File.dirname(__FILE__), '..', '..', 'templates', 'channel.rss.erb')
46
58
 
47
59
  begin
48
60
  @erb_template = ERB.new(File.new(channel_template), 0, "%<>")
@@ -68,13 +80,20 @@ module Dropcaster
68
80
  @source_files.each{|src|
69
81
  item = Item.new(src)
70
82
 
83
+ Dropcaster.logger.debug("Adding new item from file #{src}")
84
+
71
85
  # set author and image_url from channel if empty
72
- item.tag.artist = self.author if item.artist.blank?
73
- item.image_url = self.image_url if item.image_url.blank?
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
74
95
 
75
96
  # construct absolute URL, based on the channel's enclosures_url attribute
76
- # If enclosures_url is not given, take the channel URL as a base.
77
- self.enclosures_url = self.url if self.enclosures_url.blank?
78
97
  self.enclosures_url << '/' unless self.enclosures_url =~ /\/$/
79
98
  item.url = URI.join(URI.escape(self.enclosures_url), URI.escape(item.file_name))
80
99
 
@@ -84,6 +103,37 @@ module Dropcaster
84
103
  all_items.sort{|x, y| y.pub_date <=> x.pub_date}
85
104
  end
86
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
+
87
137
  private
88
138
  def add_files(src)
89
139
  if File.directory?(src)
@@ -92,5 +142,18 @@ module Dropcaster
92
142
  @source_files << src
93
143
  end
94
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
95
158
  end
96
159
  end
@@ -27,7 +27,7 @@ module Dropcaster
27
27
 
28
28
  if sources.respond_to?(:at)
29
29
  # More than one source given. Check that they are all in the same directory.
30
- distinct_dirs = sources.collect{|source| File.dirname(source)}.uniq
30
+ distinct_dirs = sources.collect{|source| dir_or_self(source)}.uniq
31
31
 
32
32
  if 1 == distinct_dirs.size
33
33
  # If all are the in same directory, use that as source directory where channel.yml is expected.
@@ -38,11 +38,20 @@ module Dropcaster
38
38
  end
39
39
  else
40
40
  # If a single file or directory is given, use that as source directory where channel.yml is expected.
41
- channel_source_dir = File.dirname(sources)
41
+ channel_source_dir = dir_or_self(sources)
42
42
  end
43
-
43
+
44
44
  File.join(channel_source_dir, CHANNEL_YML)
45
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
46
55
  end
47
56
  end
48
57
  end
@@ -7,7 +7,7 @@ module Dropcaster
7
7
 
8
8
  class MissingAttributeError < ConfigurationError
9
9
  def initialize(missingAttribute)
10
- super("#{missingAttribute} is a mandatory attribute, but it is missing.")
10
+ super("#{missingAttribute} is a mandatory channel attribute, but it is missing.")
11
11
  end
12
12
  end
13
13
 
@@ -17,23 +17,27 @@ module Dropcaster
17
17
 
18
18
  self[:file_size] = File.new(self.file_name).stat.size
19
19
  self[:uuid] = Digest::SHA1.hexdigest(File.read(self.file_name))
20
-
21
- if self.tag2.TDR.blank?
22
- self[:pub_date] = DateTime.parse(File.new(self.file_name).mtime.to_s)
23
- else
20
+
21
+ unless self.tag2.TDR.blank?
24
22
  self[:pub_date] = DateTime.parse(self.tag2.TDR)
23
+ else
24
+ Dropcaster.logger.info("#{file_path} has no pub date set, using the file's modification time")
25
+ self[:pub_date] = DateTime.parse(File.new(self.file_name).mtime.to_s)
25
26
  end
26
-
27
+
27
28
  # Remove iTunes normalization crap (if configured)
28
- self.tag2.COM.delete_if{|comment|
29
- comment =~ /^( [0-9A-F]{8}){10}$/
30
- } if options && options[:strip_itunes_private]
31
-
29
+ if options && options[:strip_itunes_private]
30
+ Dropcaster.logger.info("Removing iTunes' private normalization information from comments")
31
+ self.tag2.COM.delete_if{|comment|
32
+ comment =~ /^( [0-9A-F]{8}){10}$/
33
+ }
34
+ end
35
+
32
36
  # Convert lyrics frame into a hash, keyed by the three-letter language code
33
37
  if tag2.ULT
34
38
  lyrics_parts = tag2.ULT.split(0.chr)
35
39
 
36
- if lyrics_parts && 3 == lyrics_parts.size
40
+ if lyrics_parts && 3 == lyrics_parts.size
37
41
  self.lyrics = Hash.new
38
42
  self.lyrics[lyrics_parts[1]] = lyrics_parts[2]
39
43
  end
@@ -0,0 +1,9 @@
1
+ require 'logger'
2
+
3
+ module Dropcaster
4
+ class LogFormatter < Logger::Formatter
5
+ def call(severity, time, program_name, message)
6
+ "#{severity}: #{message}\n"
7
+ end
8
+ end
9
+ end
data/lib/dropcaster.rb CHANGED
@@ -5,8 +5,12 @@ require 'delegate'
5
5
  require 'yaml'
6
6
  require 'active_support/core_ext/date_time/conversions'
7
7
  require 'active_support/core_ext/object/blank'
8
+ require 'active_support/core_ext/module/attribute_accessors'
9
+ require 'logger'
10
+ require 'active_support/core_ext/logger'
8
11
 
9
12
  require 'dropcaster/errors'
13
+ require 'dropcaster/log_formatter'
10
14
  require 'dropcaster/hashkeys'
11
15
  require 'dropcaster/channel'
12
16
  require 'dropcaster/item'
@@ -15,4 +19,12 @@ require 'dropcaster/channel_file_locator'
15
19
  module Dropcaster
16
20
  VERSION = File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION]))
17
21
  CHANNEL_YML = 'channel.yml'
22
+
23
+ mattr_accessor :logger
24
+
25
+ unless @@logger
26
+ @@logger = Logger.new(STDERR)
27
+ @@logger.level = Logger::WARN
28
+ @@logger.formatter = LogFormatter.new
29
+ end
18
30
  end
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= h(language) %>">
3
+ <head>
4
+ <meta charset="utf-8"/>
5
+ <title><%= h(title) %></title>
6
+ <meta name="generator" content="Dropcaster <%= Dropcaster::VERSION%>"/>
7
+ <% unless copyright.blank? %>
8
+ <meta name="copyright" content="<%= h(copyright) %>"/>
9
+ <% end %>
10
+ <link rel="canonical" href="<%= h(url) %>"/>
11
+ </head>
12
+
13
+ <body id="home">
14
+ <h1><%= h(title) %></h1>
15
+ <% unless subtitle.blank? %>
16
+ <h2><%= h(subtitle) %></h2>
17
+ <% end %>
18
+
19
+ <% unless image_url.blank? %>
20
+ <img src="<%= image_url %>"/>
21
+ <% end %>
22
+ <p><%= h(description) %></p>
23
+
24
+ <h1>Episodes</h1>
25
+ <% items.each{|item| %>
26
+ <div class="item" id="<%= h(item.uuid) %>">
27
+ <h1><%= item.tag.title || item.tag2.TIT2%></h1>
28
+ <% unless item.tag2.SUBTITLE.blank? %>
29
+ <h2><%= h(item.tag2.SUBTITLE) %></h2>
30
+ <% end %>
31
+ <% unless item.tag2.TT3.blank? %>
32
+ <p><%= h(item.tag2.TT3) %></p>
33
+ <% end %>
34
+ <img src="<%= item.image_url %>"/>
35
+ <p>Download: <a href="<%= item.url %>">MP3</a> (<%= humanize_time(item.duration.to_i) %>, <%= humanize_size(item.file_size) %>)</p>
36
+ <p>Published <%= h(item.pub_date.to_formatted_s(:rfc822)) %></p>
37
+ <% unless item.keywords.blank? %>
38
+ <p><%= h(item.keywords) %></p>
39
+ <% end %>
40
+ </div>
41
+ <% } %>
42
+ </body>
43
+ </html>
@@ -0,0 +1,64 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- This file generated by Dropcaster <%= Dropcaster::VERSION%> -->
3
+ <rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
4
+ <channel>
5
+ <title><%= h(title) %></title>
6
+ <link><%= h(url) %></link>
7
+ <description><%= h(description) %></description>
8
+ <itunes:summary><%= h(description) %></itunes:summary>
9
+ <% unless language.blank? %>
10
+ <language><%= h(language) %></language>
11
+ <% end %>
12
+ <% unless copyright.blank? %>
13
+ <copyright><%= h(copyright) %></copyright>
14
+ <% end %>
15
+ <% unless subtitle.blank? %>
16
+ <itunes:subtitle><%= h(subtitle) %></itunes:subtitle>
17
+ <% end %>
18
+ <% unless author.blank? %>
19
+ <itunes:author><%= h(author) %></itunes:author>
20
+ <% end %>
21
+ <% unless owner.nil? %>
22
+ <itunes:owner>
23
+ <itunes:name><%= h(owner[:name]) %></itunes:name>
24
+ <itunes:email><%= h(owner[:email]) %></itunes:email>
25
+ </itunes:owner>
26
+ <% end %>
27
+ <% unless image_url.blank? %>
28
+ <itunes:image href="<%= image_url %>"/>
29
+ <% end %>
30
+ <% categories.each{|category| %>
31
+ <% if category.respond_to?(:each_line) %>
32
+ <itunes:category text="<%= h(category) %>"/>
33
+ <% else %>
34
+ <itunes:category text="<%= h(category.first) %>">
35
+ <itunes:category text="<%= h(category.last) %>"/>
36
+ </itunes:category>
37
+ <% end %>
38
+ <% } unless categories.blank? %>
39
+ <% unless explicit.blank? %>
40
+ <itunes:explicit><%= h(explicit) %></itunes:explicit>
41
+ <% end %>
42
+ <generator>Dropcaster <%= Dropcaster::VERSION%></generator>
43
+ <% items.each{|item| %>
44
+ <item>
45
+ <title><%= item.tag.title || item.tag2.TIT2%></title>
46
+ <itunes:author><%= h(item.tag2.TP1 || item.tag2.TPE1) %></itunes:author>
47
+ <% unless item.tag2.SUBTITLE.blank? %>
48
+ <itunes:subtitle><%= h(item.tag2.SUBTITLE) %></itunes:subtitle>
49
+ <% end %>
50
+ <% unless item.tag2.TT3.blank? %>
51
+ <itunes:summary><%= h(item.tag2.TT3) %></itunes:summary>
52
+ <% end %>
53
+ <itunes:image href="<%= item.image_url %>"/>
54
+ <enclosure url="<%= item.url %>" length="<%= item.file_size %>" type="audio/mp3"/>
55
+ <guid isPermaLink="false"><%= h(item.uuid) %></guid>
56
+ <pubDate><%= h(item.pub_date.to_formatted_s(:rfc822)) %></pubDate>
57
+ <itunes:duration><%= item.duration.to_i %></itunes:duration>
58
+ <% unless item.keywords.blank? %>
59
+ <itunes:keywords><%= h(item.keywords) %></itunes:keywords>
60
+ <% end %>
61
+ </item>
62
+ <% } %>
63
+ </channel>
64
+ </rss>
@@ -1,13 +1,15 @@
1
- :title: 'All About Everything'
2
- :subtitle: 'A show about everything'
3
- :url: 'http://www.example.com/podcasts/everything/'
4
- :language: 'en-us'
5
- :copyright: '© 2011 John Doe and Family'
6
- :author: 'John Doe'
7
- :description: 'All About Everything is a show about everything. Each week we dive into any subject known to man and talk about it as much as we can. Look for our Podcast in the iTunes Store!'
1
+ :title: All About Everything
2
+ :subtitle: A show about everything
3
+ :url: http://www.example.com/podcasts/everything/
4
+ :language: en-us
5
+ :copyright: © 2011 John Doe and Family
6
+ :author: John Doe
7
+ :description: All About Everything is a show about everything. Each week we dive into any subject known to man and talk about it as much as we can. Look for our Podcast in the iTunes Store!
8
8
  :owner:
9
- :name: 'John Doe'
10
- :email: 'john.doe@example.com'
11
- :image_url: 'AllAboutEverything.jpg'
12
- :categories: ['Technology', 'Gadgets']
13
- :explicit: 'No'
9
+ :name: John Doe
10
+ :email: john.doe@example.com
11
+ :image_url: AllAboutEverything.jpg
12
+ :categories:
13
+ - [Technology, Gadgets]
14
+ - TV & Film
15
+ :explicit: No
@@ -0,0 +1,40 @@
1
+ {
2
+ "generator" : {
3
+ "name" : "Dropcaster",
4
+ "version" : "<%= Dropcaster::VERSION%>"
5
+ },
6
+ "channel" : {
7
+ "title" : "<%= title %>",
8
+ "link" : "<%= url %>",
9
+ "description" : "<%= description %>",
10
+ "language" : "<%= language %>",
11
+ "copyright" : "<%= copyright %>",
12
+ "subtitle" : "<%= subtitle %>",
13
+ "author" : "<%= author %>",
14
+ "owner" : {
15
+ "name" : "<%= owner[:name] %>",
16
+ "email" : "<%= owner[:email] %>"
17
+ },
18
+ "image" : "<%= image_url %>",
19
+ "categories": ["<% categories.join('\",\"') %>"],
20
+ "explicit" : "<%= explicit %>",
21
+ "items" : [<% items.collect{|item| %>{
22
+ "title" : "<%= item.tag.title || item.tag2.TIT2 %>",
23
+ "author" : "<%= item.tag2.TP1 || item.tag2.TPE1 %>",
24
+ "subtitle" : "<%= item.tag2.SUBTITLE %>",
25
+ "summary" : "<%= item.tag2.TT3 %>",
26
+ "image" : "<%= item.image_url %>",
27
+ "enclosure" : {
28
+ "url" : "<%= item.url %>",
29
+ "length" : "<%= item.file_size %>",
30
+ "type" : "audio/mp3"
31
+ },
32
+ "guid" : "<%= item.uuid %>",
33
+ "pubDate" : "<%= item.pub_date.to_formatted_s(:rfc822) %>",
34
+ "duration" : "<%= item.duration.to_i %>",
35
+ "keywords" : "<%= item.keywords %>"
36
+ }
37
+ <% }.join(',') %>
38
+ ]
39
+ }
40
+ }
@@ -2,6 +2,7 @@ require 'helper'
2
2
  require 'test_channel_xml'
3
3
  require 'open3'
4
4
  require 'uri'
5
+ require 'json'
5
6
 
6
7
  #
7
8
  # End-to-end test
@@ -28,6 +29,11 @@ class TestApp < TestChannelXML
28
29
  assert_equal(test_link, channel.find('link').first.content)
29
30
  end
30
31
 
32
+ def test_dir_only
33
+ channel = channel_node(%x[#{APP_SCRIPT} #{FIXTURES_DIR}])
34
+ assert_equal(1, channel.find('item').size)
35
+ end
36
+
31
37
  def test_overwrite_enclosures_url
32
38
  test_enclosures_url = 'http://www.example.com/foo/bar/episodes/'
33
39
  channel = channel_node(%x[#{APP_SCRIPT} #{FIXTURE_ITUNES_MP3} --enclosures '#{test_enclosures_url}'])
@@ -57,21 +63,21 @@ class TestApp < TestChannelXML
57
63
 
58
64
  def test_no_channel_file
59
65
  Open3.popen3(APP_SCRIPT){|stdin, stdout, stderr|
60
- assert_match(/No \.\/channel.yml found/, stderr.read)
66
+ assert_match(/ERROR: No channel file found/, stderr.read)
61
67
  } unless Kernel.is_windows?
62
68
  end
63
69
 
64
70
  def test_overwrite_all
65
- test_title = 'Bob and Alice in Wonderland'
66
- test_link = 'http://www.example.com/bar/foot'
67
- test_description = 'Testing commandline apps is really not that hard.'
71
+ test_title = 'Bob and Alice in Wonderland'
72
+ test_link = 'http://www.example.com/bar/foot'
73
+ test_description = 'Testing commandline apps is really not that hard.'
68
74
 
69
- channel = channel_node(%x[#{APP_SCRIPT} #{FIXTURE_ITUNES_MP3} --title '#{test_title}' --url '#{test_link}' --description '#{test_description}'])
75
+ channel = channel_node(%x[#{APP_SCRIPT} #{FIXTURE_ITUNES_MP3} --title '#{test_title}' --url '#{test_link}' --description '#{test_description}'])
70
76
 
71
- assert_equal(test_title, channel.find('title').first.content)
72
- assert_equal(test_link, channel.find('link').first.content)
73
- assert_equal(test_description, channel.find('description').first.content)
74
- assert_equal(test_description, channel.find('itunes:summary', NS_ITUNES).first.content)
77
+ assert_equal(test_title, channel.find('title').first.content)
78
+ assert_equal(test_link, channel.find('link').first.content)
79
+ assert_equal(test_description, channel.find('description').first.content)
80
+ assert_equal(test_description, channel.find('itunes:summary', NS_ITUNES).first.content)
75
81
  end
76
82
 
77
83
  def test_channel_template_not_found
@@ -80,5 +86,14 @@ class TestApp < TestChannelXML
80
86
  } unless Kernel.is_windows?
81
87
  end
82
88
 
89
+ #
90
+ # We supply an alternative channel template that produces JSON, parse it back and make some basic assertions on the results
91
+ #
92
+ def test_overwrite_channel_template
93
+ channel_template = File.join(FIXTURES_DIR, 'test_template.json.erb')
94
+ channel = JSON.parse(%x[#{APP_SCRIPT} #{FIXTURE_ITUNES_MP3} --channel-template #{channel_template}])
95
+ assert_equal("All About Everything", channel['channel']['title'])
96
+ end
97
+
83
98
  # TODO --channel
84
99
  end
@@ -26,7 +26,48 @@ class TestChannel < Test::Unit::TestCase
26
26
  assert_equal(@options[:owner][:email], owner[:email])
27
27
 
28
28
  assert_equal(URI.join(@options[:url], @options[:image_url]).to_s, @channel.image_url)
29
- # TODO :categories: ['Technology', 'Gadgets']
30
- assert_equal(@options[:explicit], @channel.explicit)
29
+
30
+ categories = @channel.categories
31
+ assert_equal(@options[:categories], categories)
32
+ end
33
+
34
+ def test_channel_explicit_yes
35
+ assert_channel_explicit('Yes', true)
36
+ end
37
+
38
+ def test_channel_explicit_no
39
+ assert_channel_explicit('No', false)
40
+ end
41
+
42
+ def test_channel_explicit_nil
43
+ assert_channel_explicit(nil, nil)
44
+ end
45
+
46
+ def test_channel_explicit_clean
47
+ assert_channel_explicit('Clean', 'Clean')
48
+ end
49
+
50
+ def assert_channel_explicit(expected, value)
51
+ @options[:explicit] = value
52
+ channel = Dropcaster::Channel.new(FIXTURES_DIR, @options)
53
+ assert_equal(expected, channel.explicit)
54
+ end
55
+
56
+ def test_raise_on_missing_title
57
+ assert_raises Dropcaster::MissingAttributeError do
58
+ Dropcaster::Channel.new(FIXTURES_DIR, {:url => 'bar', :description => 'foobar'})
59
+ end
60
+ end
61
+
62
+ def test_raise_on_missing_url
63
+ assert_raises Dropcaster::MissingAttributeError do
64
+ Dropcaster::Channel.new(FIXTURES_DIR, {:title => 'foo', :description => 'foobar'})
65
+ end
66
+ end
67
+
68
+ def test_raise_on_missing_description
69
+ assert_raises Dropcaster::MissingAttributeError do
70
+ Dropcaster::Channel.new(FIXTURES_DIR, {:title => 'foo', :url => 'bar'})
71
+ end
31
72
  end
32
73
  end
@@ -36,12 +36,14 @@ class TestChannelLocator < Test::Unit::TestCase
36
36
 
37
37
  def test_current_directory
38
38
  sources = File.join(TestChannelLocator.temp_dir, '.')
39
- assert_location(sources)
39
+ assert_location(Pathname.new(sources).cleanpath) # Cleanup path before we compare
40
40
  end
41
41
 
42
42
  def test_single_directory
43
- sources = File.join(TestChannelLocator.temp_dir, 'single_dir')
44
- assert_location(sources)
43
+ source_dir = File.join(TestChannelLocator.temp_dir, 'single_dir')
44
+ Dir.mkdir(source_dir)
45
+ expected = File.join(TestChannelLocator.temp_dir, 'single_dir', Dropcaster::CHANNEL_YML)
46
+ assert_equal(expected, Dropcaster::ChannelFileLocator.locate(source_dir))
45
47
  end
46
48
 
47
49
  def test_array_of_files_same_dir
@@ -46,12 +46,8 @@ class TestChannelXML < Test::Unit::TestCase
46
46
  assert_equal('false', guid['isPermaLink'])
47
47
  assert_equal('77bf84447c0f69ce4a33a18b0ae1e030b82010de', guid.content)
48
48
 
49
- assert_equal('Sat, 01 Oct 2011 14:08:20 +0200', item.find('pubDate').first.content)
49
+ assert_equal('Wed, 05 Oct 2011 21:32:26 +0200', item.find('pubDate').first.content)
50
50
  assert_equal('3', item.find('itunes:duration', NS_ITUNES).first.content)
51
-
52
- # Not used in our fixture yet
53
- # assert_equal('', item.find('itunes:subtitle', NS_ITUNES).first.content)
54
- # assert_equal('', item.find('itunes:keywords', NS_ITUNES).first.content)
55
51
  end
56
52
 
57
53
  def test_attributes_mandatory
@@ -81,7 +77,13 @@ class TestChannelXML < Test::Unit::TestCase
81
77
  assert_equal(@options[:owner][:email], owner.find('itunes:email', NS_ITUNES).first.content)
82
78
  assert_equal(URI.join(@options[:url], @options[:image_url]).to_s, @channel.find('itunes:image', NS_ITUNES).first['href'])
83
79
 
84
- # TODO :categories: ['Technology', 'Gadgets']
85
- assert_equal(@options[:explicit], @channel.find('itunes:explicit', NS_ITUNES).first.content)
80
+ categories = @channel.find('itunes:category', NS_ITUNES)
81
+ assert_not_nil(categories)
82
+ assert_equal(2, categories.size)
83
+ assert_equal('Technology', categories.first['text'])
84
+ assert_equal('Gadgets', categories.first.find('itunes:category', NS_ITUNES).first['text'])
85
+ assert_equal('TV & Film', categories.last['text'])
86
+
87
+ assert_equal(@options[:explicit] ? 'Yes' : 'No', @channel.find('itunes:explicit', NS_ITUNES).first.content)
86
88
  end
87
89
  end
@@ -11,7 +11,7 @@ class TestItem < Test::Unit::TestCase
11
11
  assert_in_delta(3, @item.duration, 0.005)
12
12
  assert_equal(58119, @item.file_size)
13
13
  assert_equal('77bf84447c0f69ce4a33a18b0ae1e030b82010de', @item.uuid)
14
- assert_equal(1317470900, @item.pub_date.to_i)
14
+ assert_equal(1317843146, @item.pub_date.to_i)
15
15
  assert_equal('test/fixtures/iTunes.mp3', @item.file_name)
16
16
  end
17
17
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dropcaster
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 3
10
- version: 0.0.3
9
+ - 4
10
+ version: 0.0.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - nerab
@@ -15,12 +15,11 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-10-13 00:00:00 +02:00
18
+ date: 2011-10-24 00:00:00 +02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
- type: :runtime
23
- requirement: &id001 !ruby/object:Gem::Requirement
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
23
  none: false
25
24
  requirements:
26
25
  - - ">="
@@ -29,12 +28,12 @@ dependencies:
29
28
  segments:
30
29
  - 0
31
30
  version: "0"
32
- name: ruby-mp3info
33
- version_requirements: *id001
31
+ type: :runtime
32
+ requirement: *id001
34
33
  prerelease: false
34
+ name: ruby-mp3info
35
35
  - !ruby/object:Gem::Dependency
36
- type: :runtime
37
- requirement: &id002 !ruby/object:Gem::Requirement
36
+ version_requirements: &id002 !ruby/object:Gem::Requirement
38
37
  none: false
39
38
  requirements:
40
39
  - - ">="
@@ -43,12 +42,28 @@ dependencies:
43
42
  segments:
44
43
  - 0
45
44
  version: "0"
46
- name: activesupport
47
- version_requirements: *id002
45
+ type: :runtime
46
+ requirement: *id002
48
47
  prerelease: false
48
+ name: activesupport
49
49
  - !ruby/object:Gem::Dependency
50
+ version_requirements: &id003 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - "="
54
+ - !ruby/object:Gem::Version
55
+ hash: 49
56
+ segments:
57
+ - 0
58
+ - 8
59
+ - 7
60
+ version: 0.8.7
50
61
  type: :development
51
- requirement: &id003 !ruby/object:Gem::Requirement
62
+ requirement: *id003
63
+ prerelease: false
64
+ name: rake
65
+ - !ruby/object:Gem::Dependency
66
+ version_requirements: &id004 !ruby/object:Gem::Requirement
52
67
  none: false
53
68
  requirements:
54
69
  - - ~>
@@ -59,12 +74,12 @@ dependencies:
59
74
  - 0
60
75
  - 0
61
76
  version: 1.0.0
62
- name: bundler
63
- version_requirements: *id003
77
+ type: :development
78
+ requirement: *id004
64
79
  prerelease: false
80
+ name: bundler
65
81
  - !ruby/object:Gem::Dependency
66
- type: :development
67
- requirement: &id004 !ruby/object:Gem::Requirement
82
+ version_requirements: &id005 !ruby/object:Gem::Requirement
68
83
  none: false
69
84
  requirements:
70
85
  - - ~>
@@ -75,12 +90,12 @@ dependencies:
75
90
  - 6
76
91
  - 4
77
92
  version: 1.6.4
78
- name: jeweler
79
- version_requirements: *id004
93
+ type: :development
94
+ requirement: *id005
80
95
  prerelease: false
96
+ name: jeweler
81
97
  - !ruby/object:Gem::Dependency
82
- type: :development
83
- requirement: &id005 !ruby/object:Gem::Requirement
98
+ version_requirements: &id006 !ruby/object:Gem::Requirement
84
99
  none: false
85
100
  requirements:
86
101
  - - ">="
@@ -89,12 +104,12 @@ dependencies:
89
104
  segments:
90
105
  - 0
91
106
  version: "0"
92
- name: libxml-ruby
93
- version_requirements: *id005
107
+ type: :development
108
+ requirement: *id006
94
109
  prerelease: false
110
+ name: libxml-ruby
95
111
  - !ruby/object:Gem::Dependency
96
- type: :development
97
- requirement: &id006 !ruby/object:Gem::Requirement
112
+ version_requirements: &id007 !ruby/object:Gem::Requirement
98
113
  none: false
99
114
  requirements:
100
115
  - - ">="
@@ -103,9 +118,24 @@ dependencies:
103
118
  segments:
104
119
  - 0
105
120
  version: "0"
121
+ type: :development
122
+ requirement: *id007
123
+ prerelease: false
106
124
  name: rdoc
107
- version_requirements: *id006
125
+ - !ruby/object:Gem::Dependency
126
+ version_requirements: &id008 !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ hash: 3
132
+ segments:
133
+ - 0
134
+ version: "0"
135
+ type: :development
136
+ requirement: *id008
108
137
  prerelease: false
138
+ name: json
109
139
  description: 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.
110
140
  email: nerab@gmx.at
111
141
  executables:
@@ -142,10 +172,13 @@ files:
142
172
  - lib/dropcaster/errors.rb
143
173
  - lib/dropcaster/hashkeys.rb
144
174
  - lib/dropcaster/item.rb
145
- - templates/iTunes.rss.erb
175
+ - lib/dropcaster/log_formatter.rb
176
+ - templates/channel.html.erb
177
+ - templates/channel.rss.erb
146
178
  - test/extensions/windows.rb
147
179
  - test/fixtures/channel.yml
148
180
  - test/fixtures/iTunes.mp3
181
+ - test/fixtures/test_template.json.erb
149
182
  - test/helper.rb
150
183
  - test/unit/test_app.rb
151
184
  - test/unit/test_channel.rb
@@ -1,57 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!-- This file was automatically generated by Dropcaster <%= Dropcaster::VERSION%> -->
3
- <rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
4
- <channel>
5
- <title><%= title %></title>
6
- <link><%= url %></link>
7
- <description><%= description %></description>
8
- <itunes:summary><%= description %></itunes:summary>
9
- <% unless language.blank? %>
10
- <language><%= language %></language>
11
- <% end %>
12
- <% unless copyright.blank? %>
13
- <copyright><%= copyright %></copyright>
14
- <% end %>
15
- <% unless subtitle.blank? %>
16
- <itunes:subtitle><%= subtitle %></itunes:subtitle>
17
- <% end %>
18
- <% unless author.blank? %>
19
- <itunes:author><%= author %></itunes:author>
20
- <% end %>
21
- <% unless owner.nil? %>
22
- <itunes:owner>
23
- <itunes:name><%= owner[:name] %></itunes:name>
24
- <itunes:email><%= owner[:email] %></itunes:email>
25
- </itunes:owner>
26
- <% end %>
27
- <% unless image_url.blank? %>
28
- <itunes:image href="<%= image_url %>"/>
29
- <% end %>
30
- <% categories.each{|category| %>
31
- <itunes:category text="<%= category %>"/>
32
- <% } %>
33
- <% unless explicit.blank? %>
34
- <itunes:explicit><%= explicit %></itunes:explicit>
35
- <% end %>
36
- <% items.each{|item| %>
37
- <item>
38
- <title><%= item.tag.title || item.tag2.TIT2%></title>
39
- <itunes:author><%= item.tag2.TP1 || item.tag2.TPE1 %></itunes:author>
40
- <% unless item.tag2.SUBTITLE.blank? %>
41
- <itunes:subtitle><%= item.tag2.SUBTITLE %></itunes:subtitle>
42
- <% end %>
43
- <% unless item.tag2.TT3.blank? %>
44
- <itunes:summary><%= item.tag2.TT3 %></itunes:summary>
45
- <% end %>
46
- <itunes:image href="<%= item.image_url %>"/>
47
- <enclosure url="<%= item.url %>" length="<%= item.file_size %>" type="audio/mp3"/>
48
- <guid isPermaLink="false"><%= item.uuid %></guid>
49
- <pubDate><%= item.pub_date.to_formatted_s(:rfc822) %></pubDate>
50
- <itunes:duration><%= item.duration.to_i %></itunes:duration>
51
- <% unless item.keywords.blank? %>
52
- <itunes:keywords><%= item.keywords %></itunes:keywords>
53
- <% end %>
54
- </item>
55
- <% } %>
56
- </channel>
57
- </rss>