s7 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +311 -0
- data/LICENCE +24 -0
- data/README +56 -0
- data/Rakefile +128 -0
- data/VERSION +1 -0
- data/bin/s7cli +27 -0
- data/lib/gettext.rb +28 -0
- data/lib/s7.rb +12 -0
- data/lib/s7/action.rb +7 -0
- data/lib/s7/attribute.rb +226 -0
- data/lib/s7/cipher.rb +110 -0
- data/lib/s7/configuration.rb +41 -0
- data/lib/s7/entry.rb +106 -0
- data/lib/s7/entry_collection.rb +44 -0
- data/lib/s7/entry_template.rb +10 -0
- data/lib/s7/exception.rb +116 -0
- data/lib/s7/file.rb +77 -0
- data/lib/s7/gpass_file.rb +202 -0
- data/lib/s7/key.rb +83 -0
- data/lib/s7/message_catalog.rb +5 -0
- data/lib/s7/s7_file.rb +47 -0
- data/lib/s7/s7cli.rb +213 -0
- data/lib/s7/s7cli/attribute_command.rb +69 -0
- data/lib/s7/s7cli/command.rb +210 -0
- data/lib/s7/s7cli/entry_collection_command.rb +63 -0
- data/lib/s7/s7cli/entry_command.rb +728 -0
- data/lib/s7/s7cli/option.rb +101 -0
- data/lib/s7/secret_generator.rb +30 -0
- data/lib/s7/undo_stack.rb +7 -0
- data/lib/s7/unicode_data.rb +29 -0
- data/lib/s7/utils.rb +37 -0
- data/lib/s7/world.rb +150 -0
- data/setup.rb +1585 -0
- data/test/s7/attribute_test.rb +45 -0
- data/test/s7/gpass_file_test.rb +169 -0
- data/test/s7/gpass_file_test/passwords.gps.empty +1 -0
- data/test/s7/gpass_file_test/passwords.gps.one_entry +2 -0
- data/test/s7/gpass_file_test/passwords.gps.three_entries +3 -0
- data/test/s7/gpass_file_test/passwords.gps.with_folders +0 -0
- data/test/s7/secret_generator_test.rb +29 -0
- data/test/s7/unicode_data_test.rb +28 -0
- data/test/s7/world_test.rb +35 -0
- data/test/test_helper.rb +19 -0
- metadata +108 -0
data/lib/s7/key.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
|
5
|
+
module S7
|
6
|
+
# 暗号化用のキーを表現する。
|
7
|
+
class Key
|
8
|
+
@@key_classes = {}
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def create_instance(type, passphrase)
|
12
|
+
return @@key_classes[type].new(passphrase)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def key_type(name)
|
18
|
+
if @@key_classes.nil?
|
19
|
+
@@key_classes = {}
|
20
|
+
end
|
21
|
+
@@key_classes[name] = self
|
22
|
+
self.const_set("KEY_TYPE", name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :key
|
27
|
+
attr_reader :length
|
28
|
+
|
29
|
+
def initialize(passphrase)
|
30
|
+
@key = OpenSSL::Digest.digest(hash_type, passphrase)
|
31
|
+
@length = @key.length
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def hash_type
|
37
|
+
begin
|
38
|
+
return self.class.const_get("HASH_TYPE")
|
39
|
+
rescue NameError
|
40
|
+
return self.class.const_get("KEY_TYPE")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Md5Key < Key
|
46
|
+
key_type "MD5"
|
47
|
+
end
|
48
|
+
|
49
|
+
class Sha1Key < Key
|
50
|
+
key_type "SHA-1"
|
51
|
+
HASH_TYPE = "SHA1"
|
52
|
+
end
|
53
|
+
|
54
|
+
begin
|
55
|
+
OpenSSL::Digest.const_get("SHA224")
|
56
|
+
|
57
|
+
class Sha224Key < Key
|
58
|
+
key_type "SHA-224"
|
59
|
+
HASH_TYPE = "SHA224"
|
60
|
+
end
|
61
|
+
rescue NameError
|
62
|
+
end
|
63
|
+
|
64
|
+
begin
|
65
|
+
OpenSSL::Digest.const_get("SHA256")
|
66
|
+
|
67
|
+
class Sha256Key < Key
|
68
|
+
key_type "SHA-256"
|
69
|
+
HASH_TYPE = "SHA256"
|
70
|
+
end
|
71
|
+
rescue NameError
|
72
|
+
end
|
73
|
+
|
74
|
+
begin
|
75
|
+
OpenSSL::Digest.const_get("SHA512")
|
76
|
+
|
77
|
+
class Sha512Key < Key
|
78
|
+
key_type "SHA-512"
|
79
|
+
HASH_TYPE = "SHA512"
|
80
|
+
end
|
81
|
+
rescue NameError
|
82
|
+
end
|
83
|
+
end
|
data/lib/s7/s7_file.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "stringio"
|
5
|
+
require "s7/utils"
|
6
|
+
require "s7/cipher"
|
7
|
+
|
8
|
+
module S7
|
9
|
+
# s7 オリジナルのファイル形式で機密情報の読み書きを表現する。
|
10
|
+
module S7File
|
11
|
+
# マジック。
|
12
|
+
MAGIC = "s7 file version 0\n"
|
13
|
+
|
14
|
+
# data で指定したバイト列を暗号化して書き込む。
|
15
|
+
def write(path, master_key, cipher_type, data)
|
16
|
+
cipher = Cipher.create_instance(cipher_type, :passphrase => master_key)
|
17
|
+
FileUtils.touch(path)
|
18
|
+
File.open(path, "r+b") do |f|
|
19
|
+
f.flock(File::LOCK_EX)
|
20
|
+
data = [MAGIC,
|
21
|
+
cipher_type + "\n",
|
22
|
+
cipher.encrypt(StringIO.new(data))].join
|
23
|
+
f.write(data)
|
24
|
+
f.truncate(f.pos)
|
25
|
+
end
|
26
|
+
FileUtils.chmod(0600, path)
|
27
|
+
end
|
28
|
+
module_function :write
|
29
|
+
|
30
|
+
# path で指定したファイルを読み込み、master_key で指定したマスター
|
31
|
+
# キーを使用して復号する。
|
32
|
+
def read(path, master_key)
|
33
|
+
File.open(path, "rb") do |f|
|
34
|
+
f.flock(File::LOCK_EX)
|
35
|
+
magic = f.gets
|
36
|
+
if magic != MAGIC
|
37
|
+
raise InvalidPassphrase
|
38
|
+
end
|
39
|
+
cipher_type = f.gets.chomp
|
40
|
+
cipher = Cipher.create_instance(cipher_type,
|
41
|
+
:passphrase => master_key)
|
42
|
+
return cipher.decrypt(f)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
module_function :read
|
46
|
+
end
|
47
|
+
end
|
data/lib/s7/s7cli.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require "readline"
|
5
|
+
require "gettext"
|
6
|
+
require "fileutils"
|
7
|
+
require "s7/s7cli/option"
|
8
|
+
require "s7/s7cli/command"
|
9
|
+
command_pattern =
|
10
|
+
File.expand_path("s7cli/*_command.rb", File.dirname(__FILE__))
|
11
|
+
Dir.glob(command_pattern).each do |path|
|
12
|
+
require path.gsub(/\.rb\z/, "")
|
13
|
+
end
|
14
|
+
|
15
|
+
# s7 のコマンドラインインタフェースを表現する。
|
16
|
+
module S7
|
17
|
+
class S7Cli
|
18
|
+
include GetText
|
19
|
+
|
20
|
+
# 最初に実行するメソッド。
|
21
|
+
def self.run(argv)
|
22
|
+
obj = S7Cli.new
|
23
|
+
obj.parse_command_line_argument(argv)
|
24
|
+
obj.run
|
25
|
+
end
|
26
|
+
|
27
|
+
# prompt に指定した文字列を表示後、Readline ライブラリを利用してユー
|
28
|
+
# ザからの入力を得る。
|
29
|
+
def self.input_string(prompt = "", add_history = true)
|
30
|
+
input = Readline.readline(prompt, add_history)
|
31
|
+
input.force_encoding("utf-8") if input
|
32
|
+
return input
|
33
|
+
end
|
34
|
+
|
35
|
+
# prompt に指定した文字列を表示後、エコーバックせずにユーザからの入
|
36
|
+
# 力を得る。パスフレーズの入力を想定。
|
37
|
+
def self.input_string_without_echo(prompt = "")
|
38
|
+
print(prompt) if prompt.length > 0
|
39
|
+
STDOUT.flush
|
40
|
+
Utils.execute("stty -echo")
|
41
|
+
begin
|
42
|
+
input = STDIN.gets
|
43
|
+
ensure
|
44
|
+
Utils.execute("stty echo")
|
45
|
+
puts("")
|
46
|
+
end
|
47
|
+
if input
|
48
|
+
input.chomp!
|
49
|
+
input.force_encoding("utf-8")
|
50
|
+
end
|
51
|
+
return input
|
52
|
+
end
|
53
|
+
|
54
|
+
# prompt に指定した文字列を表示後、ユーザからの入力を得る。
|
55
|
+
# それがyes、y、true、t、okのいずれかであれば true を返し、
|
56
|
+
# そうでなければ false を返す。大小文字は区別しない。
|
57
|
+
# ^D などにより何も入力しない場合、defualt で指定した真偽値を返す。
|
58
|
+
def self.input_boolean(prompt = "", default = false)
|
59
|
+
if input = Readline.readline(prompt, false)
|
60
|
+
input.strip!
|
61
|
+
if input.empty?
|
62
|
+
return default
|
63
|
+
end
|
64
|
+
input.force_encoding("utf-8")
|
65
|
+
if /\Ay|yes|t|true|ok\z/i =~ input
|
66
|
+
return true
|
67
|
+
else
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
else
|
71
|
+
return default
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# World クラスのインスタンス。
|
76
|
+
attr_accessor :world
|
77
|
+
|
78
|
+
def initialize
|
79
|
+
@world = World.new
|
80
|
+
end
|
81
|
+
|
82
|
+
# コマンドラインオプションを解析する。
|
83
|
+
def parse_command_line_argument(argv)
|
84
|
+
option_parser = OptionParser.new { |opts|
|
85
|
+
opts.banner = _("usage: s7cli [options]")
|
86
|
+
opts.separator("")
|
87
|
+
opts.separator(_("options:"))
|
88
|
+
opts.on("-d", "--base-dir=DIR",
|
89
|
+
_("Specify the base directory. Default is '%s'.") % world.base_dir) do |dir|
|
90
|
+
world.base_dir = dir
|
91
|
+
end
|
92
|
+
opts.on("-v", "--version", _("Show version.")) do
|
93
|
+
puts(_("s7cli, version %s") % S7::VERSION)
|
94
|
+
exit(0)
|
95
|
+
end
|
96
|
+
opts.on("-h", "--help",
|
97
|
+
_("Show help and exit.")) do
|
98
|
+
puts(opts)
|
99
|
+
exit(0)
|
100
|
+
end
|
101
|
+
}
|
102
|
+
option_parser.parse(argv)
|
103
|
+
end
|
104
|
+
|
105
|
+
# メインの処理を行う。
|
106
|
+
def run
|
107
|
+
if File.exist?(world.base_dir)
|
108
|
+
if !File.directory?(world.base_dir)
|
109
|
+
Utils::shred_string(master_key)
|
110
|
+
raise InvalidPath.new(world.base_dir)
|
111
|
+
end
|
112
|
+
world.start_logging
|
113
|
+
begin
|
114
|
+
master_key =
|
115
|
+
S7Cli.input_string_without_echo(_("Enter the master key for s7: "))
|
116
|
+
if master_key.nil?
|
117
|
+
exit(1)
|
118
|
+
end
|
119
|
+
world.load(S7File.read(world.secrets_path, master_key))
|
120
|
+
world.master_key = master_key
|
121
|
+
rescue InvalidPassphrase => e
|
122
|
+
puts(e.message)
|
123
|
+
retry
|
124
|
+
end
|
125
|
+
else
|
126
|
+
master_key =
|
127
|
+
S7Cli.input_string_without_echo(_("Enter the master key for s7: "))
|
128
|
+
if master_key.nil?
|
129
|
+
exit(1)
|
130
|
+
end
|
131
|
+
confirmation =
|
132
|
+
S7Cli.input_string_without_echo(_("Comfirm the master key for s7: "))
|
133
|
+
if master_key != confirmation
|
134
|
+
Utils::shred_string(master_key)
|
135
|
+
Utils::shred_string(confirmation) if !confirmation.nil?
|
136
|
+
raise InvalidPassphrase
|
137
|
+
end
|
138
|
+
world.master_key = master_key
|
139
|
+
FileUtils.mkdir_p(world.base_dir)
|
140
|
+
FileUtils.chmod(0700, world.base_dir)
|
141
|
+
world.start_logging
|
142
|
+
world.logger.info(_("created base directory: %s") % world.base_dir)
|
143
|
+
world.save(true)
|
144
|
+
end
|
145
|
+
world.lock
|
146
|
+
begin
|
147
|
+
path = File.join(world.base_dir, "s7cli")
|
148
|
+
if File.file?(path)
|
149
|
+
begin
|
150
|
+
hash = YAML.load(S7File.read(path, world.master_key))
|
151
|
+
hash["history"].each_line do |line|
|
152
|
+
Readline::HISTORY.push(line.chomp)
|
153
|
+
end
|
154
|
+
rescue
|
155
|
+
puts(_("could not read: %s") % path)
|
156
|
+
raise
|
157
|
+
end
|
158
|
+
end
|
159
|
+
loop do
|
160
|
+
begin
|
161
|
+
prompt = "%ss7> " % (world.changed ? "*" : "")
|
162
|
+
input = S7Cli.input_string(prompt, true)
|
163
|
+
if !input
|
164
|
+
puts("")
|
165
|
+
break
|
166
|
+
end
|
167
|
+
name, argv = *Command.split_name_and_argv(input)
|
168
|
+
if !name
|
169
|
+
next
|
170
|
+
end
|
171
|
+
command = Command.create_instance(name, world)
|
172
|
+
if command
|
173
|
+
if !command.run(argv)
|
174
|
+
break
|
175
|
+
end
|
176
|
+
else
|
177
|
+
raise ApplicationError, _("Unknown command: %s") % name
|
178
|
+
end
|
179
|
+
rescue Interrupt
|
180
|
+
puts("")
|
181
|
+
retry
|
182
|
+
rescue ApplicationError
|
183
|
+
puts($!)
|
184
|
+
rescue => e
|
185
|
+
puts(e.message)
|
186
|
+
puts("----- back trace -----")
|
187
|
+
e.backtrace.each do |line|
|
188
|
+
puts(" #{line}")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
ensure
|
193
|
+
begin
|
194
|
+
world.save
|
195
|
+
begin
|
196
|
+
path = File.join(world.base_dir, "s7cli")
|
197
|
+
# TODO: history の上限を設ける。
|
198
|
+
hash = {
|
199
|
+
"history" => Readline::HISTORY.to_a.join("\n"),
|
200
|
+
}
|
201
|
+
S7File.write(path, world.master_key,
|
202
|
+
world.configuration.cipher_type, hash.to_yaml)
|
203
|
+
rescue
|
204
|
+
puts(_("could not write: %s") % path)
|
205
|
+
puts($!)
|
206
|
+
end
|
207
|
+
ensure
|
208
|
+
world.unlock
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "s7/s7cli/command"
|
4
|
+
require "open3"
|
5
|
+
|
6
|
+
module S7
|
7
|
+
class S7Cli
|
8
|
+
# 機密情報の属性のうち、指定したものの値をクリップボードにコピーするコ
|
9
|
+
# マンドを表現する。
|
10
|
+
class AttributeCopyCommand < Command
|
11
|
+
command("copy",
|
12
|
+
["attribute:copy",
|
13
|
+
"c"],
|
14
|
+
_("Copy the attribute value to the clipboard."),
|
15
|
+
_("usage: copy [ID] [ATTRIBUTE NAME] [OPTIONS]"))
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
super
|
19
|
+
@config["copy_command"] = "/usr/bin/pbcopy"
|
20
|
+
@config["inclement_rate"] = true
|
21
|
+
@options.push(IntOption.new("i", "id", "ID", _("Entry ID.")),
|
22
|
+
StringOption.new("n", "name", _("Attribute name.")),
|
23
|
+
BoolOption.new("inclement_rate",
|
24
|
+
_("Inclement the entry's rate after copied.")),
|
25
|
+
StringOption.new("copy_command", _("Copy command.")))
|
26
|
+
end
|
27
|
+
|
28
|
+
# コマンドを実行する。
|
29
|
+
def run(argv)
|
30
|
+
option_parser.parse!(argv)
|
31
|
+
if argv.length > 0
|
32
|
+
config["id"] = argv.shift.to_i
|
33
|
+
end
|
34
|
+
if argv.length > 0
|
35
|
+
config["name"] = argv.shift
|
36
|
+
end
|
37
|
+
if config["id"].nil? || config["name"].nil?
|
38
|
+
puts(_("Too few arguments."))
|
39
|
+
help
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
puts(_("Copy the attribute value to the clipboard."))
|
43
|
+
entry = world.entry_collection.find(config["id"])
|
44
|
+
if entry.nil?
|
45
|
+
raise NoSuchEntry.new("id" => config["id"])
|
46
|
+
end
|
47
|
+
attr = entry.get_attribute(config["name"])
|
48
|
+
if attr.nil?
|
49
|
+
raise NoSuchAttribute.new("id" => config["id"],
|
50
|
+
"name" => config["name"])
|
51
|
+
end
|
52
|
+
copy_command = @config["copy_command"]
|
53
|
+
res, status = Open3.capture2(copy_command,
|
54
|
+
stdin_data: attr.value.to_s,
|
55
|
+
binmode: true)
|
56
|
+
if status != 0
|
57
|
+
raise CommandFailed.new(copy_command, status)
|
58
|
+
end
|
59
|
+
if config["inclement_rate"]
|
60
|
+
# TODO: undo スタックに操作を追加する。
|
61
|
+
entry.rate += 1
|
62
|
+
puts(_("Changed rate to %d.") % entry.rate)
|
63
|
+
world.changed = true
|
64
|
+
end
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "gettext"
|
4
|
+
|
5
|
+
module S7
|
6
|
+
class S7Cli
|
7
|
+
# コマンドを表現する。
|
8
|
+
class Command
|
9
|
+
include GetText
|
10
|
+
|
11
|
+
# オプションの配列。
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
# 設定。
|
15
|
+
attr_reader :config
|
16
|
+
|
17
|
+
# World オブジェクト。
|
18
|
+
attr_reader :world
|
19
|
+
|
20
|
+
@@command_classes = {}
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# s で指定した文字列をコマンド名とオプションに分割する。
|
24
|
+
def split_name_and_argv(s)
|
25
|
+
s = s.strip
|
26
|
+
name = s.slice!(/\A\w+/)
|
27
|
+
argv = s.split
|
28
|
+
return name, argv
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_instance(name, world)
|
32
|
+
klass = @@command_classes[name]
|
33
|
+
if klass
|
34
|
+
return klass.new(world)
|
35
|
+
else
|
36
|
+
return nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def command(name, aliases, description, usage)
|
43
|
+
@@command_classes[name] = self
|
44
|
+
self.const_set("NAME", name)
|
45
|
+
aliases.each do |s|
|
46
|
+
@@command_classes[s] = self
|
47
|
+
end
|
48
|
+
self.const_set("ALIASES", aliases)
|
49
|
+
self.const_set("DESCRIPTION", description)
|
50
|
+
self.const_set("USAGE", usage)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(world)
|
55
|
+
@world = world
|
56
|
+
@config = {}
|
57
|
+
@options = []
|
58
|
+
end
|
59
|
+
|
60
|
+
# コマンド名を取得する。
|
61
|
+
def name
|
62
|
+
return self.class.const_get("NAME")
|
63
|
+
end
|
64
|
+
|
65
|
+
# 別名の配列を取得する。
|
66
|
+
def aliases
|
67
|
+
return self.class.const_get("ALIASES")
|
68
|
+
end
|
69
|
+
|
70
|
+
# 概要を取得する。
|
71
|
+
def description
|
72
|
+
return self.class.const_get("DESCRIPTION")
|
73
|
+
end
|
74
|
+
|
75
|
+
# 使用例を取得する。
|
76
|
+
def usage
|
77
|
+
return self.class.const_get("USAGE")
|
78
|
+
end
|
79
|
+
|
80
|
+
# コマンドラインオプションを解析するための OptionParser オブジェ
|
81
|
+
# クトを取得する。
|
82
|
+
def option_parser
|
83
|
+
return OptionParser.new { |opts|
|
84
|
+
opts.banner = [
|
85
|
+
"#{name} (#{aliases.join(",")}): #{description}",
|
86
|
+
usage,
|
87
|
+
].join("\n")
|
88
|
+
if options && options.length > 0
|
89
|
+
opts.separator("")
|
90
|
+
opts.separator("Valid options:")
|
91
|
+
for option in options
|
92
|
+
option.define(opts, config)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
# 使用方法を示す文字列を返す。
|
99
|
+
def help
|
100
|
+
puts(option_parser)
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
# キャンセルした旨を出力し、true を返す。
|
106
|
+
# Command オブジェクトの run メソッドで return canceled という使
|
107
|
+
# い方を想定している。
|
108
|
+
def canceled
|
109
|
+
puts("\n" + Canceled::MESSAGE)
|
110
|
+
return true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# プログラムを終了するコマンドを表現する。
|
115
|
+
class HelpCommand < Command
|
116
|
+
command("help",
|
117
|
+
["usage", "h"],
|
118
|
+
_("Describe the commands."),
|
119
|
+
_("usage: help [COMMAND]"))
|
120
|
+
|
121
|
+
def run(argv)
|
122
|
+
if argv.length > 0
|
123
|
+
command_name = argv.shift
|
124
|
+
command = Command.create_instance(command_name, world)
|
125
|
+
if command
|
126
|
+
command.help
|
127
|
+
else
|
128
|
+
puts(_("Unknown command: %s") % command_name)
|
129
|
+
end
|
130
|
+
else
|
131
|
+
puts(_("Available commands:"))
|
132
|
+
command_classes = @@command_classes.values.uniq.sort_by { |klass|
|
133
|
+
klass.const_get("NAME")
|
134
|
+
}
|
135
|
+
command_classes.each do |klass|
|
136
|
+
s = " #{klass.const_get("NAME")}"
|
137
|
+
aliases = klass.const_get("ALIASES")
|
138
|
+
if aliases.length > 0
|
139
|
+
s << " (#{aliases.join(", ")})"
|
140
|
+
end
|
141
|
+
puts(s)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
return true
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# 現在の状態を保存するコマンドを表現する。
|
149
|
+
class SaveCommand < Command
|
150
|
+
command("save", [], _("Save."), _("usage: save"))
|
151
|
+
|
152
|
+
def initialize(*args)
|
153
|
+
super
|
154
|
+
@options.push(BoolOption.new("f", "force", _("Force.")))
|
155
|
+
end
|
156
|
+
|
157
|
+
def run(argv)
|
158
|
+
option_parser.parse!(argv)
|
159
|
+
world.save(config["force"])
|
160
|
+
puts(_("Saved."))
|
161
|
+
return true
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# スクリーンロックし、マスターキーを入力するまでは操作できないよう
|
166
|
+
# にするコマンドを表現する。
|
167
|
+
class LockCommand < Command
|
168
|
+
command("lock", [], _("Lock screen after saved."), _("usage: lock"))
|
169
|
+
|
170
|
+
def run(argv)
|
171
|
+
system("clear")
|
172
|
+
puts(_("Locked screen."))
|
173
|
+
world.save
|
174
|
+
prompt = _("Enter the master key for s7: ")
|
175
|
+
begin
|
176
|
+
input = S7Cli.input_string_without_echo(prompt)
|
177
|
+
if input != world.master_key
|
178
|
+
raise InvalidPassphrase
|
179
|
+
end
|
180
|
+
puts(_("Unlocked."))
|
181
|
+
return true
|
182
|
+
rescue Interrupt
|
183
|
+
rescue ApplicationError => e
|
184
|
+
puts($!.message + _("Try again."))
|
185
|
+
retry
|
186
|
+
rescue Exception => e
|
187
|
+
puts(e.message)
|
188
|
+
puts("----- back trace -----")
|
189
|
+
e.backtrace.each do |line|
|
190
|
+
puts(" #{line}")
|
191
|
+
end
|
192
|
+
end
|
193
|
+
puts(_("Quit."))
|
194
|
+
return false
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# プログラムを終了するコマンドを表現する。
|
199
|
+
class QuitCommand < Command
|
200
|
+
command("quit",
|
201
|
+
["exit", "q"],
|
202
|
+
_("Quit."),
|
203
|
+
_("usage: quit"))
|
204
|
+
|
205
|
+
def run(argv)
|
206
|
+
return false
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|