s7 0.0.1
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.
- 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
|