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.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +27 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +17 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +86 -0
  8. data/Rakefile +2 -0
  9. data/audio-feed-manager.gemspec +32 -0
  10. data/bin/afm +5 -0
  11. data/lib/audio-feed-manager.rb +1 -0
  12. data/lib/audio_feed_manager/application.rb +39 -0
  13. data/lib/audio_feed_manager/audio_file.rb +9 -0
  14. data/lib/audio_feed_manager/audio_file_adder.rb +14 -0
  15. data/lib/audio_feed_manager/audio_file_repository.rb +42 -0
  16. data/lib/audio_feed_manager/cli/add_audio_file.rb +39 -0
  17. data/lib/audio_feed_manager/cli/add_feed.rb +20 -0
  18. data/lib/audio_feed_manager/cli/arguments.rb +27 -0
  19. data/lib/audio_feed_manager/cli/arguments_parser.rb +67 -0
  20. data/lib/audio_feed_manager/cli/arguments_specification.rb +103 -0
  21. data/lib/audio_feed_manager/cli/command.rb +23 -0
  22. data/lib/audio_feed_manager/cli/commands_list.rb +120 -0
  23. data/lib/audio_feed_manager/cli/get_feed_url.rb +20 -0
  24. data/lib/audio_feed_manager/cli/initialize_project.rb +44 -0
  25. data/lib/audio_feed_manager/cli/list_feeds.rb +38 -0
  26. data/lib/audio_feed_manager/cli/publish.rb +21 -0
  27. data/lib/audio_feed_manager/cli/show_feed.rb +44 -0
  28. data/lib/audio_feed_manager/cli/update_feed_rss.rb +19 -0
  29. data/lib/audio_feed_manager/cli.rb +19 -0
  30. data/lib/audio_feed_manager/config.rb +5 -0
  31. data/lib/audio_feed_manager/config_repository.rb +14 -0
  32. data/lib/audio_feed_manager/console.rb +28 -0
  33. data/lib/audio_feed_manager/directory_lister.rb +9 -0
  34. data/lib/audio_feed_manager/error.rb +4 -0
  35. data/lib/audio_feed_manager/feed.rb +9 -0
  36. data/lib/audio_feed_manager/feed_items_repository.rb +30 -0
  37. data/lib/audio_feed_manager/feed_repository.rb +32 -0
  38. data/lib/audio_feed_manager/feed_syncer.rb +20 -0
  39. data/lib/audio_feed_manager/file_storage.rb +20 -0
  40. data/lib/audio_feed_manager/id3_tags.rb +14 -0
  41. data/lib/audio_feed_manager/new_model_creator.rb +15 -0
  42. data/lib/audio_feed_manager/rss_generator.rb +10 -0
  43. data/lib/audio_feed_manager/rss_repository.rb +36 -0
  44. data/lib/audio_feed_manager/s3_gateway.rb +39 -0
  45. data/lib/audio_feed_manager/secret_token_generator.rb +9 -0
  46. data/lib/audio_feed_manager/stop_application.rb +4 -0
  47. data/lib/audio_feed_manager/support/getter_setter_method.rb +18 -0
  48. data/lib/audio_feed_manager/support/hash_constructor.rb +9 -0
  49. data/lib/audio_feed_manager/tags.rb +9 -0
  50. data/lib/audio_feed_manager/unique_id_generator.rb +19 -0
  51. data/lib/audio_feed_manager/url_maker.rb +13 -0
  52. data/lib/audio_feed_manager/version.rb +3 -0
  53. data/lib/audio_feed_manager.rb +57 -0
  54. data/spec/audio_feed_manager/audio_file_adder_spec.rb +49 -0
  55. data/spec/audio_feed_manager/audio_file_repository_spec.rb +58 -0
  56. data/spec/audio_feed_manager/cli/add_audio_file_spec.rb +44 -0
  57. data/spec/audio_feed_manager/cli/add_feed_spec.rb +27 -0
  58. data/spec/audio_feed_manager/cli/arguments_parser_spec.rb +85 -0
  59. data/spec/audio_feed_manager/cli/arguments_specification_spec.rb +51 -0
  60. data/spec/audio_feed_manager/cli/commands_list_spec.rb +91 -0
  61. data/spec/audio_feed_manager/cli/get_feed_url_spec.rb +26 -0
  62. data/spec/audio_feed_manager/cli/initialize_project_spec.rb +47 -0
  63. data/spec/audio_feed_manager/cli/list_feeds_spec.rb +27 -0
  64. data/spec/audio_feed_manager/cli/show_feed_spec.rb +41 -0
  65. data/spec/audio_feed_manager/cli/update_feed_rss_spec.rb +26 -0
  66. data/spec/audio_feed_manager/cli_spec.rb +42 -0
  67. data/spec/audio_feed_manager/directory_lister_spec.rb +23 -0
  68. data/spec/audio_feed_manager/feed_items_repository_spec.rb +34 -0
  69. data/spec/audio_feed_manager/feeds_repository_spec.rb +40 -0
  70. data/spec/audio_feed_manager/file_storage_spec.rb +22 -0
  71. data/spec/audio_feed_manager/id3_tags_spec.rb +26 -0
  72. data/spec/audio_feed_manager/new_model_creator_spec.rb +44 -0
  73. data/spec/audio_feed_manager/rss_repository_spec.rb +34 -0
  74. data/spec/audio_feed_manager/s3_gateway_spec.rb +51 -0
  75. data/spec/audio_feed_manager/secret_token_generator_spec.rb +13 -0
  76. data/spec/audio_feed_manager/support/getter_setter_method_spec.rb +77 -0
  77. data/spec/audio_feed_manager/unique_id_generator_spec.rb +23 -0
  78. data/spec/fixtures/test.mp3 +0 -0
  79. data/spec/fixtures/test.txt +1 -0
  80. data/spec/help_spec.rb +64 -0
  81. data/spec/initializing_spec.rb +12 -0
  82. data/spec/managing_audio_files_spec.rb +21 -0
  83. data/spec/managing_feeds_spec.rb +51 -0
  84. data/spec/publishing_spec.rb +24 -0
  85. data/spec/s3_credentials.yml.sample +5 -0
  86. data/spec/spec_helper.rb +17 -0
  87. data/spec/support/builders.rb +26 -0
  88. data/spec/support/fake_console.rb +13 -0
  89. data/spec/support/fakes.rb +5 -0
  90. data/spec/support/integration_helpers.rb +38 -0
  91. 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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in audio-feed-manager.gemspec
4
+ gemspec
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,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -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,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'audio-feed-manager'
4
+
5
+ AudioFeedManager.run
@@ -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,9 @@
1
+ module AudioFeedManager
2
+ class AudioFile < Struct.new(:id, :title, :author, :secret_token, :extension, :size, :content_type)
3
+ include HashConstructor
4
+
5
+ def file_name
6
+ "#{secret_token}#{extension}"
7
+ end
8
+ end
9
+ 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