instacli 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 50a85c046d7c6a005f5ae3fb995afb467b83e462
4
+ data.tar.gz: 16832c6490f6822773c851a5d84f86394a89ae82
5
+ SHA512:
6
+ metadata.gz: af9511d7f1c66621428c30f3346b84efff385d8f7eeb4648011c297fa40fead434a12ab53c5575e0593ca6ba1c83f3015dd4f299af3283fbcba95c798159f3b9
7
+ data.tar.gz: 8d33afe6a36f0e4a1445659cff0b1bb31ce0ef2890e8f40003ee1ba28a98e3acda27cecaf423c51080d2b1ecfb0df3ee36be5291d7790173fa35c34bbebee3ca
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+
2
+ The MIT License (MIT)
3
+ Copyright © 2016 Chris Olstrom <chris@olstrom.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.org ADDED
@@ -0,0 +1,135 @@
1
+ #+TITLE: Instant CLI
2
+ #+LATEX: \pagebreak
3
+
4
+ * Overview (just add objects!)
5
+
6
+ An unopinionated universal commandline interface for Ruby. Given a collection
7
+ of Objects, provides a reasonable CLI with automatic help generation.
8
+
9
+ * Rationale (why you care)
10
+
11
+ Ruby has a number of excellent gems for building commandline applications.
12
+ They range in complexity, but share a common approach: defining an interface
13
+ to some objects.
14
+
15
+ But... your objects already have interfaces: their =public methods=.
16
+
17
+ ~instacli~ adopts this perspective when building a CLI.
18
+
19
+ * Installation (I can haz?)
20
+
21
+ #+BEGIN_SRC shell
22
+ gem install instacli
23
+ #+END_SRC
24
+
25
+ * Usage (How to use it)
26
+
27
+ ** Building a CLI for an Object
28
+
29
+ Imagine we have an =EchoClass= that implements an =echo(string)= method that
30
+ returns ~string~. Something like this:
31
+
32
+ #+BEGIN_SRC ruby
33
+ class EchoClass
34
+ def echo(string)
35
+ string
36
+ end
37
+ end
38
+ #+END_SRC
39
+
40
+ We can add a CLI for it like so:
41
+
42
+ #+BEGIN_SRC ruby
43
+ require 'instacli'
44
+
45
+ boring_object = EchoClass.new
46
+
47
+ cli = InstaCLI.new boring: boring_object
48
+
49
+ cli.execute(*ARGV)
50
+ #+END_SRC
51
+
52
+ Assuming this code is in ~cli.rb~, and ~cli.rb~ is executable, we can use this
53
+ CLI as follows:
54
+
55
+ #+BEGIN_SRC shell
56
+ ./cli.rb boring echo foo
57
+ #+END_SRC
58
+
59
+ And it will output =foo= to =STDOUT=.
60
+
61
+ ** Autogenerating Documentation
62
+
63
+ Using the same ~EchoClass~, we can add help like so:
64
+
65
+ #+BEGIN_SRC ruby
66
+ class CLI < InstaCLI::CLI
67
+ include InstaCLI::Help
68
+ end
69
+
70
+ cli = CLI.new boring: boring_object
71
+
72
+ cli.execute(*ARGV)
73
+ #+END_SRC
74
+
75
+ Now running ~cli.rb~ with ~--help~, like so:
76
+
77
+ #+BEGIN_SRC shell
78
+ ./cli.rb --help boring echo
79
+ #+END_SRC
80
+
81
+ Will print the following:
82
+
83
+ #+BEGIN_EXAMPLE
84
+ NAME:
85
+ cli boring echo - CommandLine Interface
86
+
87
+ USAGE:
88
+ cli boring echo <string>
89
+ #+END_EXAMPLE
90
+
91
+ * How does it work?
92
+
93
+ ** Mapping Commands to Objects
94
+
95
+ A single ~InstaCLI~ can expose as many objects as you like. Just give it a
96
+ hash, mapping the command names you want to use, to the objects they expose.
97
+
98
+ #+BEGIN_SRC ruby
99
+ mapping = {
100
+ foo: SomeObject.new,
101
+ bar: AnotherObject.new
102
+ }
103
+
104
+ InstaCLI::CLI.new mapping
105
+ #+END_SRC
106
+
107
+ ** Automatic Subcommands
108
+
109
+ Any =public methods= on an object will be exposed as subcommands (ignoring
110
+ any generic methods from ~Object~ itself).
111
+
112
+ ** Automatic Help
113
+
114
+ Disabled by default, can be enabled with ~include InstaCLI::Help~.
115
+
116
+ The parameters of a method are used to generate usage documentation,
117
+ following the common convention of =<required>= and =[optional]=.
118
+
119
+ If a method raises an ~ArugmentError~, a usage notice will be displayed,
120
+ outlining the correct usage.
121
+
122
+ ** Command Demultiplexing (aka "that thing =busybox= does")
123
+
124
+ Disabled by default, can be enabled with ~include InstaCLI::Demuxing~.
125
+
126
+ When enabled, the name the program was invoked with is read as the command to
127
+ execute.
128
+
129
+ * License
130
+
131
+ ~instacli~ is available under the [[https://tldrlegal.com/license/mit-license][MIT License]]. See ~LICENSE.txt~ for the full text.
132
+
133
+ * Contributors
134
+
135
+ - [[https://colstrom.github.io/][Chris Olstrom]] | [[mailto:chris@olstrom.com][e-mail]] | [[https://twitter.com/ChrisOlstrom][Twitter]]
data/instacli.gemspec ADDED
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = 'instacli'
3
+ gem.version = `git describe --tags --abbrev=0`.chomp
4
+ gem.licenses = 'MIT'
5
+ gem.authors = ['Chris Olstrom']
6
+ gem.email = 'chris@olstrom.com'
7
+ gem.homepage = 'https://github.com/colstrom/instacli'
8
+ gem.summary = 'Instant CommandLine Interfaces - just add Objects!'
9
+
10
+ gem.files = `git ls-files`.split("\n")
11
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
12
+ gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
13
+ gem.require_paths = ['lib']
14
+
15
+ gem.add_runtime_dependency 'contracts', '~> 0.14', '>= 0.14.0'
16
+ end
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'contracts'
4
+ require_relative 'exception_variant'
5
+
6
+ module InstaCLI
7
+ # Instant CLI - just add Objects!
8
+ class CLI
9
+ include ::Contracts::Core
10
+ include ::Contracts::Builtin
11
+
12
+ Contract None => ArrayOf[ExceptionVariant]
13
+ def rescues
14
+ [ArgumentError]
15
+ end
16
+
17
+ Contract Maybe[HashOf[Symbol, Object]] => InstaCLI::CLI
18
+ def initialize(**mapping)
19
+ @mapping = mapping
20
+ self
21
+ end
22
+
23
+ Contract None => ArrayOf[Symbol]
24
+ def objects
25
+ @mapping.keys
26
+ end
27
+
28
+ Contract RespondTo[:to_sym] => Bool
29
+ def object?(o)
30
+ object.include? o.to_sym
31
+ end
32
+
33
+ Contract RespondTo[:to_sym] => Object
34
+ def object(o)
35
+ @mapping.fetch(o.to_sym) { Object.new }
36
+ end
37
+
38
+ Contract RespondTo[:to_sym] => ArrayOf[Symbol]
39
+ def methods(o)
40
+ object(o).methods - Object.methods
41
+ end
42
+
43
+ Contract RespondTo[:to_sym], RespondTo[:to_sym] => Bool
44
+ def method?(o, m)
45
+ methods(o).include? m.to_sym
46
+ end
47
+
48
+ Contract RespondTo[:to_sym], RespondTo[:to_sym] => Any
49
+ def method(o, m)
50
+ raise ArgumentError unless method? o, m
51
+ object(o).method(m)
52
+ end
53
+
54
+ Contract RespondTo[:to_sym], RespondTo[:to_sym] => ArrayOf[String]
55
+ def parameters(o, m)
56
+ method(o, m)
57
+ .parameters
58
+ .map { |p| p.first == :req ? "<#{p.last}>" : "[#{p.last}]" }
59
+ end
60
+
61
+ def help(*)
62
+ ''
63
+ end
64
+
65
+ def execute(*args)
66
+ return STDERR.puts help(*args[1..-1]) if %w(--help -h).include?(args.first)
67
+
68
+ o, m, *rest = *args
69
+
70
+ begin
71
+ STDOUT.puts method(o, m).call(*rest)
72
+ rescue *rescues
73
+ STDERR.puts help(*args)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,26 @@
1
+ require 'contracts'
2
+
3
+ module InstaCLI
4
+ # Mixin for routing by invocation (like busybox)
5
+ module Demuxing
6
+ include ::Contracts::Core
7
+ include ::Contracts::Builtin
8
+
9
+ Contract None => String
10
+ def invoked_as
11
+ File.basename $PROGRAM_NAME
12
+ end
13
+
14
+ def execute(*args)
15
+ return STDERR.puts help(invoked_as, args[1..-1]) if %w(--help -h).include?(args.first)
16
+
17
+ m, *rest = *args
18
+
19
+ begin
20
+ STDOUT.puts method(invoked_as, m).call(*rest)
21
+ rescue *rescues
22
+ STDERR.puts help(invoked_as, *args)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ module InstaCLI
2
+ # Contract matching any descendant of Exception
3
+ class ExceptionVariant
4
+ def self.valid?(object)
5
+ object.ancestors.include? Exception
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,48 @@
1
+ require 'contracts'
2
+
3
+ module InstaCLI
4
+ # Usage documentation for --help
5
+ module Help
6
+ include ::Contracts::Core
7
+ include ::Contracts::Builtin
8
+
9
+ def header(*args)
10
+ "NAME:\n\t#{[program, *args].join ' '} - #{description}"
11
+ end
12
+
13
+ Contract None => String
14
+ def commands
15
+ ['COMMANDS:', *objects].join("\n\t")
16
+ end
17
+
18
+ Contract RespondTo[:to_sym] => String
19
+ def commands(o)
20
+ ['COMMANDS:', *methods(o)].join("\n\t")
21
+ end
22
+
23
+ Contract RespondTo[:to_sym], RespondTo[:to_sym] => String
24
+ def usage(o, m)
25
+ "USAGE:\n\t #{[program, o, m, *parameters(o, m)].join(' ')}"
26
+ end
27
+
28
+ def help(*args)
29
+ if args.length < 2
30
+ [header(*args), commands(*args)].join("\n\n")
31
+ else
32
+ [header(*args), usage(*args)].join("\n\n")
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ Contract None => String
39
+ def program
40
+ File.basename $PROGRAM_NAME
41
+ end
42
+
43
+ Contract None => String
44
+ def description
45
+ 'CommandLine Interface'
46
+ end
47
+ end
48
+ end
data/lib/instacli.rb ADDED
@@ -0,0 +1,12 @@
1
+
2
+ require_relative 'instacli/cli'
3
+ require_relative 'instacli/exception_variant'
4
+ require_relative 'instacli/help'
5
+ require_relative 'instacli/demuxing'
6
+
7
+ # Convenience Wrapper
8
+ module InstaCLI
9
+ def self.new(*args)
10
+ InstaCLI::CLI.new(*args)
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: instacli
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Olstrom
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: contracts
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.14'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.14.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.14'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.14.0
33
+ description:
34
+ email: chris@olstrom.com
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - LICENSE.txt
40
+ - README.org
41
+ - instacli.gemspec
42
+ - lib/instacli.rb
43
+ - lib/instacli/cli.rb
44
+ - lib/instacli/demuxing.rb
45
+ - lib/instacli/exception_variant.rb
46
+ - lib/instacli/help.rb
47
+ homepage: https://github.com/colstrom/instacli
48
+ licenses:
49
+ - MIT
50
+ metadata: {}
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project:
67
+ rubygems_version: 2.5.1
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: Instant CommandLine Interfaces - just add Objects!
71
+ test_files: []
72
+ has_rdoc: