audio-feed-manager 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +27 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Guardfile +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +86 -0
- data/Rakefile +2 -0
- data/audio-feed-manager.gemspec +32 -0
- data/bin/afm +5 -0
- data/lib/audio-feed-manager.rb +1 -0
- data/lib/audio_feed_manager/application.rb +39 -0
- data/lib/audio_feed_manager/audio_file.rb +9 -0
- data/lib/audio_feed_manager/audio_file_adder.rb +14 -0
- data/lib/audio_feed_manager/audio_file_repository.rb +42 -0
- data/lib/audio_feed_manager/cli/add_audio_file.rb +39 -0
- data/lib/audio_feed_manager/cli/add_feed.rb +20 -0
- data/lib/audio_feed_manager/cli/arguments.rb +27 -0
- data/lib/audio_feed_manager/cli/arguments_parser.rb +67 -0
- data/lib/audio_feed_manager/cli/arguments_specification.rb +103 -0
- data/lib/audio_feed_manager/cli/command.rb +23 -0
- data/lib/audio_feed_manager/cli/commands_list.rb +120 -0
- data/lib/audio_feed_manager/cli/get_feed_url.rb +20 -0
- data/lib/audio_feed_manager/cli/initialize_project.rb +44 -0
- data/lib/audio_feed_manager/cli/list_feeds.rb +38 -0
- data/lib/audio_feed_manager/cli/publish.rb +21 -0
- data/lib/audio_feed_manager/cli/show_feed.rb +44 -0
- data/lib/audio_feed_manager/cli/update_feed_rss.rb +19 -0
- data/lib/audio_feed_manager/cli.rb +19 -0
- data/lib/audio_feed_manager/config.rb +5 -0
- data/lib/audio_feed_manager/config_repository.rb +14 -0
- data/lib/audio_feed_manager/console.rb +28 -0
- data/lib/audio_feed_manager/directory_lister.rb +9 -0
- data/lib/audio_feed_manager/error.rb +4 -0
- data/lib/audio_feed_manager/feed.rb +9 -0
- data/lib/audio_feed_manager/feed_items_repository.rb +30 -0
- data/lib/audio_feed_manager/feed_repository.rb +32 -0
- data/lib/audio_feed_manager/feed_syncer.rb +20 -0
- data/lib/audio_feed_manager/file_storage.rb +20 -0
- data/lib/audio_feed_manager/id3_tags.rb +14 -0
- data/lib/audio_feed_manager/new_model_creator.rb +15 -0
- data/lib/audio_feed_manager/rss_generator.rb +10 -0
- data/lib/audio_feed_manager/rss_repository.rb +36 -0
- data/lib/audio_feed_manager/s3_gateway.rb +39 -0
- data/lib/audio_feed_manager/secret_token_generator.rb +9 -0
- data/lib/audio_feed_manager/stop_application.rb +4 -0
- data/lib/audio_feed_manager/support/getter_setter_method.rb +18 -0
- data/lib/audio_feed_manager/support/hash_constructor.rb +9 -0
- data/lib/audio_feed_manager/tags.rb +9 -0
- data/lib/audio_feed_manager/unique_id_generator.rb +19 -0
- data/lib/audio_feed_manager/url_maker.rb +13 -0
- data/lib/audio_feed_manager/version.rb +3 -0
- data/lib/audio_feed_manager.rb +57 -0
- data/spec/audio_feed_manager/audio_file_adder_spec.rb +49 -0
- data/spec/audio_feed_manager/audio_file_repository_spec.rb +58 -0
- data/spec/audio_feed_manager/cli/add_audio_file_spec.rb +44 -0
- data/spec/audio_feed_manager/cli/add_feed_spec.rb +27 -0
- data/spec/audio_feed_manager/cli/arguments_parser_spec.rb +85 -0
- data/spec/audio_feed_manager/cli/arguments_specification_spec.rb +51 -0
- data/spec/audio_feed_manager/cli/commands_list_spec.rb +91 -0
- data/spec/audio_feed_manager/cli/get_feed_url_spec.rb +26 -0
- data/spec/audio_feed_manager/cli/initialize_project_spec.rb +47 -0
- data/spec/audio_feed_manager/cli/list_feeds_spec.rb +27 -0
- data/spec/audio_feed_manager/cli/show_feed_spec.rb +41 -0
- data/spec/audio_feed_manager/cli/update_feed_rss_spec.rb +26 -0
- data/spec/audio_feed_manager/cli_spec.rb +42 -0
- data/spec/audio_feed_manager/directory_lister_spec.rb +23 -0
- data/spec/audio_feed_manager/feed_items_repository_spec.rb +34 -0
- data/spec/audio_feed_manager/feeds_repository_spec.rb +40 -0
- data/spec/audio_feed_manager/file_storage_spec.rb +22 -0
- data/spec/audio_feed_manager/id3_tags_spec.rb +26 -0
- data/spec/audio_feed_manager/new_model_creator_spec.rb +44 -0
- data/spec/audio_feed_manager/rss_repository_spec.rb +34 -0
- data/spec/audio_feed_manager/s3_gateway_spec.rb +51 -0
- data/spec/audio_feed_manager/secret_token_generator_spec.rb +13 -0
- data/spec/audio_feed_manager/support/getter_setter_method_spec.rb +77 -0
- data/spec/audio_feed_manager/unique_id_generator_spec.rb +23 -0
- data/spec/fixtures/test.mp3 +0 -0
- data/spec/fixtures/test.txt +1 -0
- data/spec/help_spec.rb +64 -0
- data/spec/initializing_spec.rb +12 -0
- data/spec/managing_audio_files_spec.rb +21 -0
- data/spec/managing_feeds_spec.rb +51 -0
- data/spec/publishing_spec.rb +24 -0
- data/spec/s3_credentials.yml.sample +5 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/builders.rb +26 -0
- data/spec/support/fake_console.rb +13 -0
- data/spec/support/fakes.rb +5 -0
- data/spec/support/integration_helpers.rb +38 -0
- metadata +297 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2ed5834bc31327d2e1dfe53b430279a4fda081dd
|
4
|
+
data.tar.gz: 4e40fb4b0950e64972a3f5f87f28520ca0372e26
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1a86780dfbdd3f331623546d7d6c496a3d62056922ed6945693cd98b7e0bffa3c810c0d1a9b99afec86a5ddb947138288d23815cb48f842187cfa9e8cdf97b27
|
7
|
+
data.tar.gz: c35007e7d9f6ce772f543a706799fbb474aa4c59e47b4106fcdd46ea22a1e2801872230b65d7172d5c784b0e92983ab42d1f49e36b67e8dbbed9c37f2ab414d4
|
data/.gitignore
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.ruby-version
|
24
|
+
.ruby-gemset
|
25
|
+
tags
|
26
|
+
gems.tags
|
27
|
+
spec/s3_credentials.yml
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
2
|
+
require "guard/rspec/dsl"
|
3
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
4
|
+
|
5
|
+
rspec = dsl.rspec
|
6
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
7
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
8
|
+
watch(rspec.spec_files)
|
9
|
+
|
10
|
+
ruby = dsl.ruby
|
11
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
12
|
+
end
|
13
|
+
|
14
|
+
guard 'ctags-bundler', :binary => 'ctags-exuberant', :src_path => ["lib", "spec"] do
|
15
|
+
watch(/^(app|lib|spec)\/.*\.rb$/)
|
16
|
+
watch('Gemfile.lock')
|
17
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Adam Pohorecki
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# AudioFeedManager
|
2
|
+
|
3
|
+
A tool for managing RSS podcast feeds.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Install the gem from rubygems:
|
8
|
+
|
9
|
+
$ gem install audio-feed-manager
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
In order to use the app, you will need to perform a couple of steps.
|
14
|
+
|
15
|
+
First, you will need to initialize a project (a directory containing the feeds and audio files).
|
16
|
+
Then, you will need to add some feeds and some audio files to the feeds.
|
17
|
+
Once you're ready, you can deploy the feeds to S3.
|
18
|
+
|
19
|
+
### Initial set up
|
20
|
+
|
21
|
+
In order to create a directory for the feeds and audio files, you will need to call:
|
22
|
+
|
23
|
+
$ afm init my-project
|
24
|
+
|
25
|
+
Where `my-project` is a name of the directory you will want to store the files in.
|
26
|
+
|
27
|
+
The command will ask you for some additional information, like the S3 credentials to use.
|
28
|
+
|
29
|
+
|
30
|
+
### Adding feeds
|
31
|
+
|
32
|
+
In order to add a feed execute the following command (while in a project directory):
|
33
|
+
|
34
|
+
$ afm feeds add "My Feed Title"
|
35
|
+
|
36
|
+
This will create a file `feeds/my-feed-title`. This file name is also an identifier of the feed for later use.
|
37
|
+
|
38
|
+
You can see the list of created feeds by issuing the following command:
|
39
|
+
|
40
|
+
$ afm feeds list
|
41
|
+
|
42
|
+
### Adding audio files
|
43
|
+
|
44
|
+
Once you have created your first feed, you can start adding audio files to it:
|
45
|
+
|
46
|
+
$ afm audio add feeds/my-feed-title /path/to/file.mp3
|
47
|
+
|
48
|
+
This command will read the title and author from ID3 tags of the MP3 file. If those are not present, it will ask you to provide that information.
|
49
|
+
|
50
|
+
### Publishing the feeds
|
51
|
+
|
52
|
+
You can publish all of the feeds to S3 using the following command:
|
53
|
+
|
54
|
+
$ afm publish
|
55
|
+
|
56
|
+
This will upload all of the MP3 and .xml files to S3.
|
57
|
+
|
58
|
+
### Getting an URL for a feed
|
59
|
+
|
60
|
+
In order to share a URL to a feed with other people, you can use:
|
61
|
+
|
62
|
+
$ afm feeds get-url feeds/my-feed-title
|
63
|
+
|
64
|
+
You can see the URL and other feed details by issuing the following command:
|
65
|
+
|
66
|
+
$ afm feeds show feeds/my-feed-title
|
67
|
+
|
68
|
+
### Re-generating RSS file
|
69
|
+
|
70
|
+
All of the information about feeds and audio files is stored in plain-text files, so you can use your favorite text editor to change the initially entered details.
|
71
|
+
|
72
|
+
For example, to change the order of items in a feed, you can simply edit the items file:
|
73
|
+
|
74
|
+
vim items/my-feed-title
|
75
|
+
|
76
|
+
Once you are done with the editing, you will need to re-generate the RSS file for the feed you changed:
|
77
|
+
|
78
|
+
$ afm feeds update-rss feeds/my-feed-title
|
79
|
+
|
80
|
+
## Contributing
|
81
|
+
|
82
|
+
1. Fork it ( https://github.com/[my-github-username]/audio-feed-manager/fork )
|
83
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
84
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
85
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
86
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'audio_feed_manager/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "audio-feed-manager"
|
8
|
+
spec.version = AudioFeedManager::VERSION
|
9
|
+
spec.authors = ["Adam Pohorecki"]
|
10
|
+
spec.email = ["adam@gunpowderlabs.com"]
|
11
|
+
spec.summary = %q{A CLI tool for managing audio RSS feeds}
|
12
|
+
spec.description = %q{A CLI tool for managing audio RSS feeds}
|
13
|
+
spec.homepage = "https://github.com/gunpowderlabs/audio-feed-manager"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "bogus"
|
26
|
+
spec.add_development_dependency "guard-rspec"
|
27
|
+
spec.add_development_dependency "guard-ctags-bundler"
|
28
|
+
|
29
|
+
spec.add_dependency "dependor"
|
30
|
+
spec.add_dependency "id3tag"
|
31
|
+
spec.add_dependency "aws-sdk", "~> 2"
|
32
|
+
end
|
data/bin/afm
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "audio_feed_manager"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module AudioFeedManager
|
2
|
+
class Application
|
3
|
+
include Dependor::AutoInject
|
4
|
+
look_in_modules AudioFeedManager
|
5
|
+
|
6
|
+
attr_reader :directory, :stdin, :stderr, :stdout
|
7
|
+
|
8
|
+
def initialize(directory:, stdin:, stderr:, stdout:)
|
9
|
+
@directory = Pathname.new(File.expand_path(directory))
|
10
|
+
@stdin = stdin
|
11
|
+
@stderr = stderr
|
12
|
+
@stdout = stdout
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.let(name, &block)
|
16
|
+
define_method(name) do
|
17
|
+
@object_cache ||= {}
|
18
|
+
@object_cache[name] ||= instance_exec(&block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:cli) { inject(CLI) }
|
23
|
+
let(:help) { commands_list }
|
24
|
+
let(:feeds_prefix) { "feeds" }
|
25
|
+
let(:feeds_directory) { directory.join(feeds_prefix) }
|
26
|
+
let(:audio_files_prefix) { "audio_files" }
|
27
|
+
let(:audio_files_directory) { directory.join(audio_files_prefix) }
|
28
|
+
let(:rss_files_prefix) { "rss" }
|
29
|
+
let(:rss_files_directory) { directory.join(rss_files_prefix) }
|
30
|
+
let(:data_files_prefix) { "data" }
|
31
|
+
let(:data_files_directory) { directory.join(data_files_prefix) }
|
32
|
+
let(:items_prefix) { "items" }
|
33
|
+
let(:items_directory) { directory.join(items_prefix) }
|
34
|
+
let(:storage) { file_storage }
|
35
|
+
let(:rss_repository) { inject(RSSRepository) }
|
36
|
+
let(:config_file) { directory.join("config.yml") }
|
37
|
+
let(:config) { config_repository.load(config_file) }
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module AudioFeedManager
|
2
|
+
class AudioFileAdder
|
3
|
+
takes :id3_tags, :audio_file_repository, :feed_items_repository
|
4
|
+
|
5
|
+
def call(feed, file_name, title: ->(){}, author: ->(){})
|
6
|
+
tags = id3_tags.read(file_name)
|
7
|
+
audio_file = AudioFile.new(title: tags.title || title.call,
|
8
|
+
author: tags.artist || author.call)
|
9
|
+
audio_file = audio_file_repository.add(audio_file, file_name)
|
10
|
+
feed_items_repository.add(feed, audio_file)
|
11
|
+
audio_file
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module AudioFeedManager
|
4
|
+
class AudioFileRepository
|
5
|
+
takes :new_model_creator, :audio_files_directory, :audio_files_prefix, :data_files_directory, :storage
|
6
|
+
|
7
|
+
def add(audio_file, file_name)
|
8
|
+
add_file_information(audio_file, file_name)
|
9
|
+
audio_file = new_model_creator.create(audio_file,
|
10
|
+
directory: audio_files_directory,
|
11
|
+
prefix: audio_files_prefix)
|
12
|
+
copy_file(file_name, audio_file.file_name)
|
13
|
+
|
14
|
+
audio_file
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetch(id)
|
18
|
+
attrs = storage.read(id)
|
19
|
+
AudioFile.new(attrs)
|
20
|
+
rescue FileNotFound
|
21
|
+
raise AudioFileNotFound.for_id(id)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def add_file_information(audio_file, file_name)
|
27
|
+
audio_file.extension = File.extname(file_name)
|
28
|
+
audio_file.size = File.size(file_name)
|
29
|
+
audio_file.content_type = `file --mime-type --brief #{file_name}`.chomp
|
30
|
+
end
|
31
|
+
|
32
|
+
def copy_file(file, new_name)
|
33
|
+
FileUtils.cp(file, data_files_directory.join(new_name))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class AudioFileNotFound < Error
|
38
|
+
def self.for_id(id)
|
39
|
+
new("AudioFile by id: #{id} was not found!")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module AudioFeedManager
|
2
|
+
class AddAudioFile < Command
|
3
|
+
arguments do |s|
|
4
|
+
s.name "audio add"
|
5
|
+
s.description "Add a mp3 file to the specified feed"
|
6
|
+
|
7
|
+
s.argument :feed, required: true, description: "ID of the feed to add the file to, ex. feeds/hello-world"
|
8
|
+
s.varargs :files, minimum: 1, description: "Files to add"
|
9
|
+
end
|
10
|
+
|
11
|
+
takes :feed_repository, :audio_file_adder, :rss_generator, :console, :directory
|
12
|
+
|
13
|
+
def run(feed:, files:)
|
14
|
+
feed = feed_repository.fetch(feed)
|
15
|
+
|
16
|
+
files.each do |file|
|
17
|
+
add_file(feed, file)
|
18
|
+
end
|
19
|
+
|
20
|
+
rss_generator.generate(feed)
|
21
|
+
rescue FeedNotFound => e
|
22
|
+
console.die(e)
|
23
|
+
rescue SystemCallError => e
|
24
|
+
console.die(e)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def add_file(feed, file)
|
30
|
+
file_name = Pathname.new(directory.join(file))
|
31
|
+
|
32
|
+
console.info("Adding #{file_name} to #{feed.id}")
|
33
|
+
|
34
|
+
audio_file_adder.call feed, file_name,
|
35
|
+
title: ->() { console.ask("No title tag found. Please provide title", default: File.basename(file_name)) },
|
36
|
+
author: ->() { console.ask("No artist tag found. Please provide author") }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module AudioFeedManager
|
2
|
+
class AddFeed < Command
|
3
|
+
arguments do |s|
|
4
|
+
s.name 'feeds add'
|
5
|
+
s.description 'Creates a new feed with a given title'
|
6
|
+
|
7
|
+
s.argument :title, required: true, description: "Title of the feed"
|
8
|
+
end
|
9
|
+
|
10
|
+
takes :feed_repository, :rss_generator, :console
|
11
|
+
|
12
|
+
def run(title:)
|
13
|
+
description = console.ask("Description", default: title)
|
14
|
+
feed = Feed.new(title: title, description: description)
|
15
|
+
feed = feed_repository.add(feed)
|
16
|
+
rss_generator.generate(feed)
|
17
|
+
console.info("Feed added. You may now refer to it as #{feed.id}.")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module AudioFeedManager
|
2
|
+
class Arguments
|
3
|
+
def self.blank
|
4
|
+
new
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.from_hash(hash)
|
8
|
+
new(hash)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(arguments = {})
|
12
|
+
@arguments = arguments
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_argument(name, value)
|
16
|
+
arguments[name] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hash
|
20
|
+
arguments
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :arguments
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module AudioFeedManager
|
2
|
+
class ArgumentsParser
|
3
|
+
def parse(specification, arguments)
|
4
|
+
result = Arguments.new
|
5
|
+
|
6
|
+
result, arguments = parse_required_arguments(result, arguments, specification)
|
7
|
+
result, arguments = parse_optional_arguments(result, arguments, specification)
|
8
|
+
result, arguments = parse_varargs(result, arguments, specification)
|
9
|
+
|
10
|
+
if arguments.any?
|
11
|
+
raise InvalidArguments, "Provided #{arguments.size} extra arguments!"
|
12
|
+
end
|
13
|
+
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def parse_required_arguments(result, arguments, specification)
|
20
|
+
if arguments.size < specification.required_arguments.size
|
21
|
+
raise InvalidArguments,
|
22
|
+
"Provided #{arguments.size} arguments, but #{specification.required_arguments.size} are required."
|
23
|
+
end
|
24
|
+
|
25
|
+
extract_arguments(result, arguments, specification.required_arguments)
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_varargs(result, arguments, specification)
|
29
|
+
return [result, arguments] unless specification.varargs_arguments.any?
|
30
|
+
|
31
|
+
if arguments.size < specification.minimum_varags_count
|
32
|
+
raise InvalidArguments,
|
33
|
+
"Provided #{arguments.size} for variable-length arguments, but #{specification.minimum_varags_count} are required."
|
34
|
+
end
|
35
|
+
|
36
|
+
specs = specification.varargs_arguments
|
37
|
+
arguments = arguments.dup
|
38
|
+
|
39
|
+
specs[0..-2].each do |arg|
|
40
|
+
value = arguments.shift(arg.minimum)
|
41
|
+
result.add_argument(arg.name, value)
|
42
|
+
end
|
43
|
+
|
44
|
+
result.add_argument(specs[-1].name, arguments)
|
45
|
+
|
46
|
+
[result, []]
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_optional_arguments(result, arguments, specification)
|
50
|
+
extract_arguments(result, arguments, specification.optional_arguments)
|
51
|
+
end
|
52
|
+
|
53
|
+
def extract_arguments(result, arguments, specs)
|
54
|
+
arguments = arguments.dup
|
55
|
+
|
56
|
+
specs.each do |arg|
|
57
|
+
value = arguments.shift
|
58
|
+
result.add_argument(arg.name, value)
|
59
|
+
end
|
60
|
+
|
61
|
+
[result, arguments]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class InvalidArguments < Error
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module AudioFeedManager
|
2
|
+
class ArgumentsSpecification
|
3
|
+
extend GetterSetterMethod
|
4
|
+
|
5
|
+
Argument = Struct.new(:name, :required, :description, :default) do
|
6
|
+
def usage_name
|
7
|
+
name.upcase
|
8
|
+
end
|
9
|
+
|
10
|
+
def optional
|
11
|
+
!required
|
12
|
+
end
|
13
|
+
|
14
|
+
def varargs
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Varargs = Struct.new(:name, :minimum, :description) do
|
20
|
+
def usage_name
|
21
|
+
"#{name.upcase}..."
|
22
|
+
end
|
23
|
+
|
24
|
+
def required
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def optional
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
def varargs
|
33
|
+
true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.no_arguments
|
38
|
+
new
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.specify
|
42
|
+
specification = no_arguments
|
43
|
+
yield specification
|
44
|
+
specification
|
45
|
+
end
|
46
|
+
|
47
|
+
get_set(:name) { raise "Command name not specified" }
|
48
|
+
get_set(:description) { raise "Description not specified" }
|
49
|
+
|
50
|
+
def argument(name, required: false, description: "", default: nil)
|
51
|
+
arguments << Argument.new(name, required, description, default)
|
52
|
+
end
|
53
|
+
|
54
|
+
def varargs(name, minimum: 1, description: "")
|
55
|
+
arguments << Varargs.new(name, minimum, description)
|
56
|
+
end
|
57
|
+
|
58
|
+
def minimum_varags_count
|
59
|
+
varargs_arguments.map(&:minimum).reduce(0, :+)
|
60
|
+
end
|
61
|
+
|
62
|
+
def required_arguments
|
63
|
+
arguments.select(&:required)
|
64
|
+
end
|
65
|
+
|
66
|
+
def optional_arguments
|
67
|
+
arguments.select(&:optional)
|
68
|
+
end
|
69
|
+
|
70
|
+
def varargs_arguments
|
71
|
+
arguments.select(&:varargs)
|
72
|
+
end
|
73
|
+
|
74
|
+
def arguments
|
75
|
+
@arguments ||= []
|
76
|
+
end
|
77
|
+
|
78
|
+
def usage_line
|
79
|
+
name + required_arguments_line + optional_arguments_line + varargs_line
|
80
|
+
end
|
81
|
+
|
82
|
+
def has_arguments?
|
83
|
+
!arguments.empty?
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def required_arguments_line
|
89
|
+
return "" if required_arguments.empty?
|
90
|
+
" " + required_arguments.map(&:usage_name).map{|s| "<#{s}>"}.join(" ")
|
91
|
+
end
|
92
|
+
|
93
|
+
def optional_arguments_line
|
94
|
+
return "" if optional_arguments.empty?
|
95
|
+
" " + optional_arguments.map(&:usage_name).map{|s| "[#{s}]"}.join(" ")
|
96
|
+
end
|
97
|
+
|
98
|
+
def varargs_line
|
99
|
+
return "" if varargs_arguments.empty?
|
100
|
+
" " + varargs_arguments.map(&:usage_name).map{|s| "[#{s}]"}.join(" ")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module AudioFeedManager
|
2
|
+
class Command
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def self.arguments
|
6
|
+
yield(arguments_specification)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.arguments_specification
|
10
|
+
@arguments_specification ||= ArgumentsSpecification.no_arguments
|
11
|
+
end
|
12
|
+
|
13
|
+
def arguments
|
14
|
+
self.class.arguments_specification
|
15
|
+
end
|
16
|
+
|
17
|
+
def_delegators :arguments, :name, :description, :usage_line,
|
18
|
+
:has_arguments?, :required_arguments, :optional_arguments, :varargs_arguments
|
19
|
+
|
20
|
+
def run(arguments = {})
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|