audio-feed-manager 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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