scl 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/README.md +71 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/scl +124 -0
- data/lib/scl/aes.rb +28 -0
- data/lib/scl/control/aes.rb +51 -0
- data/lib/scl/control/controller.rb +96 -0
- data/lib/scl/control/controller_module.rb +102 -0
- data/lib/scl/control/dh.rb +84 -0
- data/lib/scl/control/digest.rb +90 -0
- data/lib/scl/control/output.rb +14 -0
- data/lib/scl/control/rsa.rb +161 -0
- data/lib/scl/control/sss.rb +48 -0
- data/lib/scl/control.rb +14 -0
- data/lib/scl/dh.rb +52 -0
- data/lib/scl/digest.rb +29 -0
- data/lib/scl/formats/auto.rb +23 -0
- data/lib/scl/formats/base64.rb +12 -0
- data/lib/scl/formats/binary.rb +11 -0
- data/lib/scl/formats/format.rb +44 -0
- data/lib/scl/formats/hex.rb +11 -0
- data/lib/scl/formats/qrcode.rb +11 -0
- data/lib/scl/formats/stdout.rb +11 -0
- data/lib/scl/formats/wordlist.txt +235886 -0
- data/lib/scl/formats/words.rb +23 -0
- data/lib/scl/rsa.rb +72 -0
- data/lib/scl/secret_share.rb +122 -0
- data/lib/scl/version.rb +3 -0
- data/lib/scl.rb +12 -0
- data/scl.gemspec +25 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 043a75c0ec7aab24c5bde01c147e009b7e0bc58f
|
4
|
+
data.tar.gz: 3e1bf4954baccfab324591cddcbaa1fc60f14dab
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: eb615f84d442a67a169c804f99a2a243553c99055879a52d7479e96742f43142e69da4971657f6ead3d19bea5584f3722553e7b64193f563507e6950fe19dd2a
|
7
|
+
data.tar.gz: c0f3e20309a25ff69d1e4ebb2dcfaf8e2275fee1ebd7fa48cbefeb6d379ad9a4fb1b72e0d3c2b64cceb593e26755e065be4c3bdb40095533ed71a7ab8e430c6b
|
data/.gitignore
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at wouter@youdo.co.nz. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Scl (Simple Crypto Library)
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/scl`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'scl'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install scl
|
22
|
+
|
23
|
+
## CLI
|
24
|
+
|
25
|
+
SCL Comes with a command line interface that allows you to perform basic encryption, decryption and key generation and key negotiation operations.
|
26
|
+
|
27
|
+
Example usage:
|
28
|
+
|
29
|
+
```
|
30
|
+
RSA:
|
31
|
+
scl rsa generate # Generate an RSA key-pair > prints to stdout in base64
|
32
|
+
scl rsa generate --size=2048 --out=/tmp/keypair # Generate an RSA key-pair with size 2048 and
|
33
|
+
# output binary as binary encoded.
|
34
|
+
# Save to path /tmp/keypair
|
35
|
+
|
36
|
+
scl rsa verify --pub-key=/tmp/keypair/rsa.pub /path/to/file "signature" # Verify the signature of a file using a public key
|
37
|
+
scl rsa sign --priv-key=/tmp/keypair/rsa.priv /path/to/file # Generate a signature, outputs to stdout
|
38
|
+
scl rsa encrypt --key=/tmp/keypair/rsa.[priv|pub] /path/to/file # Encrypt a file, output to stdout
|
39
|
+
scl rsa decrypt --key=/tmp/keypair/rsa.[priv|pub] /path/to/file # Decrypt a file, output to stdout
|
40
|
+
|
41
|
+
encrypt and decrypt both accept optional --cipher-size/-cs arguments
|
42
|
+
All rsa actions accept --format/--input-format/--output-format -f/-if/-of arguments
|
43
|
+
|
44
|
+
DH:
|
45
|
+
scl dh ping # Start a diffie hellman key-generation
|
46
|
+
scl dh pong [der] [public-key] # Complete side-1 of a diffie hellman key-generation
|
47
|
+
scl dh done [der] [public-key] [priv-key] # Complete side-2 of a diffie hellman key-generation
|
48
|
+
|
49
|
+
AES:
|
50
|
+
scl aes encrypt # Encrypt from stdin
|
51
|
+
scl aes encrypt [file] # Encrypt from file
|
52
|
+
scl aes encrypt --key="abc" [file] # Enc
|
53
|
+
```
|
54
|
+
|
55
|
+
## Usage
|
56
|
+
|
57
|
+
TODO: Write usage instructions here
|
58
|
+
|
59
|
+
## Development
|
60
|
+
|
61
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
62
|
+
|
63
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
64
|
+
|
65
|
+
## Contributing
|
66
|
+
|
67
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/scl. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
68
|
+
|
69
|
+
## Code of Conduct
|
70
|
+
|
71
|
+
Everyone interacting in the Scl project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/scl/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "scl"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
require 'pry-byebug'
|
11
|
+
Pry.start
|
12
|
+
|
13
|
+
# require "irb"
|
14
|
+
# IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/scl
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "scl"
|
5
|
+
require 'optparse'
|
6
|
+
require 'pry-byebug'
|
7
|
+
|
8
|
+
Options = Struct.new(
|
9
|
+
:input_format,
|
10
|
+
:output_format,
|
11
|
+
:key_format,
|
12
|
+
:module,
|
13
|
+
:action,
|
14
|
+
:verbose,
|
15
|
+
:key_path,
|
16
|
+
:key_size,
|
17
|
+
:private_key_file,
|
18
|
+
:public_key_file,
|
19
|
+
:output_file,
|
20
|
+
:min_shares,
|
21
|
+
:num_shares,
|
22
|
+
:block_size,
|
23
|
+
:block_cipher,
|
24
|
+
:digest,
|
25
|
+
:help,
|
26
|
+
:opts
|
27
|
+
)
|
28
|
+
|
29
|
+
args = Options.new()
|
30
|
+
opt_parser = OptionParser.new do |opts|
|
31
|
+
opts.banner = "Usage: scl [mode] [command] [options] "
|
32
|
+
|
33
|
+
opts.separator ""
|
34
|
+
opts.separator "Where mode is one of (aes, rsa, dh, sss, digest)"
|
35
|
+
opts.separator "try scl [mode] -h or scl [mode] [command] -h for more details"
|
36
|
+
opts.separator ""
|
37
|
+
opts.separator "Where options are:"
|
38
|
+
|
39
|
+
opts.on("-v", "--verbose", "Verbose output") do
|
40
|
+
args.verbose = true
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on(%i(words qrcode base64 binary hex stdout), "-f [FORMAT]", "--format [=FORMAT]", "Format to use for output, one of base64, qrcode, words, hex, stdout, none") do |f|
|
44
|
+
puts "Selected output format #{f}" if args.verbose
|
45
|
+
args.output_format = f
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on(%i(words qrcode base64 binary hex), "-i [INPUT_FORMAT]", "--input-format [=INPUT_FORMAT]", "Format to use for input, one of base64, qrcode, words, hex, none") do |f|
|
49
|
+
puts "Selected input format #{f}" if args.verbose
|
50
|
+
args.input_format = f
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on(%i(words qrcode base64 binary hex), "-b [KEY_FORMAT]", "--key-format [=KEY_FORMAT]", "Format to use for keys, one of base64, qrcode, words, hex, none") do |f|
|
54
|
+
puts "Selected key format #{f}" if args.verbose
|
55
|
+
args.key_format = f
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on("-k [KEY_PATH]", "--key-path [=KEY_PATH]", "The path of the key(s) to use (Looks for [key_path].pub and [key_path].priv)") do |p|
|
59
|
+
puts "Selected key path #{p}" if args.verbose
|
60
|
+
args.key_path = p
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on("-Z [PRIVATE_KEY]", "--priv-key [=PRIVATE_KEY]", "Private key file to use") do |p|
|
64
|
+
puts "Using private key file #{p}" if args.verbose
|
65
|
+
args.private_key_file = p
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on("-p [PUBLIC_KEY]", "--pub-key [=PUBLIC_KEY]", "Public key file to use") do |p|
|
69
|
+
puts "Using public key file #{p}" if args.verbose
|
70
|
+
args.public_key_file = p
|
71
|
+
end
|
72
|
+
|
73
|
+
opts.on("-o [OUTPUT_FILE]", "--out [=OUTPUT_FILE]", "File or file-prefix for where to save outputs") do |p|
|
74
|
+
puts "Using output file #{p}" if args.verbose
|
75
|
+
args.output_file = p
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on("-s [KEY_SIZE]", "--key-size [=KEY_SIZE]", Integer, "Size of the generated key") do |ks|
|
79
|
+
puts "Using key size #{ks}" if args.verbose
|
80
|
+
args.key_size = ks
|
81
|
+
end
|
82
|
+
|
83
|
+
opts.on("-m [MIN_SHARES]", "--min-shares [=MIN_SHARES]", Integer, "Size of the generated key") do |ms|
|
84
|
+
puts "Using min shares #{ms}" if args.verbose
|
85
|
+
args.min_shares = ms
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on("-n [NUM_SHARES]", "--num-shares [=NUM_SHARES]", Integer, "Size of the generated key") do |ns|
|
89
|
+
puts "Using num shares #{ns}" if args.verbose
|
90
|
+
args.num_shares = ns
|
91
|
+
end
|
92
|
+
|
93
|
+
opts.on("-S [BLOCK_SIZE]", "--block-size [=BLOCK_SIZE]", Integer, "Block size of cipher") do |bs|
|
94
|
+
puts "Using block size #{bs}" if args.verbose
|
95
|
+
args.block_size = bs
|
96
|
+
end
|
97
|
+
|
98
|
+
opts.on("-C [BLOCK_CIPHER]", "--block-size [=BLOCK_CIPHER]", String, "Block cipher") do |bc|
|
99
|
+
puts "Using block cipher #{bc}" if args.verbose
|
100
|
+
args.block_cipher = bc
|
101
|
+
end
|
102
|
+
|
103
|
+
opts.on('-d [DIGEST]', '--digest [=DIGEST]', String, 'Digest (e.g. sha256)') do |dg|
|
104
|
+
puts "Using digest #{dg}" if args.verbose
|
105
|
+
args.digest = dg
|
106
|
+
end
|
107
|
+
|
108
|
+
opts.on("-h", "--help", "Prints this help") do
|
109
|
+
args.help = true
|
110
|
+
end
|
111
|
+
args.opts = opts
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
opt_parser.parse!
|
116
|
+
|
117
|
+
unless ARGV.any?
|
118
|
+
puts args.opts
|
119
|
+
exit(0)
|
120
|
+
else
|
121
|
+
Scl::Control::Controller.new(args)
|
122
|
+
.module(ARGV.shift)
|
123
|
+
.action(ARGV.shift, ARGV)
|
124
|
+
end
|
data/lib/scl/aes.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Scl
|
2
|
+
class AES
|
3
|
+
def initialize(block_size=256, block_cipher=:CBC)
|
4
|
+
@block_cipher = block_cipher || :CBC
|
5
|
+
@block_size = block_size || 256
|
6
|
+
end
|
7
|
+
|
8
|
+
def build_cypher
|
9
|
+
OpenSSL::Cipher::AES.new(@block_size, @block_cipher)
|
10
|
+
end
|
11
|
+
|
12
|
+
def encrypt(plaintext, key=nil, iv=nil)
|
13
|
+
block_cipher = build_cypher
|
14
|
+
block_cipher.encrypt
|
15
|
+
block_cipher.key = key ||= block_cipher.random_key
|
16
|
+
block_cipher.iv = iv ||= block_cipher.random_iv
|
17
|
+
[block_cipher.update(plaintext) + block_cipher.final, key, iv]
|
18
|
+
end
|
19
|
+
|
20
|
+
def decrypt(ciphertext, key, iv)
|
21
|
+
block_cipher = build_cypher
|
22
|
+
block_cipher.decrypt
|
23
|
+
block_cipher.key = key
|
24
|
+
block_cipher.iv = iv
|
25
|
+
block_cipher.update( ciphertext ) + block_cipher.final
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Scl
|
2
|
+
module Control
|
3
|
+
class AES < ControllerModule
|
4
|
+
|
5
|
+
help <<-HELP,
|
6
|
+
encrypt. Encrypt a given file.
|
7
|
+
Can optionally be given an existing key, otherwise a unique one will be generated alongside
|
8
|
+
the cipher text.
|
9
|
+
e.g
|
10
|
+
scl aes encrypt somefile
|
11
|
+
scl aes encrypt somefile -k somekey
|
12
|
+
HELP
|
13
|
+
def encrypt(file)
|
14
|
+
input_key = args.key_path ?
|
15
|
+
read_file(args.key_path) :
|
16
|
+
nil
|
17
|
+
file_content = read_file(file)
|
18
|
+
ct, key, iv = aes.encrypt(file_content, input_key)
|
19
|
+
args.output_file ||= file
|
20
|
+
controller.output(
|
21
|
+
Output.new(iv << '::' << ct, ".enc"),
|
22
|
+
input_key ? nil : Output.new(key, '.key')
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
help <<-HELP,
|
28
|
+
decrypt. Decrypt a given file.
|
29
|
+
Must be given a key using -k/--key-path
|
30
|
+
e.g
|
31
|
+
scl aes decrypt somefile.enc -k somekey.key
|
32
|
+
scl aes decrypt somefile.enc -k somekey.key -o output.txt
|
33
|
+
HELP
|
34
|
+
def decrypt(file)
|
35
|
+
key = input_decoder.decode(read_file(args.key_path, "encryption key", "Use -k option"))
|
36
|
+
iv, cipher_text = input_decoder.decode(read_file(file, 'ciphertext')).split('::', 2)
|
37
|
+
|
38
|
+
plaintext = aes.decrypt(cipher_text, key, iv)
|
39
|
+
args.output_format = 'binary'
|
40
|
+
controller.output(
|
41
|
+
Output.new(plaintext, '')
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def aes
|
47
|
+
Scl::AES.new(args.block_size, args.block_cipher)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Scl
|
2
|
+
module Control
|
3
|
+
class Controller
|
4
|
+
attr_reader :args
|
5
|
+
def initialize(args)
|
6
|
+
@args = args
|
7
|
+
puts "Using output format #{output_encoder.name}" if verbose?
|
8
|
+
puts "Using input format #{input_decoder.name}" if verbose?
|
9
|
+
end
|
10
|
+
|
11
|
+
def output_encoder
|
12
|
+
coder_for(args.output_format)
|
13
|
+
end
|
14
|
+
|
15
|
+
def input_decoder
|
16
|
+
coder_for(args.input_format)
|
17
|
+
end
|
18
|
+
|
19
|
+
def key_coder
|
20
|
+
coder_for(args.key_format)
|
21
|
+
end
|
22
|
+
|
23
|
+
def output_file
|
24
|
+
"#{@args.output_file}".strip
|
25
|
+
end
|
26
|
+
|
27
|
+
def verbose?
|
28
|
+
@args.verbose
|
29
|
+
end
|
30
|
+
|
31
|
+
def module(module_name)
|
32
|
+
case module_name
|
33
|
+
when "aes" then Control::AES.new(self)
|
34
|
+
when "rsa" then Control::RSA.new(self)
|
35
|
+
when "dh" then Control::DH.new(self)
|
36
|
+
when "sss" then Control::SSS.new(self)
|
37
|
+
when "digest" then Control::Digest.new(self)
|
38
|
+
else
|
39
|
+
puts "No scl module found \"#{module_name}\""
|
40
|
+
puts args.opts
|
41
|
+
exit(1)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def coder_for(format)
|
46
|
+
case "#{format}".strip
|
47
|
+
when "base64" then Format::BASE64
|
48
|
+
when "qrcode" then Format::QRCODE
|
49
|
+
when "base64" then Format::BASE64
|
50
|
+
when "words" then Format::WORDS
|
51
|
+
when "hex" then Format::HEX
|
52
|
+
when "binary","text","none" then Format::BINARY
|
53
|
+
when "", "auto" then Format::AUTO
|
54
|
+
when "stdout" then Format::STDOUT
|
55
|
+
else
|
56
|
+
puts "Unexpected format \"#{format}\""
|
57
|
+
exit(1)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def output(*results)
|
62
|
+
case output_file
|
63
|
+
when '' then
|
64
|
+
puts "\n\n"
|
65
|
+
puts results.compact.map{|r| output_encoder.encode(r.content) }.join("\n\n")
|
66
|
+
else
|
67
|
+
results.compact.each do |result|
|
68
|
+
puts "Writing #{result.file(output_file)}" if verbose?
|
69
|
+
if args.output_format == 'stdout'
|
70
|
+
output_encoder.encode(result.content)
|
71
|
+
else
|
72
|
+
IO.write(
|
73
|
+
result.file(output_file),
|
74
|
+
output_encoder.encode(result.content)
|
75
|
+
) if confirm_overwrite?(result.file(output_file))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def confirm_overwrite?(filename)
|
82
|
+
if File.exists?(filename)
|
83
|
+
puts "File #{filename} already exists. Confirm overwrite? [Yn]"
|
84
|
+
if gets.strip.downcase == 'y'
|
85
|
+
puts "Confirmed overwrite for #{filename}" if verbose?
|
86
|
+
return true
|
87
|
+
else
|
88
|
+
puts "Skipping save for #{filename}" if verbose?
|
89
|
+
return false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
return true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Scl
|
2
|
+
module Control
|
3
|
+
class ControllerModule
|
4
|
+
attr_reader :controller
|
5
|
+
def initialize(controller)
|
6
|
+
@controller = controller
|
7
|
+
end
|
8
|
+
|
9
|
+
def args
|
10
|
+
@controller.args
|
11
|
+
end
|
12
|
+
|
13
|
+
def input_decoder
|
14
|
+
@controller.input_decoder
|
15
|
+
end
|
16
|
+
|
17
|
+
def output_encoder
|
18
|
+
@controller.output_encoder
|
19
|
+
end
|
20
|
+
|
21
|
+
def key_coder
|
22
|
+
@controller.key_coder
|
23
|
+
end
|
24
|
+
|
25
|
+
def help?
|
26
|
+
args.help
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.help(message, method)
|
30
|
+
@@help ||= {}
|
31
|
+
@@help[self.name] ||= {}
|
32
|
+
@@help[self.name][method.to_s] = message
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.print_help(args, method, own_methods=[])
|
36
|
+
module_name = self.name.split('::').last.downcase
|
37
|
+
puts "======="
|
38
|
+
if method && @@help[self.name] && @@help[self.name][method]
|
39
|
+
puts "Usage: scl #{module_name} #{method} (options)\n"
|
40
|
+
puts
|
41
|
+
puts @@help[self.name][method].split("\n").map{|line| line.gsub(/^\s{6}/,'')}.join("\n")
|
42
|
+
puts
|
43
|
+
else
|
44
|
+
puts "No help docs found for \"#{method}\"\n=======\n" if method
|
45
|
+
puts "Usage: scl #{module_name} [command] (options)"
|
46
|
+
puts
|
47
|
+
puts "Supported commands are [#{own_methods.join(' ')}]"
|
48
|
+
puts
|
49
|
+
puts "Try scl #{module_name} [command] -h for more info"
|
50
|
+
puts
|
51
|
+
puts args.opts.to_s[/Where options.*/m]
|
52
|
+
end
|
53
|
+
exit(0)
|
54
|
+
end
|
55
|
+
|
56
|
+
def action(action_name, args)
|
57
|
+
args = args.dup
|
58
|
+
ARGV.clear
|
59
|
+
if @controller.args.help || !action_name
|
60
|
+
self.class.print_help(@controller.args, action_name, self.public_methods.select{|m| self.method(m).owner == self.class })
|
61
|
+
exit(0)
|
62
|
+
end
|
63
|
+
if self.respond_to?(action_name)
|
64
|
+
begin
|
65
|
+
action = self.method(action_name)
|
66
|
+
required_args = action.arity >= 0 ? action.arity : -(action.arity + 1)
|
67
|
+
unless required_args <= args.length
|
68
|
+
raise ControlError.new("#{action_name} expected at least #{required_args} arguments\nE.g.\n#{action_name} #{action.parameters.map(&:last).map{|x| "[#{x}]"}[-required_args..-1].join(' ')} (options)")
|
69
|
+
end
|
70
|
+
self.send(action_name, *args)
|
71
|
+
rescue ArgumentError => e
|
72
|
+
puts e.message
|
73
|
+
puts "#{action_name} expects #{required_args} arguments by default\nE.g.\n#{action_name} #{action.parameters.map(&:last).map{|x| "[#{x}]"}[-required_args..-1].join(' ')}"
|
74
|
+
self.class.print_help(@controller.args, action_name)
|
75
|
+
puts e.backtrace if @controller.verbose?
|
76
|
+
rescue ControlError => e
|
77
|
+
puts e.message
|
78
|
+
puts e.cause
|
79
|
+
puts e.cause.backtrace if @controller.verbose?
|
80
|
+
self.class.print_help(@controller.args, action_name)
|
81
|
+
end
|
82
|
+
else
|
83
|
+
own_methods = self.public_methods.select{|m| self.method(m).owner == self.class }
|
84
|
+
puts "Command not supported: \"#{action_name}\""
|
85
|
+
puts "Supported commands are [#{own_methods.join(' ')}]"
|
86
|
+
exit(1)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def read_file(file, label='', help)
|
92
|
+
unless file
|
93
|
+
raise ControlError.new("Expected #{label} file not given\n#{help}")
|
94
|
+
end
|
95
|
+
unless File.exists?(file)
|
96
|
+
raise ControlError.new("Expected #{label} file #{file} doesnt exist\n#{help}")
|
97
|
+
end
|
98
|
+
IO.read(file)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|