instacli 1.0.0

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