Skip to content

Commit

Permalink
Add progressbar and create CLI class
Browse files Browse the repository at this point in the history
  • Loading branch information
ustasb committed Sep 15, 2013
1 parent 40f33b0 commit b49f5bf
Show file tree
Hide file tree
Showing 43 changed files with 42,692 additions and 100 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -2,3 +2,5 @@
*.gem
doc/*
.yardoc
.rspec
Gemfile.lock
3 changes: 3 additions & 0 deletions Gemfile
@@ -0,0 +1,3 @@
source "https://rubygems.org"

gemspec
86 changes: 4 additions & 82 deletions bin/pandata
@@ -1,86 +1,8 @@
#!/usr/bin/env ruby

require_relative '../lib/pandata'
require_relative '../lib/pandata/argv_parser'
require_relative '../lib/pandata/data_formatter'
require_relative '../lib/pandata/cli'

options = Pandata::ArgvParser.parse(ARGV)

output_file = options[:output_file]
if output_file
File.delete(output_file) if File.exists?(output_file)

Object.send(:define_method, :write) do |string|
File.open(output_file, 'a') do |file|
file.puts string
end
end
else
def write(string)
puts string
end
end

if ARGV.empty?
# Print command-line usage help.
puts options[:opts]
exit
end

scraper = Pandata::Scraper.get(options[:user_id])
formatter = Pandata::DataFormatter.new

# If scraper is an array, a Pandora user could not be found with certainty.
# In this case, scraper will contain webnames similar to options[:user_id].
if scraper.kind_of?(Array)
puts "No exact match for '#{options[:user_id]}'."

unless scraper.empty?
puts "\nWebname results for '#{options[:user_id]}':"
puts formatter.list(scraper)
end

exit
end

scraper_data = {}
options[:data_to_get].each do |data_type|
if /(bookmark|like)e?d_(.*)/ =~ data_type
method = $1 << 's' # 'likes' or 'bookmarks'
argument = $2.to_sym # :tracks, :artists, :stations or :albums
scraper_data[data_type] = scraper.public_send(method, argument)
else
scraper_data[data_type] = scraper.public_send(data_type)
end
end

if options[:return_as_json]
require 'json'
write JSON.generate(scraper_data)
exit
end

scraper_data.each do |key, value|
# Capitalize each word in the key symbol.
# e.g. :liked_tracks becomes 'Liked Tracks:'
title = key.to_s.split('_').map(&:capitalize).join(' ') << ':'

if value.empty?
output = ' ** No Data **'
else
output = case key
when /playing_station|recent_activity/
formatter.list(value)
when /liked_tracks|bookmarked_tracks/
formatter.tracks(value)
when /liked_artists|bookmarked_artists|stations|liked_stations/
formatter.sort_list(value)
when :liked_albums
formatter.albums(value)
when /following|followers/
formatter.followx(value)
end
end

write "#{ title }\n#{ output }"
begin
Pandata::CLI.scrape(ARGV)
rescue Pandata::PandataError
end
6 changes: 4 additions & 2 deletions lib/pandata.rb
Expand Up @@ -6,10 +6,12 @@
require_relative 'pandata/scraper'

module Pandata
class PandataError < StandardError; end

module Version
MAJOR = 0
MINOR = 1
PATCH = 2
MINOR = 2
PATCH = 0
BUILD = nil

STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
Expand Down
10 changes: 5 additions & 5 deletions lib/pandata/argv_parser.rb
Expand Up @@ -15,6 +15,8 @@ class ArgvParser
# - :output_file [String]
# - :data_to_get [Array]
# - :get_all_data [Boolean]
# - :help [Boolean]
# - :version [Boolean]
# - :return_as_json [Boolean]
def self.parse(argv)
options = { data_to_get: [] }
Expand Down Expand Up @@ -90,20 +92,18 @@ def self.parse(argv)
end

opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
options[:help] = true
end

opts.on_tail("--version", "Show version") do
puts Pandata::Version::STRING
exit
options[:version] = true
end
end

options[:opts].parse(argv)

# User ID is the first argument.
options[:user_id] = argv.shift
options[:user_id] = argv[0]

if get_all_data
options[:data_to_get] = [
Expand Down
144 changes: 144 additions & 0 deletions lib/pandata/cli.rb
@@ -0,0 +1,144 @@
require 'json'
require 'ruby-progressbar'
require_relative '../pandata'
require_relative 'argv_parser'
require_relative 'data_formatter'

module Pandata

# Pandata command-line interface
class CLI

def self.scrape(argv)
options = Pandata::ArgvParser.parse(argv)

if argv.empty? || options[:help]
puts options[:opts].to_s # Log usage information
elsif options[:version]
puts Pandata::Version::STRING
else
new(options).download_and_output
end
end

def initialize(options)
@data_to_get = options[:data_to_get]
@output_file = options[:output_file]
@return_as_json = options[:return_as_json]

@scraper = scraper_for(options[:user_id])
@scraper.download_cb = method(:update_progress)
end

def update_progress(num_data)
progressbar.progress += num_data
end

def download_and_output
output_data format_data(download_data, @return_as_json)
end

private

def progressbar
@progressbar ||= ProgressBar.create(
title: 'Data Downloaded',
format: '%t: %c',
total: nil
)
end

def formatter
@formatter ||= DataFormatter.new
end

def log(msg)
puts msg
end

# Writes the data to STDOUT or a file.
# @param formatted_data [String]
def output_data(formatted_data)
@progressbar.stop if @progressbar

if @output_file
File.write(@output_file, formatted_data)
else
log formatted_data
end
end

# Formats data as a string list or JSON.
# @param data [Hash]
# @param json [Boolean]
# @return [String]
def format_data(data, json = false)
if json
JSON.generate(data)
else
data.map do |category, cat_data|
# Capitalize each word in the category symbol.
# e.g. :liked_tracks becomes 'Liked Tracks'
title = category.to_s.split('_').map(&:capitalize).join(' ')

output = if cat_data.empty?
" ** No Data **\n"
else
case category
when /playing_station|recent_activity/
formatter.list(cat_data)
when /liked_tracks|bookmarked_tracks/
formatter.tracks(cat_data)
when /liked_artists|bookmarked_artists|stations|liked_stations/
formatter.sort_list(cat_data)
when :liked_albums
formatter.albums(cat_data)
when /following|followers/
formatter.followx(cat_data)
end
end

"#{title}:\n#{output}"
end.join
end
end

# Downloads the user's desired data.
# @return [Hash]
def download_data
scraper_data = {}

@data_to_get.each do |data_category|
if /(bookmark|like)e?d_(.*)/ =~ data_category
method = $1 << 's' # 'likes' or 'bookmarks'
argument = $2.to_sym # :tracks, :artists, :stations or :albums
scraper_data[data_category] = @scraper.public_send(method, argument)
else
scraper_data[data_category] = @scraper.public_send(data_category)
end
end

scraper_data
end

# Returns a scraper for the user's id.
# @param user_id [String] webname or email
# @return [Pandata::Scraper]
def scraper_for(user_id)
scraper = Pandata::Scraper.get(user_id)

if scraper.kind_of?(Array)
log "No exact match for '#{user_id}'."

unless scraper.empty?
log "\nWebname results for '#{user_id}':\n#{formatter.list(scraper)}"
end

raise PandataError, "Could not create a scraper for '#{user_id}'."
end

scraper
end

end
end
1 change: 0 additions & 1 deletion lib/pandata/downloader.rb
Expand Up @@ -2,7 +2,6 @@
require 'open-uri'

module Pandata
class PandataError < StandardError; end

# Retrieves data from Pandora.com and handles errors.
class Downloader
Expand Down
5 changes: 5 additions & 0 deletions lib/pandata/scraper.rb
Expand Up @@ -12,6 +12,9 @@ class Scraper
# the user ties a new email address to their Pandora account.
attr_reader :webname

# A Proc that gets called after some data has been downloaded.
attr_accessor :download_cb

# If possible, get a Scraper instance for the user_id otherwise return
# an array of similar webnames.
# @param user_id [String] email or webname
Expand Down Expand Up @@ -134,6 +137,8 @@ def scrape_for(data_type, parser_method)
results.push(new_data)
end

@download_cb[new_data.size] if @download_cb

get_url(data_type, next_data_indices) if next_data_indices
end

Expand Down
5 changes: 4 additions & 1 deletion pandata.gemspec
Expand Up @@ -14,6 +14,9 @@ Gem::Specification.new do |s|
s.extra_rdoc_files = %w[LICENSE README.md]
s.executables << 'pandata'
s.add_runtime_dependency 'nokogiri', '~> 1.5.6'
s.add_development_dependency 'rspec', '~> 2.12.2'
s.add_runtime_dependency 'ruby-progressbar', '~> 1.2.0'
s.add_development_dependency 'rspec', '~> 2.14.0'
s.add_development_dependency 'vcr', '~> 2.5.0'
s.add_development_dependency 'webmock', '~> 1.13.0'
s.add_development_dependency 'yard', '~> 0.8.5'
end
3 changes: 2 additions & 1 deletion spec/argv_parser_spec.rb
@@ -1,4 +1,5 @@
require_relative '../lib/pandata/argv_parser'
require 'spec_helper'
require 'pandata/argv_parser'

describe Pandata::ArgvParser do
it 'does not allow instances of itself' do
Expand Down

0 comments on commit b49f5bf

Please sign in to comment.