dropcaster 0.0.8 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|