wordword 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +268 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +149 -0
- data/README.md +44 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/locales/en.yml +10 -0
- data/exe/wordword +18 -0
- data/lib/initializers/i18n.rb +4 -0
- data/lib/wordword.rb +9 -0
- data/lib/wordword/cli.rb +61 -0
- data/lib/wordword/command.rb +123 -0
- data/lib/wordword/commands/.gitkeep +1 -0
- data/lib/wordword/commands/compose.rb +43 -0
- data/lib/wordword/commands/train.rb +67 -0
- data/lib/wordword/interactors/compose/handle_exit.rb +60 -0
- data/lib/wordword/interactors/train/single_choice.rb +58 -0
- data/lib/wordword/interactors/train/word_loop.rb +42 -0
- data/lib/wordword/operations/read_word_table.rb +37 -0
- data/lib/wordword/templates/.gitkeep +1 -0
- data/lib/wordword/templates/compose/.gitkeep +1 -0
- data/lib/wordword/templates/train/.gitkeep +1 -0
- data/lib/wordword/version.rb +5 -0
- data/wordword.gemspec +67 -0
- metadata +472 -0
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Wordword
|
2
|
+
|
3
|
+
Process of learning of a new language consist of two parts - grammar and vocabulary. First is best studied with a person near you, a book and will to understand and grasp the concepts of a language. The second, though, can be improved by repetition and perservance. In order to ease the process of repetition of learning words and stick to the most minimal sufficient environment without hussle of the UI this gem is created specifically to work in the console. Just toss the words and translations list file in the `wordword train FILE` command and you are good to go!
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Install it with:
|
8
|
+
|
9
|
+
$ gem install wordword
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
This application has two commands - `train` and `compose`.
|
14
|
+
|
15
|
+
`train` command is a command that is aimed to train (oh yeah?) your knowledge of the words in the file provided with tests like that one. It goes over all the words in the file and says you if you are wrong. In the end you are either congratulated with no wrong answers or given a list of wrong answers and their relative correct ones.
|
16
|
+
|
17
|
+
```
|
18
|
+
What is the translation of 'die Apfeltasche'? (Use ↑/↓ arrow keys, press Enter to select)
|
19
|
+
‣ apple pie
|
20
|
+
beer
|
21
|
+
hamburger
|
22
|
+
fries
|
23
|
+
```
|
24
|
+
|
25
|
+
`compose` command allows you to create files that can be processed by `train` command. It asks you for word and translation until you ethier interrupt the command or type `\q` into the prompt.
|
26
|
+
After this you can save this in the file. If the file already exists, it allows you to merge new and old words and save them in that file, discard your new words or rewrite the file and write only the new words in the file.
|
27
|
+
|
28
|
+
## Development
|
29
|
+
|
30
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
31
|
+
|
32
|
+
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).
|
33
|
+
|
34
|
+
## Contributing
|
35
|
+
|
36
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/artur-martsinkovskyi/wordword. 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.
|
37
|
+
|
38
|
+
## Code of Conduct
|
39
|
+
|
40
|
+
Everyone interacting in the Wordword project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/artur-martsinovskyi/wordword/blob/master/CODE_OF_CONDUCT.md).
|
41
|
+
|
42
|
+
## Copyright
|
43
|
+
|
44
|
+
Copyright (c) 2020 Artur Martsinkovskyi. See [MIT License](LICENSE.txt) for further details.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'wordword'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
en:
|
2
|
+
errors:
|
3
|
+
file_is_not_readable: "File is not readable. Check if it exists and you have correct permissions."
|
4
|
+
file_is_not_parseable: "File can't be parsed. Check if it is a valid words file e.g. that it includes only lines separated by # with words on each sides of it."
|
5
|
+
train:
|
6
|
+
interrupted: "\nYou finished the training abruptly."
|
7
|
+
wrong_alert: "Wrong!"
|
8
|
+
wrong_answers_intro: "Here are the words/phrases you got wrong:"
|
9
|
+
wrong_answer_entry: "%{word} is %{correct_answer}. Your answer was %{answer}."
|
10
|
+
everything_correct: "You've got every word/phrase right. You're awesome, [FRIENDLY PRONOUN OF CHOICE]."
|
data/exe/wordword
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
lib_path = File.expand_path("../lib", __dir__)
|
5
|
+
$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
|
6
|
+
require "wordword/cli"
|
7
|
+
|
8
|
+
Signal.trap("INT") do
|
9
|
+
warn("\n#{caller.join("\n")}: interrupted")
|
10
|
+
exit(1)
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
Wordword::CLI.start
|
15
|
+
rescue Wordword::CLI::Error => e
|
16
|
+
puts "ERROR: #{e.message}"
|
17
|
+
exit 1
|
18
|
+
end
|
data/lib/wordword.rb
ADDED
data/lib/wordword/cli.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
require_relative "../wordword"
|
5
|
+
|
6
|
+
module Wordword
|
7
|
+
# Handle the application command line parsing
|
8
|
+
# and the dispatch to various command objects
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
class CLI < Thor
|
12
|
+
|
13
|
+
# Error raised by this runner
|
14
|
+
Error = Class.new(StandardError)
|
15
|
+
|
16
|
+
desc "version", "wordword version"
|
17
|
+
def version
|
18
|
+
require_relative "version"
|
19
|
+
puts File.read(File.join(File.dirname(__FILE__), "..", "..", "assets/logo.txt"))
|
20
|
+
puts "v#{Wordword::VERSION}"
|
21
|
+
end
|
22
|
+
map %w[--version -v] => :version
|
23
|
+
|
24
|
+
desc "compose", "Launches a repl loop to create file with words/phrases and translations that can be fed to train"
|
25
|
+
method_option :help, aliases: "-h", type: :boolean,
|
26
|
+
desc: "Display usage information"
|
27
|
+
def compose(*)
|
28
|
+
if options[:help]
|
29
|
+
invoke :help, ["compose"]
|
30
|
+
else
|
31
|
+
require_relative "commands/compose"
|
32
|
+
Wordword::Commands::Compose.new(options).execute
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "train FILE", "Train knowledge of the words in the provided file."
|
37
|
+
method_option :help, aliases: "-h", type: :boolean,
|
38
|
+
desc: "Display usage information"
|
39
|
+
method_option :number, type: :numeric, aliases: %w[-n],
|
40
|
+
desc: "Number of words to be trained"
|
41
|
+
def train(file = nil)
|
42
|
+
if options[:help]
|
43
|
+
invoke :help, ["train"]
|
44
|
+
else
|
45
|
+
require_relative "commands/train"
|
46
|
+
Wordword::Commands::Train.new(file, options).execute
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
__END__
|
53
|
+
|
54
|
+
I8, 8 ,8I ,ad8888ba, 88888888ba 88888888ba, "8a I8, 8 ,8I ,ad8888ba, 88888888ba 88888888ba,
|
55
|
+
`8b d8b d8' d8"' `"8b 88 "8b 88 `"8b "8a `8b d8b d8' d8"' `"8b 88 "8b 88 `"8b
|
56
|
+
"8, ,8"8, ,8" d8' `8b 88 ,8P 88 `8b "8a "8, ,8"8, ,8" d8' `8b 88 ,8P 88 `8b
|
57
|
+
Y8 8P Y8 8P 88 88 88aaaaaa8P' 88 88 "8a Y8 8P Y8 8P 88 88 88aaaaaa8P' 88 88
|
58
|
+
`8b d8' `8b d8' 88 88 88""""88' 88 88 aaaaaaaa a8" `8b d8' `8b d8' 88 88 88""""88' 88 88
|
59
|
+
`8a a8' `8a a8' Y8, ,8P 88 `8b 88 8P """""""" a8" `8a a8' `8a a8' Y8, ,8P 88 `8b 88 8P
|
60
|
+
`8a8' `8a8' Y8a. .a8P 88 `8b 88 .a8P a8" `8a8' `8a8' Y8a. .a8P 88 `8b 88 .a8P
|
61
|
+
`8' `8' `"Y8888Y"' 88 `8b 88888888Y"' a8" `8' `8' `"Y8888Y"' 88 `8b 88888888Y"'
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Wordword
|
6
|
+
class Command
|
7
|
+
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :command, :run
|
11
|
+
|
12
|
+
# Execute this command
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
def execute(*)
|
16
|
+
raise(
|
17
|
+
NotImplementedError,
|
18
|
+
"#{self.class}##{__method__} must be implemented",
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
# The external commands runner
|
23
|
+
#
|
24
|
+
# @see http://www.rubydoc.info/gems/tty-command
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
def command(**options)
|
28
|
+
require "tty-command"
|
29
|
+
TTY::Command.new(options)
|
30
|
+
end
|
31
|
+
|
32
|
+
# The cursor movement
|
33
|
+
#
|
34
|
+
# @see http://www.rubydoc.info/gems/tty-cursor
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def cursor
|
38
|
+
require "tty-cursor"
|
39
|
+
TTY::Cursor
|
40
|
+
end
|
41
|
+
|
42
|
+
# Open a file or text in the user's preferred editor
|
43
|
+
#
|
44
|
+
# @see http://www.rubydoc.info/gems/tty-editor
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def editor
|
48
|
+
require "tty-editor"
|
49
|
+
TTY::Editor
|
50
|
+
end
|
51
|
+
|
52
|
+
# File manipulation utility methods
|
53
|
+
#
|
54
|
+
# @see http://www.rubydoc.info/gems/tty-file
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
def generator
|
58
|
+
require "tty-file"
|
59
|
+
TTY::File
|
60
|
+
end
|
61
|
+
|
62
|
+
# Terminal output paging
|
63
|
+
#
|
64
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
65
|
+
#
|
66
|
+
# @api public
|
67
|
+
def pager(**options)
|
68
|
+
require "tty-pager"
|
69
|
+
TTY::Pager.new(options)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Terminal platform and OS properties
|
73
|
+
#
|
74
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def platform
|
78
|
+
require "tty-platform"
|
79
|
+
TTY::Platform.new
|
80
|
+
end
|
81
|
+
|
82
|
+
# The interactive prompt
|
83
|
+
#
|
84
|
+
# @see http://www.rubydoc.info/gems/tty-prompt
|
85
|
+
#
|
86
|
+
# @api public
|
87
|
+
def prompt(**options)
|
88
|
+
require "tty-prompt"
|
89
|
+
TTY::Prompt.new(options)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get terminal screen properties
|
93
|
+
#
|
94
|
+
# @see http://www.rubydoc.info/gems/tty-screen
|
95
|
+
#
|
96
|
+
# @api public
|
97
|
+
def screen
|
98
|
+
require "tty-screen"
|
99
|
+
TTY::Screen
|
100
|
+
end
|
101
|
+
|
102
|
+
# The unix which utility
|
103
|
+
#
|
104
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
105
|
+
#
|
106
|
+
# @api public
|
107
|
+
def which(*args)
|
108
|
+
require "tty-which"
|
109
|
+
TTY::Which.which(*args)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Check if executable exists
|
113
|
+
#
|
114
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
115
|
+
#
|
116
|
+
# @api public
|
117
|
+
def exec_exist?(*args)
|
118
|
+
require "tty-which"
|
119
|
+
TTY::Which.exist?(*args)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
#
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../command"
|
4
|
+
require_relative "../interactors/compose/handle_exit"
|
5
|
+
|
6
|
+
module Wordword
|
7
|
+
module Commands
|
8
|
+
class Compose < Wordword::Command
|
9
|
+
|
10
|
+
QUIT_CODE = '\\q'
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(input: $stdin, output: $stdout)
|
17
|
+
words = {}
|
18
|
+
loop do
|
19
|
+
word = prompt.ask(
|
20
|
+
"What is the word/phrase?(write #{QUIT_CODE} to exit)",
|
21
|
+
) do |w|
|
22
|
+
w.required true
|
23
|
+
end
|
24
|
+
break if word == QUIT_CODE
|
25
|
+
|
26
|
+
translation = prompt.ask(
|
27
|
+
"What is the translation?(write #{QUIT_CODE} to exit)",
|
28
|
+
) do |t|
|
29
|
+
t.required true
|
30
|
+
end
|
31
|
+
break if translation == QUIT_CODE
|
32
|
+
|
33
|
+
words[word] = translation
|
34
|
+
end
|
35
|
+
rescue TTY::Reader::InputInterrupt
|
36
|
+
prompt.error("\nYou interrupted the command, exiting...")
|
37
|
+
ensure
|
38
|
+
::Compose::HandleExit.new(self).call(words)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../command"
|
4
|
+
require_relative "../operations/read_word_table"
|
5
|
+
require_relative "../interactors/train/word_loop"
|
6
|
+
require "tty/reader"
|
7
|
+
|
8
|
+
module Wordword
|
9
|
+
module Commands
|
10
|
+
class Train < Wordword::Command
|
11
|
+
|
12
|
+
def initialize(file, options)
|
13
|
+
@file = file
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute(input: $stdin, output: $stdout)
|
18
|
+
read_result = ReadWordTable.new.call(filename: @file)
|
19
|
+
if read_result.success?
|
20
|
+
words = read_result.success
|
21
|
+
else
|
22
|
+
output.puts(
|
23
|
+
pastel.red(
|
24
|
+
I18n.t("errors.#{read_result.failure}"),
|
25
|
+
),
|
26
|
+
)
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
word_loop.run(
|
31
|
+
words,
|
32
|
+
loop_depth: @options[:number],
|
33
|
+
)
|
34
|
+
rescue TTY::Reader::InputInterrupt
|
35
|
+
prompt.error(
|
36
|
+
pastel.red(
|
37
|
+
I18n.t("train.interrupted"),
|
38
|
+
),
|
39
|
+
)
|
40
|
+
ensure
|
41
|
+
if words && word_loop
|
42
|
+
if word_loop.wrong_answers.any?
|
43
|
+
prompt.error(I18n.t("train.wrong_answers_intro"))
|
44
|
+
word_loop.wrong_answers.each do |wrong_answer_message|
|
45
|
+
prompt.say(wrong_answer_message)
|
46
|
+
end
|
47
|
+
elsif words.any?
|
48
|
+
prompt.ok(
|
49
|
+
I18n.t("train.everything_correct"),
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def word_loop
|
58
|
+
@word_loop ||= ::Train::WordLoop.new(self)
|
59
|
+
end
|
60
|
+
|
61
|
+
def pastel
|
62
|
+
@pastel ||= Pastel.new
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
require_relative "../../operations/read_word_table"
|
5
|
+
|
6
|
+
module Compose
|
7
|
+
class HandleExit
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def_delegators :@command_context, :prompt
|
12
|
+
|
13
|
+
MERGE = :merge
|
14
|
+
REWRITE = :rewrite
|
15
|
+
|
16
|
+
def initialize(command_context)
|
17
|
+
@command_context = command_context
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(words)
|
21
|
+
return unless words.any?
|
22
|
+
return unless prompt.yes?("Save the file?")
|
23
|
+
|
24
|
+
filename = prompt.ask("What is the filename?")
|
25
|
+
if File.exist?(filename)
|
26
|
+
file_mode = ask_file_mode
|
27
|
+
if file_mode == REWRITE
|
28
|
+
write_words(filename, words)
|
29
|
+
elsif file_mode == MERGE
|
30
|
+
existing_words = ReadWordTable.new.call(filename: filename).value!
|
31
|
+
merged_words = existing_words.merge(words)
|
32
|
+
|
33
|
+
write_words(filename, merged_words)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
write_words(filename, words)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def ask_file_mode
|
43
|
+
prompt.select(
|
44
|
+
"File with that name already exists. Merge, rewrite or discard current input?(it will be lost in that case)",
|
45
|
+
) do |menu|
|
46
|
+
menu.choice "Merge", -> { MERGE }
|
47
|
+
menu.choice "Rewrite", -> { REWRITE }
|
48
|
+
menu.choice "Discard"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def write_words(filename, words)
|
53
|
+
File.write(
|
54
|
+
filename,
|
55
|
+
words.sort.to_h.map { |word| word.join(" # ") }.join("\n"),
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|