dropcaster 0.0.8 → 1.0.0
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.
- checksums.yaml +5 -5
- data/.rubocop.yml +174 -0
- data/.ruby-version +1 -1
- data/.travis.yml +14 -4
- data/Gemfile.lock +244 -135
- data/Guardfile +8 -6
- data/Rakefile +7 -4
- data/Vagrantfile +2 -0
- data/bin/dropcaster +60 -58
- data/bin/lstags +28 -25
- data/dropcaster.gemspec +14 -12
- data/lib/dropcaster/channel.rb +53 -42
- data/lib/dropcaster/channel_file_locator.rb +8 -5
- data/lib/dropcaster/contributors.rb +32 -5
- data/lib/dropcaster/errors.rb +6 -4
- data/lib/dropcaster/item.rb +19 -13
- data/lib/dropcaster/log_formatter.rb +10 -0
- data/lib/dropcaster/logging.rb +13 -0
- data/lib/dropcaster/version.rb +3 -1
- data/lib/dropcaster.rb +2 -15
- data/templates/channel.html.erb +1 -1
- data/templates/channel.rss.erb +1 -1
- data/test/Vagrantfile +3 -1
- data/test/bin/vagrant-status +36 -30
- data/test/extensions/windows.rb +3 -1
- data/test/fixtures/extension.MP3 +0 -0
- data/test/fixtures/special &.mp3 +0 -0
- data/test/helper.rb +4 -1
- data/test/unit/test_app.rb +29 -24
- data/test/unit/test_channel.rb +9 -5
- data/test/unit/test_channel_locator.rb +8 -5
- data/test/unit/test_channel_xml.rb +29 -9
- data/test/unit/{test_item.rb → test_itunes_item.rb} +4 -2
- metadata +39 -19
data/Rakefile
CHANGED
@@ -1,16 +1,19 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'bundler/gem_tasks'
|
4
4
|
require 'rake/testtask'
|
5
5
|
require 'rake/clean'
|
6
|
+
require 'rubocop/rake_task'
|
6
7
|
require 'dropcaster'
|
7
8
|
|
9
|
+
RuboCop::RakeTask.new
|
10
|
+
|
8
11
|
Rake::TestTask.new(:test) do |test|
|
9
12
|
test.libs << 'test'
|
10
13
|
test.pattern = 'test/**/test_*.rb'
|
11
14
|
end
|
12
15
|
|
13
|
-
task :
|
16
|
+
task default: %i[rubocop test]
|
14
17
|
|
15
18
|
namespace :web do
|
16
19
|
file 'website/index.markdown' do |f|
|
@@ -35,8 +38,8 @@ namespace :web do
|
|
35
38
|
end
|
36
39
|
CLOBBER << 'website/contributing.md'
|
37
40
|
|
38
|
-
desc
|
39
|
-
task :
|
41
|
+
desc 'Generate web page'
|
42
|
+
task generate: ['website/index.markdown', 'website/vision.markdown', 'website/contributing.md'] do
|
40
43
|
cd 'website' do
|
41
44
|
`bundle exec jekyll build`
|
42
45
|
end
|
data/Vagrantfile
CHANGED
data/bin/dropcaster
CHANGED
@@ -1,22 +1,24 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
|
4
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
4
5
|
|
5
6
|
require 'yaml'
|
7
|
+
require 'English'
|
6
8
|
|
7
|
-
help =
|
8
|
-
Dropcaster is a podcast feed generator for the command line.
|
9
|
+
help = <<~HELP
|
10
|
+
Dropcaster is a podcast feed generator for the command line.
|
9
11
|
|
10
|
-
Author: Nicolas E. Rabenau nerab@gmx.at
|
11
|
-
Homepage: http://nerab.github.io/dropcaster/
|
12
|
+
Author: Nicolas E. Rabenau nerab@gmx.at
|
13
|
+
Homepage: http://nerab.github.io/dropcaster/
|
12
14
|
|
13
|
-
Basic Usage:
|
15
|
+
Basic Usage:
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
dropcaster Prints a podcast feed document for the mp3 files in the current directory.
|
18
|
+
dropcaster [FILE]... Prints a podcast feed document for FILES
|
19
|
+
dropcaster [DIR]... Prints a podcast feed document for the mp3 files in DIR
|
18
20
|
|
19
|
-
Options:
|
21
|
+
Options:
|
20
22
|
HELP
|
21
23
|
|
22
24
|
def usage
|
@@ -25,105 +27,105 @@ end
|
|
25
27
|
|
26
28
|
require 'optparse'
|
27
29
|
require 'dropcaster'
|
30
|
+
require 'dropcaster/log_formatter'
|
28
31
|
|
29
|
-
options =
|
32
|
+
options = {}
|
30
33
|
options[:auto_detect_channel_file] = true
|
31
34
|
|
32
|
-
|
35
|
+
logger = Logger.new(STDERR).tap do |l|
|
36
|
+
l.level = Logger::WARN
|
37
|
+
l.formatter = Dropcaster::LogFormatter.new
|
38
|
+
end
|
39
|
+
|
40
|
+
# rubocop:disable Metrics/BlockLength
|
41
|
+
OptionParser.new do |opts|
|
33
42
|
opts.banner = help
|
34
43
|
|
35
|
-
opts.on(
|
36
|
-
|
37
|
-
Dropcaster.logger.formatter = Dropcaster::LogFormatter.new
|
38
|
-
Dropcaster.logger.level = Logger::INFO
|
44
|
+
opts.on('--verbose', 'Verbose mode - displays additional diagnostic information') do |file|
|
45
|
+
logger.level = Logger::INFO
|
39
46
|
end
|
40
47
|
|
41
|
-
opts.on(
|
42
|
-
|
43
|
-
Dropcaster.logger.formatter = Dropcaster::LogFormatter.new
|
44
|
-
Dropcaster.logger.level = Logger::DEBUG
|
48
|
+
opts.on('--trace', 'Verbose mode - displays additional diagnostic information') do |file|
|
49
|
+
logger.level = Logger::DEBUG
|
45
50
|
end
|
46
51
|
|
47
|
-
opts.on(
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
exit(1)
|
56
|
-
end
|
52
|
+
opts.on('--channel FILE', 'Read the channel definition from FILE instead of channel.yml in the current directory.') do |file|
|
53
|
+
logger.info "Reading channel definition from #{file}"
|
54
|
+
options = YAML.load_file(file).merge(options)
|
55
|
+
options[:auto_detect_channel_file] = false
|
56
|
+
rescue StandardError
|
57
|
+
logger.error "Could not load channel definition. #{$ERROR_INFO.message}"
|
58
|
+
logger.info $ERROR_INFO.backtrace
|
59
|
+
exit(1)
|
57
60
|
end
|
58
61
|
|
59
|
-
opts.on(
|
60
|
-
|
62
|
+
opts.on('--title STRING', "Use STRING as the channel's title. Overrides settings read from channel definition file.") do |title|
|
63
|
+
logger.info "Setting channel title to '#{title}' via command line"
|
61
64
|
options[:title] = title
|
62
65
|
end
|
63
66
|
|
64
|
-
opts.on(
|
65
|
-
|
67
|
+
opts.on('--subtitle STRING', "Use STRING as the channel's subtitle. Overrides settings read from channel definition file.") do |subtitle|
|
68
|
+
logger.info "Setting channel subtitle to '#{subtitle}' via command line"
|
66
69
|
options[:subtitle] = subtitle
|
67
70
|
end
|
68
71
|
|
69
|
-
opts.on(
|
70
|
-
|
72
|
+
opts.on('--url URL', "Use URL as the channel's url. Overrides settings read from channel definition file.") do |url|
|
73
|
+
logger.info "Setting channel URL to '#{url}' via command line"
|
71
74
|
options[:url] = url
|
72
75
|
end
|
73
76
|
|
74
|
-
opts.on(
|
75
|
-
|
77
|
+
opts.on('--description STRING', "Use STRING as the channel's description. Overrides settings read from channel definition file.") do |description|
|
78
|
+
logger.info "Setting channel description to '#{description}' via command line"
|
76
79
|
options[:description] = description
|
77
80
|
end
|
78
81
|
|
79
|
-
opts.on(
|
80
|
-
|
82
|
+
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|
|
83
|
+
logger.info "Setting enclosures base URL to '#{enclosures_url}' via command line"
|
81
84
|
options[:enclosures_url] = enclosures_url
|
82
85
|
end
|
83
86
|
|
84
|
-
opts.on(
|
85
|
-
|
87
|
+
opts.on('--image URL', "Use URL as the channel's image URL. Overrides settings read from channel definition file.") do |image_url|
|
88
|
+
logger.info "Setting image URL to '#{image_url}' via command line"
|
86
89
|
options[:image_url] = image_url
|
87
90
|
end
|
88
91
|
|
89
|
-
opts.on(
|
90
|
-
|
92
|
+
opts.on('--channel-template FILE', 'Use FILE as template for generating the channel feed. Overrides the default that comes with Dropcaster.') do |file|
|
93
|
+
logger.info "Using'#{file}' as channel template file"
|
91
94
|
options[:channel_template] = file
|
92
95
|
end
|
93
96
|
|
94
|
-
opts.on(
|
97
|
+
opts.on('--version', 'Display current version') do
|
95
98
|
puts "#{File.basename(__FILE__)} " + Dropcaster::VERSION
|
96
99
|
exit 0
|
97
100
|
end
|
98
|
-
end
|
101
|
+
end.parse!
|
102
|
+
# rubocop:enable Metrics/BlockLength
|
99
103
|
|
100
|
-
opts.parse!
|
101
104
|
sources = ARGV.blank? ? '.' : ARGV
|
102
105
|
|
103
106
|
if options[:auto_detect_channel_file]
|
104
107
|
# There was no channel file specified, so we try to load channel.yml from sources dir
|
105
108
|
channel_file = Dropcaster::ChannelFileLocator.locate(sources)
|
106
109
|
|
107
|
-
if File.
|
108
|
-
|
110
|
+
if File.exist?(channel_file)
|
111
|
+
logger.info "Auto-detected channel file at #{channel_file}"
|
109
112
|
options_from_yaml = YAML.load_file(channel_file)
|
110
113
|
options = options_from_yaml.merge(options)
|
111
114
|
else
|
112
|
-
|
113
|
-
|
115
|
+
logger.error "No channel file found at #{channel_file})"
|
116
|
+
logger.info usage
|
114
117
|
exit(1) # No way to continue without a channel definition
|
115
118
|
end
|
116
119
|
end
|
117
120
|
|
118
|
-
|
119
|
-
|
121
|
+
logger.info "Generating the channel with these options: #{options.inspect}"
|
120
122
|
begin
|
121
123
|
puts Dropcaster::Channel.new(sources, options).to_rss
|
122
|
-
rescue
|
123
|
-
|
124
|
-
|
125
|
-
|
124
|
+
rescue StandardError
|
125
|
+
logger.error $ERROR_INFO.message
|
126
|
+
$ERROR_INFO.backtrace.each do |line|
|
127
|
+
logger.debug(line)
|
126
128
|
end
|
127
|
-
|
129
|
+
logger.info usage
|
128
130
|
exit(1)
|
129
131
|
end
|
data/bin/lstags
CHANGED
@@ -1,51 +1,54 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
unless ARGV.size == 1
|
4
5
|
STDERR.puts "#{File.basename(__FILE__)}: Missing required parameter for the mp3 file to process"
|
5
6
|
exit(1)
|
6
7
|
end
|
7
8
|
|
8
|
-
require 'rubygems'
|
9
9
|
require 'mp3info'
|
10
|
-
|
11
10
|
begin
|
12
11
|
file_name = ARGV.first
|
13
12
|
|
14
13
|
puts "Listing tags for file: #{file_name}"
|
15
14
|
|
15
|
+
# rubocop:disable Metrics/BlockLength
|
16
|
+
# rubocop:disable Lint/EmptyWhen
|
16
17
|
Mp3Info.open(file_name) do |mp3info|
|
17
18
|
puts 'ID3v1 tags:'
|
18
|
-
mp3info.tag.
|
19
|
+
mp3info.tag.each_key { |key|
|
19
20
|
puts " #{key} => #{mp3info.tag.send(key)}"
|
20
21
|
}
|
21
22
|
puts
|
22
23
|
puts 'ID3v2 tags:'
|
23
|
-
mp3info.tag2.
|
24
|
+
mp3info.tag2.each_key { |key|
|
24
25
|
case key
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
26
|
+
when 'PIC'
|
27
|
+
when 'APIC'
|
28
|
+
# picture - do not print binary data
|
29
|
+
when 'ULT'
|
30
|
+
print ' ULT => '
|
31
|
+
block_counter = 0
|
32
|
+
mp3info.tag2.ULT.bytes { |b|
|
33
|
+
print sprintf('0x%02x ', b.to_i)
|
34
|
+
print b > 31 ? " '#{b.chr}' " : ' ' * 5
|
35
|
+
if (block_counter += 1) > 7 # display in blocks of 8 bytes
|
36
|
+
puts
|
37
|
+
print ' ' * 9
|
38
|
+
block_counter = 0
|
39
|
+
end
|
40
|
+
}
|
41
|
+
puts
|
42
|
+
else
|
43
|
+
puts " #{key} => #{mp3info.tag2.send(key)}"
|
43
44
|
end
|
44
45
|
}
|
45
46
|
end
|
46
|
-
|
47
|
+
# rubocop:enable Metrics/BlockLength
|
48
|
+
# rubocop:enable Lint/EmptyWhen
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
+
puts "Modification date: #{File.new(file_name).mtime}"
|
51
|
+
rescue StandardError
|
52
|
+
puts "Error: #{$ERROR_INFO.message}"
|
50
53
|
exit(1)
|
51
54
|
end
|
data/dropcaster.gemspec
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
lib = File.expand_path('
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
5
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
5
|
require 'dropcaster/version'
|
7
6
|
|
7
|
+
# rubocop:disable Metrics/BlockLength
|
8
8
|
Gem::Specification.new do |spec|
|
9
9
|
spec.name = 'dropcaster'
|
10
10
|
spec.version = Dropcaster::VERSION
|
@@ -20,22 +20,24 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
21
|
spec.require_paths = ['lib']
|
22
22
|
|
23
|
-
spec.add_dependency 'ruby-mp3info'
|
24
23
|
spec.add_dependency 'activesupport'
|
25
24
|
spec.add_dependency 'bundler'
|
25
|
+
spec.add_dependency 'nokogiri', '~> 1.8.2'
|
26
|
+
spec.add_dependency 'null-logger'
|
27
|
+
spec.add_dependency 'ruby-mp3info'
|
26
28
|
|
27
|
-
spec.add_development_dependency '
|
28
|
-
spec.add_development_dependency 'rake'
|
29
|
-
spec.add_development_dependency 'libxml-ruby'
|
30
|
-
spec.add_development_dependency 'guard-minitest'
|
29
|
+
spec.add_development_dependency 'github-pages'
|
31
30
|
spec.add_development_dependency 'guard-bundler'
|
31
|
+
spec.add_development_dependency 'guard-minitest'
|
32
32
|
spec.add_development_dependency 'libnotify'
|
33
|
-
spec.add_development_dependency '
|
34
|
-
spec.add_development_dependency '
|
33
|
+
spec.add_development_dependency 'libxml-ruby'
|
34
|
+
spec.add_development_dependency 'minitest'
|
35
|
+
spec.add_development_dependency 'octokit'
|
35
36
|
spec.add_development_dependency 'pry'
|
36
37
|
spec.add_development_dependency 'pry-byebug'
|
37
|
-
spec.add_development_dependency '
|
38
|
-
spec.add_development_dependency '
|
39
|
-
spec.add_development_dependency '
|
38
|
+
spec.add_development_dependency 'rake'
|
39
|
+
spec.add_development_dependency 'rb-fsevent'
|
40
|
+
spec.add_development_dependency 'rb-inotify'
|
40
41
|
spec.add_development_dependency 'rubocop'
|
41
42
|
end
|
43
|
+
# rubocop:enable Metrics/BlockLength
|
data/lib/dropcaster/channel.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'erb'
|
2
4
|
require 'uri'
|
5
|
+
require 'dropcaster/logging'
|
3
6
|
|
4
7
|
module Dropcaster
|
5
8
|
#
|
6
9
|
# Represents a podcast feed in the RSS 2.0 format
|
7
10
|
#
|
8
11
|
class Channel
|
12
|
+
include Logging
|
9
13
|
include ERB::Util # for h() in the ERB template
|
10
14
|
|
11
|
-
STORAGE_UNITS = %w
|
15
|
+
STORAGE_UNITS = %w[Byte KB MB GB TB].freeze
|
12
16
|
MAX_KEYWORD_COUNT = 12
|
13
17
|
|
14
18
|
# Instantiate a new Channel object. +sources+ must be present and can be a String or Array
|
@@ -23,13 +27,13 @@ module Dropcaster
|
|
23
27
|
#
|
24
28
|
def initialize(sources, attributes)
|
25
29
|
# Assert mandatory attributes
|
26
|
-
[
|
30
|
+
%i[title url description].each { |attr|
|
27
31
|
raise MissingAttributeError.new(attr) if attributes[attr].blank?
|
28
32
|
}
|
29
33
|
|
30
34
|
@attributes = attributes
|
31
35
|
@attributes[:explicit] = yes_no_or_input(attributes[:explicit])
|
32
|
-
@source_files =
|
36
|
+
@source_files = []
|
33
37
|
|
34
38
|
# if (sources.respond_to?(:each)) # array
|
35
39
|
if sources.is_a? Array
|
@@ -43,13 +47,13 @@ module Dropcaster
|
|
43
47
|
|
44
48
|
# If not absolute, prepend the image URL with the channel's base to make an absolute URL
|
45
49
|
unless @attributes[:image_url].blank? || @attributes[:image_url] =~ /^https?:/
|
46
|
-
|
50
|
+
logger.info("Channel image URL '#{@attributes[:image_url]}' is relative, so we prepend it with the channel URL '#{@attributes[:url]}'")
|
47
51
|
@attributes[:image_url] = (URI.parse(@attributes[:url]) + @attributes[:image_url]).to_s
|
48
52
|
end
|
49
53
|
|
50
54
|
# If enclosures_url is not given, take the channel URL as a base.
|
51
55
|
if @attributes[:enclosures_url].blank?
|
52
|
-
|
56
|
+
logger.info("No enclosure URL given, using the channel's enclosure URL")
|
53
57
|
@attributes[:enclosures_url] = @attributes[:url]
|
54
58
|
end
|
55
59
|
|
@@ -59,7 +63,7 @@ module Dropcaster
|
|
59
63
|
channel_template = @attributes[:channel_template] || File.join(File.dirname(__FILE__), '..', '..', 'templates', 'channel.rss.erb')
|
60
64
|
|
61
65
|
begin
|
62
|
-
@erb_template = ERB.new(IO.read(channel_template), 0,
|
66
|
+
@erb_template = ERB.new(IO.read(channel_template), 0, '%<>')
|
63
67
|
rescue Errno::ENOENT => e
|
64
68
|
raise TemplateNotFoundError.new(e.message)
|
65
69
|
end
|
@@ -78,26 +82,25 @@ module Dropcaster
|
|
78
82
|
# Returns all items (episodes) of this channel, ordered by newest-first.
|
79
83
|
#
|
80
84
|
def items
|
81
|
-
all_items =
|
82
|
-
@source_files.each{|src|
|
83
|
-
|
85
|
+
all_items = []
|
86
|
+
@source_files.each { |src|
|
84
87
|
item = Item.new(src)
|
85
88
|
|
86
|
-
|
89
|
+
logger.debug("Adding new item from file #{src}")
|
87
90
|
|
88
91
|
# set author and image_url from channel if empty
|
89
92
|
if item.artist.blank?
|
90
|
-
|
93
|
+
logger.info("#{src} has no artist, using the channel's author")
|
91
94
|
item.tag.artist = @attributes[:author]
|
92
95
|
end
|
93
96
|
|
94
97
|
if item.image_url.blank?
|
95
|
-
|
98
|
+
logger.info("#{src} has no image URL set, using the channel's image URL")
|
96
99
|
item.image_url = @attributes[:image_url]
|
97
100
|
end
|
98
101
|
|
99
102
|
# construct absolute URL, based on the channel's enclosures_url attribute
|
100
|
-
item.url =
|
103
|
+
item.url = URI.parse(enclosures_url) + item.file_path.each_filename.map { |p| url_encode(p) }.join('/')
|
101
104
|
|
102
105
|
# Warn if keyword count is larger than recommended
|
103
106
|
assert_keyword_count(item.keywords)
|
@@ -105,13 +108,13 @@ module Dropcaster
|
|
105
108
|
all_items << item
|
106
109
|
}
|
107
110
|
|
108
|
-
all_items.sort{|x, y| y.pub_date <=> x.pub_date}
|
111
|
+
all_items.sort { |x, y| y.pub_date <=> x.pub_date }
|
109
112
|
end
|
110
113
|
|
111
114
|
# from http://stackoverflow.com/questions/4136248
|
112
115
|
def humanize_time(secs)
|
113
|
-
[[60, :s], [60, :m], [24, :h], [1000, :d]].map{ |count, name|
|
114
|
-
if secs
|
116
|
+
[[60, :s], [60, :m], [24, :h], [1000, :d]].map { |count, name|
|
117
|
+
if secs.positive?
|
115
118
|
secs, n = secs.divmod(count)
|
116
119
|
"#{n.to_i}#{name}"
|
117
120
|
end
|
@@ -122,37 +125,43 @@ module Dropcaster
|
|
122
125
|
def humanize_size(number)
|
123
126
|
return nil if number.nil?
|
124
127
|
|
125
|
-
storage_units_format = '%n %u'
|
126
|
-
|
127
128
|
if number.to_i < 1024
|
128
129
|
unit = number > 1 ? 'Bytes' : 'Byte'
|
129
|
-
return storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
|
130
130
|
else
|
131
131
|
max_exp = STORAGE_UNITS.size - 1
|
132
132
|
number = Float(number)
|
133
133
|
exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
|
134
134
|
exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
|
135
|
-
number /= 1024
|
136
|
-
|
135
|
+
number /= 1024**exponent
|
137
136
|
unit = STORAGE_UNITS[exponent]
|
138
|
-
return storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
|
139
137
|
end
|
138
|
+
|
139
|
+
'%n %u'.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
|
140
140
|
end
|
141
141
|
|
142
|
-
#
|
142
|
+
#
|
143
|
+
# Delegate all unknown methods to @attributes
|
144
|
+
#
|
145
|
+
# rubocop:disable Style/MethodMissing
|
143
146
|
def method_missing(meth, *args)
|
144
147
|
m = meth.id2name
|
145
|
-
if
|
148
|
+
if /=$/.match?(m)
|
146
149
|
@attributes[m.chop.to_sym] = (args.length < 2 ? args[0] : args)
|
147
150
|
else
|
148
151
|
@attributes[m.to_sym]
|
149
152
|
end
|
150
153
|
end
|
154
|
+
# rubocop:enable Style/MethodMissing
|
155
|
+
|
156
|
+
def respond_to_missing?(meth, *)
|
157
|
+
/=$/.match?(meth.id2name) || super
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
151
161
|
|
152
|
-
private
|
153
162
|
def add_files(src)
|
154
163
|
if File.directory?(src)
|
155
|
-
@source_files.concat(Dir.glob(File.join(src, '*.mp3')))
|
164
|
+
@source_files.concat(Dir.glob(File.join(src, '*.mp3'), File::FNM_CASEFOLD))
|
156
165
|
else
|
157
166
|
@source_files << src
|
158
167
|
end
|
@@ -163,12 +172,12 @@ module Dropcaster
|
|
163
172
|
#
|
164
173
|
def yes_no_or_input(flag)
|
165
174
|
case flag
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
175
|
+
when nil
|
176
|
+
nil
|
177
|
+
when true
|
178
|
+
'Yes'
|
179
|
+
when false
|
180
|
+
'No'
|
172
181
|
else
|
173
182
|
flag
|
174
183
|
end
|
@@ -177,19 +186,21 @@ module Dropcaster
|
|
177
186
|
#
|
178
187
|
# http://snippets.dzone.com/posts/show/4578
|
179
188
|
#
|
180
|
-
def truncate(string, count
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
+
def truncate(string, count=30)
|
190
|
+
if string.length >= count
|
191
|
+
shortened = string[0, count]
|
192
|
+
splitted = shortened.split(/\s/)
|
193
|
+
words = splitted.length
|
194
|
+
splitted[0, words - 1].join(' ') + '...'
|
195
|
+
else
|
196
|
+
string
|
197
|
+
end
|
189
198
|
end
|
190
199
|
|
191
200
|
def assert_keyword_count(keywords)
|
192
|
-
|
201
|
+
if keywords && MAX_KEYWORD_COUNT < keywords.size
|
202
|
+
logger.info("The list of keywords has #{keywords.size} entries, which exceeds the recommended maximum of #{MAX_KEYWORD_COUNT}.")
|
203
|
+
end
|
193
204
|
end
|
194
205
|
end
|
195
206
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Dropcaster
|
2
4
|
#
|
3
5
|
# Encapsulates the strategy how to find the channel definition file
|
@@ -27,9 +29,9 @@ module Dropcaster
|
|
27
29
|
|
28
30
|
if sources.respond_to?(:at)
|
29
31
|
# 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
|
32
|
+
distinct_dirs = sources.collect { |source| dir_or_self(source) }.uniq
|
31
33
|
|
32
|
-
if
|
34
|
+
if distinct_dirs.size == 1
|
33
35
|
# If all are the in same directory, use that as source directory where channel.yml is expected.
|
34
36
|
channel_source_dir = distinct_dirs.first
|
35
37
|
else
|
@@ -40,11 +42,12 @@ module Dropcaster
|
|
40
42
|
# If a single file or directory is given, use that as source directory where channel.yml is expected.
|
41
43
|
channel_source_dir = dir_or_self(sources)
|
42
44
|
end
|
43
|
-
|
45
|
+
|
44
46
|
File.join(channel_source_dir, CHANNEL_YML)
|
45
47
|
end
|
46
|
-
|
47
|
-
|
48
|
+
|
49
|
+
private
|
50
|
+
|
48
51
|
def dir_or_self(source)
|
49
52
|
if File.directory?(source)
|
50
53
|
source
|
@@ -1,12 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'octokit'
|
2
4
|
|
3
5
|
module Dropcaster
|
4
|
-
|
5
|
-
|
6
|
+
class << self
|
7
|
+
def contributors
|
8
|
+
@octokit ||= if ENV.include?('GH_TOKEN')
|
9
|
+
Octokit::Client.new(access_token: ENV['GH_TOKEN'])
|
10
|
+
else
|
11
|
+
Octokit::Client.new
|
12
|
+
end
|
13
|
+
|
14
|
+
@octokit.contributors('nerab/dropcaster', true).
|
15
|
+
sort { |x, y| y.contributions <=> x.contributions }.
|
16
|
+
map { |c| "* #{contributor_summary(c)}" }.
|
17
|
+
join("\n")
|
18
|
+
end
|
6
19
|
|
7
|
-
|
8
|
-
|
20
|
+
def contributor_summary(contributor)
|
21
|
+
contributions = contributor.contributions
|
22
|
+
"#{contributor_link(contributor)} (#{contributions} contribution#{contributions == 1 ? '' : 's'})"
|
23
|
+
end
|
9
24
|
|
10
|
-
|
25
|
+
def contributor_link(contributor)
|
26
|
+
if contributor.type == 'Anonymous'
|
27
|
+
contributor.name.tr('[]', '()')
|
28
|
+
else
|
29
|
+
# rubocop:disable Style/RescueStandardError
|
30
|
+
begin
|
31
|
+
"[#{@octokit.user(contributor.login).name}](#{contributor.html_url})"
|
32
|
+
rescue
|
33
|
+
contributor.login
|
34
|
+
end
|
35
|
+
# rubocop:enable Style/RescueStandardError
|
36
|
+
end
|
37
|
+
end
|
11
38
|
end
|
12
39
|
end
|