dropcaster 0.0.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,16 +1,19 @@
1
- # encoding: utf-8
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 :default => :test
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 "Generate web page"
39
- task :generate => ['website/index.markdown', 'website/vision.markdown', 'website/contributing.md'] do
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # -*- mode: ruby -*-
2
4
  # vi: set ft=ruby :
3
5
 
data/bin/dropcaster CHANGED
@@ -1,22 +1,24 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
5
 
5
6
  require 'yaml'
7
+ require 'English'
6
8
 
7
- help = <<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
- dropcaster Prints a podcast feed document for the mp3 files in the current directory.
16
- dropcaster [FILE]... Prints a podcast feed document for FILES
17
- dropcaster [DIR]... Prints a podcast feed document for the mp3 files in DIR
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 = Hash.new
32
+ options = {}
30
33
  options[:auto_detect_channel_file] = true
31
34
 
32
- opts = OptionParser.new do |opts|
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("--verbose", "Verbose mode - displays additional diagnostic information") do |file|
36
- Dropcaster.logger = Logger.new(STDERR)
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("--trace", "Verbose mode - displays additional diagnostic information") do |file|
42
- Dropcaster.logger = Logger.new(STDERR)
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("--channel FILE", "Read the channel definition from FILE instead of channel.yml in the current directory.") do |file|
48
- begin
49
- Dropcaster.logger.info "Reading channel definition from #{file}"
50
- options = YAML.load_file(file).merge(options)
51
- options[:auto_detect_channel_file] = false
52
- rescue
53
- Dropcaster.logger.error "Could not load channel definition. #{$!.message}"
54
- Dropcaster.logger.info $!.backtrace
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("--title STRING", "Use STRING as the channel's title. Overrides settings read from channel definition file.") do |title|
60
- Dropcaster.logger.info "Setting channel title to '#{title}' via command line"
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("--subtitle STRING", "Use STRING as the channel's subtitle. Overrides settings read from channel definition file.") do |subtitle|
65
- Dropcaster.logger.info "Setting channel subtitle to '#{subtitle}' via command line"
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("--url URL", "Use URL as the channel's url. Overrides settings read from channel definition file.") do |url|
70
- Dropcaster.logger.info "Setting channel URL to '#{url}' via command line"
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("--description STRING", "Use STRING as the channel's description. Overrides settings read from channel definition file.") do |description|
75
- Dropcaster.logger.info "Setting channel description to '#{description}' via command line"
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("--enclosures URL", "Use URL as the base URL for the channel's enclosures. Overrides settings read from channel definition file.") do |enclosures_url|
80
- Dropcaster.logger.info "Setting enclosures base URL to '#{enclosures_url}' via command line"
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("--image URL", "Use URL as the channel's image URL. Overrides settings read from channel definition file.") do |image_url|
85
- Dropcaster.logger.info "Setting image URL to '#{image_url}' via command line"
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("--channel-template FILE", "Use FILE as template for generating the channel feed. Overrides the default that comes with Dropcaster.") do |file|
90
- Dropcaster.logger.info "Using'#{file}' as channel template file"
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("--version", "Display current version") do
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.exists?(channel_file)
108
- Dropcaster.logger.info "Auto-detected channel file at #{channel_file}"
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
- Dropcaster.logger.error "No channel file found at #{channel_file})"
113
- Dropcaster.logger.info usage
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
- Dropcaster.logger.info "Generating the channel with these options: #{options.inspect}"
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
- Dropcaster.logger.error $!.message
124
- $!.backtrace.each do |line|
125
- Dropcaster.logger.debug(line)
124
+ rescue StandardError
125
+ logger.error $ERROR_INFO.message
126
+ $ERROR_INFO.backtrace.each do |line|
127
+ logger.debug(line)
126
128
  end
127
- Dropcaster.logger.info usage
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.keys.each{|key|
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.keys.each{|key|
24
+ mp3info.tag2.each_key { |key|
24
25
  case key
25
- when 'PIC'
26
- when 'APIC'
27
- # picture - do not print binary data
28
- when 'ULT'
29
- print " ULT => "
30
- block_counter = 0
31
- mp3info.tag2.ULT.bytes{|b|
32
- print "0x%02x " % b.to_i
33
- print b > 31 ? " '#{b.chr}' " : " " * 5
34
- if (block_counter += 1) > 7 # display in blocks of 8 bytes
35
- puts
36
- print " " * 9
37
- block_counter = 0
38
- end
39
- }
40
- puts
41
- else
42
- puts " #{key} => #{mp3info.tag2.send(key)}"
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
- puts "Modification date: #{File.new(file_name).mtime}"
47
+ # rubocop:enable Metrics/BlockLength
48
+ # rubocop:enable Lint/EmptyWhen
47
49
 
48
- rescue
49
- puts "Error: #{$!.message}"
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('../lib', __FILE__)
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 'minitest'
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 'rb-inotify'
34
- spec.add_development_dependency 'rb-fsevent'
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 'rb-readline'
38
- spec.add_development_dependency 'github-pages'
39
- spec.add_development_dependency 'octokit'
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
@@ -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(Byte KB MB GB TB)
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
- [:title, :url, :description].each{|attr|
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 = Array.new
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
- Dropcaster.logger.info("Channel image URL '#{@attributes[:image_url]}' is relative, so we prepend it with the channel URL '#{@attributes[:url]}'")
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
- Dropcaster.logger.info("No enclosure URL given, using the channel's enclosure URL")
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 = Array.new
82
- @source_files.each{|src|
83
-
85
+ all_items = []
86
+ @source_files.each { |src|
84
87
  item = Item.new(src)
85
88
 
86
- Dropcaster.logger.debug("Adding new item from file #{src}")
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
- Dropcaster.logger.info("#{src} has no artist, using the channel's author")
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
- Dropcaster.logger.info("#{src} has no image URL set, using the channel's image URL")
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 = (URI.parse(enclosures_url) + URI.encode(item.file_name))
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 > 0
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 ** exponent
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
- # delegate all unknown methods to @attributes
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 /=$/ =~ m
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
- when nil
167
- nil
168
- when true
169
- 'Yes'
170
- when false
171
- 'No'
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 = 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
+ 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
- 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
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 1 == distinct_dirs.size
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
- private
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
- def self.contributors
5
- cbtors = Octokit.contributors('nerab/dropcaster', true)
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
- cbtors.sort!{|x,y| y.contributions <=> x.contributions }
8
- cbtors.map!{|c| "* [#{Octokit.user(c.login).name}](#{c.html_url}) (#{c.contributions} contributions)"}
20
+ def contributor_summary(contributor)
21
+ contributions = contributor.contributions
22
+ "#{contributor_link(contributor)} (#{contributions} contribution#{contributions == 1 ? '' : 's'})"
23
+ end
9
24
 
10
- cbtors.join("\n")
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