audio-feed-manager 0.0.1
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 +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
|