wordword 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -0,0 +1,4 @@
1
+ require "i18n"
2
+
3
+ I18n.load_path << Dir[File.expand_path("config/locales") + "/*.yml"]
4
+ I18n.default_locale = :en
data/lib/wordword.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "wordword/version"
4
+
5
+ module Wordword
6
+ class Error < StandardError; end
7
+ Dir[File.join(File.dirname(__FILE__), "initializers", "*")].map { |f| require f }
8
+ # Your code goes here...
9
+ end
@@ -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