coding_challenge 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +75 -0
- data/LICENSE.txt +20 -0
- data/README.md +124 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/coding_challenge.gemspec +42 -0
- data/exe/coding_challenge +18 -0
- data/lib/coding_challenge.rb +6 -0
- data/lib/coding_challenge/cli.rb +35 -0
- data/lib/coding_challenge/command.rb +121 -0
- data/lib/coding_challenge/commands/start.rb +245 -0
- data/lib/coding_challenge/commands/text/greeting_title_ascii.txt +23 -0
- data/lib/coding_challenge/commands/util/Inventory.rb +97 -0
- data/lib/coding_challenge/commands/util/Query.rb +23 -0
- data/lib/coding_challenge/commands/util/animation.rb +45 -0
- data/lib/coding_challenge/templates/.gitkeep +1 -0
- data/lib/coding_challenge/templates/start/.gitkeep +1 -0
- data/lib/coding_challenge/version.rb +3 -0
- metadata +167 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b402c49ac44e0b69892c35722a391481078caba6dea4b116b830d98716de84c8
|
4
|
+
data.tar.gz: 19fc88c46f3c15a813004a82b1ff63d42ce72bfd25627f1fb5e9d42be3654037
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 730739aea7cb1626cfe6211aea8fff5a4035ec3e852e64a4a4b6783f8b58a39b45c67bab546face8a3473ce80ad772b6b56654f3c610602e06a223dabace5d8e
|
7
|
+
data.tar.gz: 253be807c4c0b9746aed688ee21a7b615721ce55bc8601db3757f59da8794b3a69788b61593868310f0c8d1750cf518be6228204a22ebce8be4f51b0c7203d67
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
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 jnavarr56@gmail.com. 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 [https://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: https://contributor-covenant.org
|
74
|
+
[version]: https://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
coding_challenge (0.1.0)
|
5
|
+
colorize (~> 0.8.1)
|
6
|
+
lolcat (~> 100.0, >= 100.0.1)
|
7
|
+
thor (~> 1.0, >= 1.0.1)
|
8
|
+
tty-progressbar (~> 0.17.0)
|
9
|
+
tty-prompt (~> 0.21.0)
|
10
|
+
tty-spinner (~> 0.9.3)
|
11
|
+
|
12
|
+
GEM
|
13
|
+
remote: https://rubygems.org/
|
14
|
+
specs:
|
15
|
+
colorize (0.8.1)
|
16
|
+
diff-lcs (1.4.4)
|
17
|
+
equatable (0.6.1)
|
18
|
+
lolcat (100.0.1)
|
19
|
+
manpages (~> 0.6.1)
|
20
|
+
optimist (~> 3.0.0)
|
21
|
+
paint (~> 2.1)
|
22
|
+
manpages (0.6.1)
|
23
|
+
necromancer (0.5.1)
|
24
|
+
optimist (3.0.1)
|
25
|
+
paint (2.2.0)
|
26
|
+
pastel (0.7.4)
|
27
|
+
equatable (~> 0.6)
|
28
|
+
tty-color (~> 0.5)
|
29
|
+
rake (12.3.3)
|
30
|
+
rspec (3.9.0)
|
31
|
+
rspec-core (~> 3.9.0)
|
32
|
+
rspec-expectations (~> 3.9.0)
|
33
|
+
rspec-mocks (~> 3.9.0)
|
34
|
+
rspec-core (3.9.2)
|
35
|
+
rspec-support (~> 3.9.3)
|
36
|
+
rspec-expectations (3.9.2)
|
37
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
38
|
+
rspec-support (~> 3.9.0)
|
39
|
+
rspec-mocks (3.9.1)
|
40
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
41
|
+
rspec-support (~> 3.9.0)
|
42
|
+
rspec-support (3.9.3)
|
43
|
+
strings-ansi (0.1.0)
|
44
|
+
thor (1.0.1)
|
45
|
+
tty-color (0.5.1)
|
46
|
+
tty-cursor (0.7.1)
|
47
|
+
tty-progressbar (0.17.0)
|
48
|
+
strings-ansi (~> 0.1.0)
|
49
|
+
tty-cursor (~> 0.7)
|
50
|
+
tty-screen (~> 0.7)
|
51
|
+
unicode-display_width (~> 1.6)
|
52
|
+
tty-prompt (0.21.0)
|
53
|
+
necromancer (~> 0.5.0)
|
54
|
+
pastel (~> 0.7.0)
|
55
|
+
tty-reader (~> 0.7.0)
|
56
|
+
tty-reader (0.7.0)
|
57
|
+
tty-cursor (~> 0.7)
|
58
|
+
tty-screen (~> 0.7)
|
59
|
+
wisper (~> 2.0.0)
|
60
|
+
tty-screen (0.8.0)
|
61
|
+
tty-spinner (0.9.3)
|
62
|
+
tty-cursor (~> 0.7)
|
63
|
+
unicode-display_width (1.7.0)
|
64
|
+
wisper (2.0.1)
|
65
|
+
|
66
|
+
PLATFORMS
|
67
|
+
ruby
|
68
|
+
|
69
|
+
DEPENDENCIES
|
70
|
+
coding_challenge!
|
71
|
+
rake (~> 12.0)
|
72
|
+
rspec (~> 3.0)
|
73
|
+
|
74
|
+
BUNDLED WITH
|
75
|
+
2.1.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Jorge Navarro
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# CodingChallenge
|
2
|
+
|
3
|
+
Had fun with this. Published it as gem.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
1. via cloning
|
8
|
+
|
9
|
+
Clone repo. Then in project directory `bundle install`.
|
10
|
+
|
11
|
+
2. via rubygems
|
12
|
+
|
13
|
+
``gem install coding_challenge```
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
To run with a cool CLI UI at the start, either do:
|
18
|
+
|
19
|
+
- (IF CLONED) Go to project directory and: `./exe/coding_challenge start [product type] [options]`
|
20
|
+
- (IF INSTALLED AS GEM) from CLI do: `coding_challenge start [product type] [options]`
|
21
|
+
|
22
|
+
To run without a cool CLI UI at the start but have it appear after, either do:
|
23
|
+
_ (IF CLONED) Go to project directory and: `./exe/coding_challenge start [product type] [options] --skip_intro_animation=true`
|
24
|
+
_ (IF INSTALLED AS GEM) from CLI do: `coding_challenge start [product type] [options] --skip_intro_animation=true`
|
25
|
+
|
26
|
+
## Testing
|
27
|
+
|
28
|
+
Clone and go to project directory and do `rspec spec`
|
29
|
+
|
30
|
+
## Code Highlight
|
31
|
+
|
32
|
+
I use an object to group together the query args and results like so
|
33
|
+
|
34
|
+
```
|
35
|
+
class Query
|
36
|
+
attr_reader :product_type, :options, :results
|
37
|
+
attr_writer :performed_at, :results
|
38
|
+
|
39
|
+
def initialize(query_args)
|
40
|
+
@product_type = query_args[0]
|
41
|
+
@options = query_args.slice(1, query_args.length)
|
42
|
+
@performed_at = nil
|
43
|
+
@results = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def formatted_results
|
47
|
+
results_str = "Performed At #{@performed_at}\n"
|
48
|
+
results_str += " Product Type Arg: #{@product_type}\n"
|
49
|
+
results_str += " Options Args: #{@options.join(', ')}\n"
|
50
|
+
results_str += " Results:\n"
|
51
|
+
results_str += " #{@results.join("\n ")}"
|
52
|
+
|
53
|
+
results_str
|
54
|
+
end
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
This an instance of this class is then passed to an instance of a class I made in lib/coding_challenge/commands/util/Inventory.rb as an arg to this method
|
59
|
+
|
60
|
+
```
|
61
|
+
def handle_query(query)
|
62
|
+
remaining_props_seen = {}
|
63
|
+
remaining_props = []
|
64
|
+
|
65
|
+
@products_list.each do |product|
|
66
|
+
next if product['product_type'] != query.product_type
|
67
|
+
|
68
|
+
search_start = query.options.length
|
69
|
+
option_types = product['options'].keys
|
70
|
+
|
71
|
+
(search_start..option_types.length - 1).each do |i|
|
72
|
+
option_type = option_types[i]
|
73
|
+
option_value = product['options'][option_type]
|
74
|
+
|
75
|
+
if remaining_props_seen[option_type].nil?
|
76
|
+
remaining_props_seen[option_type] = {}
|
77
|
+
remaining_props_seen[option_type][option_value] = true
|
78
|
+
remaining_props.push("#{option_type.capitalize}: #{option_value}")
|
79
|
+
elsif !remaining_props_seen[option_type][option_value]
|
80
|
+
remaining_props_seen[option_type][option_value] = true
|
81
|
+
remaining_props[i - search_start] += ", #{option_value}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
This is the main query algorithm.
|
88
|
+
|
89
|
+
A cool thing you can do is specify for the Inventory class to load in a product list in a JSON file from a URL or alternate file path. You can do this from the CLI menu.
|
90
|
+
This method invokes some private methods I created for handling the cases but the logic is captured here:
|
91
|
+
|
92
|
+
```
|
93
|
+
def load_products_list_from_source(source_type, source_uri)
|
94
|
+
if source_type == 'FILE PATH'
|
95
|
+
load_from_file_path(source_uri)
|
96
|
+
elsif source_type == 'URL'
|
97
|
+
load_from_file_url(source_uri)
|
98
|
+
end
|
99
|
+
|
100
|
+
unless @products_list.nil?
|
101
|
+
@source_type = source_type
|
102
|
+
@source_uri = source_uri
|
103
|
+
end
|
104
|
+
|
105
|
+
@products_list
|
106
|
+
end
|
107
|
+
|
108
|
+
```
|
109
|
+
|
110
|
+
## Contributing
|
111
|
+
|
112
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/coding_challenge. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/coding_challenge/blob/master/CODE_OF_CONDUCT.md).
|
113
|
+
|
114
|
+
## Code of Conduct
|
115
|
+
|
116
|
+
Everyone interacting in the CodingChallenge project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/coding_challenge/blob/master/CODE_OF_CONDUCT.md).
|
117
|
+
|
118
|
+
## Copyright
|
119
|
+
|
120
|
+
Copyright (c) 2020 Jorge Navarro. See [MIT License](LICENSE.txt) for further details.
|
121
|
+
|
122
|
+
```
|
123
|
+
|
124
|
+
```
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "coding_challenge"
|
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"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/coding_challenge/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'coding_challenge'
|
7
|
+
spec.license = 'MIT'
|
8
|
+
spec.version = CodingChallenge::VERSION
|
9
|
+
spec.authors = ['Jorge Navarro']
|
10
|
+
spec.email = ['jnavarr56@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'A hiring coding challenge.'
|
13
|
+
spec.description = 'A Ruby CLI app made for hiring coding challenge.'
|
14
|
+
spec.homepage = 'https://github.com/Jnavarr56/REDACTED-3-coding-challenge'
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
|
16
|
+
|
17
|
+
# blank for now
|
18
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
19
|
+
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/Jnavarr56/REDACTED-3-coding-challenge'
|
22
|
+
|
23
|
+
# blank for now
|
24
|
+
spec.metadata['changelog_uri'] = 'https://github.com/Jnavarr56/REDACTED-3-coding-challenge'
|
25
|
+
|
26
|
+
# Specify which files should be added to the gem when it is released.
|
27
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
28
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
29
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
30
|
+
end
|
31
|
+
spec.bindir = 'exe'
|
32
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ['lib']
|
34
|
+
|
35
|
+
# Here's the stuff that does the pretty color magic and animations!
|
36
|
+
spec.add_runtime_dependency 'colorize', '~> 0.8.1'
|
37
|
+
spec.add_runtime_dependency 'lolcat', '~> 100.0', '>= 100.0.1'
|
38
|
+
spec.add_runtime_dependency 'thor', '~> 1.0', '>= 1.0.1'
|
39
|
+
spec.add_runtime_dependency 'tty-progressbar', '~> 0.17.0'
|
40
|
+
spec.add_runtime_dependency 'tty-prompt', '~> 0.21.0'
|
41
|
+
spec.add_runtime_dependency 'tty-spinner', '~> 0.9.3'
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
lib_path = File.expand_path('../lib', __dir__)
|
5
|
+
$:.unshift(lib_path) if !$:.include?(lib_path)
|
6
|
+
require 'coding_challenge/cli'
|
7
|
+
|
8
|
+
Signal.trap('INT') do
|
9
|
+
warn("\n#{caller.join("\n")}: interrupted")
|
10
|
+
exit(1)
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
CodingChallenge::CLI.start
|
15
|
+
rescue CodingChallenge::CLI::Error => err
|
16
|
+
puts "ERROR: #{err.message}"
|
17
|
+
exit 1
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module CodingChallenge
|
6
|
+
# Handle the application command line parsing
|
7
|
+
# and the dispatch to various command objects
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
class CLI < Thor
|
11
|
+
# Error raised by this runner
|
12
|
+
Error = Class.new(StandardError)
|
13
|
+
|
14
|
+
desc 'version', 'coding_challenge version'
|
15
|
+
def version
|
16
|
+
require_relative 'version'
|
17
|
+
puts "v#{CodingChallenge::VERSION}"
|
18
|
+
end
|
19
|
+
map %w[--version -v] => :version
|
20
|
+
|
21
|
+
desc 'start', 'Runs the app!'
|
22
|
+
method_option :help, aliases: '-h', type: :boolean,
|
23
|
+
desc: 'Display usage information'
|
24
|
+
method_option :skip_intro_animation, type: :boolean,
|
25
|
+
desc: 'Skips intro animation'
|
26
|
+
def start(*)
|
27
|
+
if options[:help]
|
28
|
+
invoke :help, ['start']
|
29
|
+
else
|
30
|
+
require_relative 'commands/start'
|
31
|
+
CodingChallenge::Commands::Start.new(options).execute
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module CodingChallenge
|
6
|
+
class Command
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :command, :run
|
10
|
+
|
11
|
+
# Execute this command
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
def execute(*)
|
15
|
+
raise(
|
16
|
+
NotImplementedError,
|
17
|
+
"#{self.class}##{__method__} must be implemented"
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
# The external commands runner
|
22
|
+
#
|
23
|
+
# @see http://www.rubydoc.info/gems/tty-command
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def command(**options)
|
27
|
+
require 'tty-command'
|
28
|
+
TTY::Command.new(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
# The cursor movement
|
32
|
+
#
|
33
|
+
# @see http://www.rubydoc.info/gems/tty-cursor
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
def cursor
|
37
|
+
require 'tty-cursor'
|
38
|
+
TTY::Cursor
|
39
|
+
end
|
40
|
+
|
41
|
+
# Open a file or text in the user's preferred editor
|
42
|
+
#
|
43
|
+
# @see http://www.rubydoc.info/gems/tty-editor
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def editor
|
47
|
+
require 'tty-editor'
|
48
|
+
TTY::Editor
|
49
|
+
end
|
50
|
+
|
51
|
+
# File manipulation utility methods
|
52
|
+
#
|
53
|
+
# @see http://www.rubydoc.info/gems/tty-file
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def generator
|
57
|
+
require 'tty-file'
|
58
|
+
TTY::File
|
59
|
+
end
|
60
|
+
|
61
|
+
# Terminal output paging
|
62
|
+
#
|
63
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def pager(**options)
|
67
|
+
require 'tty-pager'
|
68
|
+
TTY::Pager.new(options)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Terminal platform and OS properties
|
72
|
+
#
|
73
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def platform
|
77
|
+
require 'tty-platform'
|
78
|
+
TTY::Platform.new
|
79
|
+
end
|
80
|
+
|
81
|
+
# The interactive prompt
|
82
|
+
#
|
83
|
+
# @see http://www.rubydoc.info/gems/tty-prompt
|
84
|
+
#
|
85
|
+
# @api public
|
86
|
+
def prompt(**options)
|
87
|
+
require 'tty-prompt'
|
88
|
+
TTY::Prompt.new(options)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get terminal screen properties
|
92
|
+
#
|
93
|
+
# @see http://www.rubydoc.info/gems/tty-screen
|
94
|
+
#
|
95
|
+
# @api public
|
96
|
+
def screen
|
97
|
+
require 'tty-screen'
|
98
|
+
TTY::Screen
|
99
|
+
end
|
100
|
+
|
101
|
+
# The unix which utility
|
102
|
+
#
|
103
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
104
|
+
#
|
105
|
+
# @api public
|
106
|
+
def which(*args)
|
107
|
+
require 'tty-which'
|
108
|
+
TTY::Which.which(*args)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Check if executable exists
|
112
|
+
#
|
113
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
114
|
+
#
|
115
|
+
# @api public
|
116
|
+
def exec_exist?(*args)
|
117
|
+
require 'tty-which'
|
118
|
+
TTY::Which.exist?(*args)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
require_relative './util/Inventory'
|
5
|
+
require_relative './util/Query'
|
6
|
+
require_relative './util/animation'
|
7
|
+
|
8
|
+
require 'tty-spinner'
|
9
|
+
require 'colorize'
|
10
|
+
|
11
|
+
module CodingChallenge
|
12
|
+
module Commands
|
13
|
+
class Start < CodingChallenge::Command
|
14
|
+
include Animation
|
15
|
+
|
16
|
+
@@BASE_NAVIGATION_STATES = %w[HOME_MENU SET_INVENTORY_FILE INIT_QUERY VIEW_QUERIES EXITED]
|
17
|
+
@@DEFAULT_PRODUCTS_LIST_URL = 'https://gist.githubusercontent.com/michaelporter/b2743e0cdad0664fa9517c0a6b82cdda/raw/67e4606007391f678c9330ee3a77a9024fce4e64/products.json'
|
18
|
+
|
19
|
+
def initialize(options)
|
20
|
+
@options = options
|
21
|
+
@inventory = nil
|
22
|
+
@base_navigation_state_index = 0
|
23
|
+
@prev_base_navigation_state_index = nil
|
24
|
+
@queries = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def execute(input: $stdin, output: $stdout)
|
28
|
+
cli_args = ARGV.slice(1, ARGV.length).reject { |arg| arg.slice(0, 2) == '--' }
|
29
|
+
|
30
|
+
perform_intro_animation if @options['skip_intro_animation'].nil?
|
31
|
+
|
32
|
+
if cli_args.empty?
|
33
|
+
loading_animation('Loading menu...', 1)
|
34
|
+
else
|
35
|
+
loading_animation('Calculating results...', 2)
|
36
|
+
|
37
|
+
new_inventory = Inventory.new
|
38
|
+
new_inventory.load_products_list_from_default
|
39
|
+
@inventory = new_inventory
|
40
|
+
|
41
|
+
new_query = Query.new(cli_args)
|
42
|
+
query_with_results = @inventory.handle_query(new_query)
|
43
|
+
|
44
|
+
puts 'Results:'.colorize(:yellow)
|
45
|
+
puts query_with_results.results
|
46
|
+
|
47
|
+
@queries.push(query_with_results.formatted_results)
|
48
|
+
end
|
49
|
+
|
50
|
+
exited = false
|
51
|
+
until exited
|
52
|
+
base_navigation_state = @@BASE_NAVIGATION_STATES[@base_navigation_state_index]
|
53
|
+
|
54
|
+
if base_navigation_state == 'HOME_MENU'
|
55
|
+
new_base_navigation_state_index = execute_home_menu
|
56
|
+
elsif base_navigation_state == 'SET_INVENTORY_FILE'
|
57
|
+
new_base_navigation_state_index = execute_set_inventory_file
|
58
|
+
elsif base_navigation_state == 'INIT_QUERY'
|
59
|
+
new_base_navigation_state_index = execute_init_query
|
60
|
+
elsif base_navigation_state == 'VIEW_QUERIES'
|
61
|
+
new_base_navigation_state_index = execute_view_queries
|
62
|
+
elsif base_navigation_state == 'EXITED'
|
63
|
+
exited = true
|
64
|
+
end
|
65
|
+
|
66
|
+
if new_base_navigation_state_index == 'BACK'
|
67
|
+
@base_navigation_state_index = @prev_base_navigation_state_index
|
68
|
+
elsif @base_navigation_state_index != @prev_base_navigation_state
|
69
|
+
@prev_base_navigation_state_index = @base_navigation_state_index
|
70
|
+
@base_navigation_state_index = new_base_navigation_state_index
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
exit(0)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def perform_intro_animation
|
80
|
+
intro_loading_bars_animation
|
81
|
+
puts "\n"
|
82
|
+
intro_title_animation
|
83
|
+
puts "\n"
|
84
|
+
end
|
85
|
+
|
86
|
+
def execute_home_menu
|
87
|
+
menu_prompt = prompt(active_color: :cyan, symbols: { marker: '>' })
|
88
|
+
menu_prompt.select("\n\nHome Menu:") do |menu|
|
89
|
+
menu.choice 'set product list file path', 1
|
90
|
+
menu.choice 'query the product list', 2
|
91
|
+
menu.choice 'view past query results', 3
|
92
|
+
menu.choice 'exit', 4
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def execute_set_inventory_file
|
97
|
+
intro_text =
|
98
|
+
if @inventory.nil?
|
99
|
+
"I don't currently have a product list loaded so I'll need to load one in from a valid JSON file."
|
100
|
+
else
|
101
|
+
"The current product list has been loaded from the file at: #{@inventory.source_uri}"
|
102
|
+
end
|
103
|
+
|
104
|
+
type_effect(intro_text)
|
105
|
+
main_menu_prompt = prompt(active_color: :cyan, symbols: { marker: '>' })
|
106
|
+
main_menu_action_index = main_menu_prompt.select("\n\nWhat would you like to do?:") do |menu|
|
107
|
+
menu.choice 'set product list file from a path', 0
|
108
|
+
menu.choice 'set product list file from a url', 1
|
109
|
+
menu.choice 'set product list file from the default file', 2
|
110
|
+
menu.choice 'go back', 'BACK'
|
111
|
+
end
|
112
|
+
|
113
|
+
return 'BACK' if main_menu_action_index == 'BACK'
|
114
|
+
|
115
|
+
new_inventory = Inventory.new
|
116
|
+
|
117
|
+
if main_menu_action_index == 0
|
118
|
+
done = false
|
119
|
+
until done
|
120
|
+
new_filepath = prompt.ask('Please enter the absolute path to the JSON file (or QUIT to cancel): ')
|
121
|
+
if new_filepath == 'QUIT'
|
122
|
+
done = true
|
123
|
+
else
|
124
|
+
loading_animation('Checking file readability...', 2)
|
125
|
+
begin
|
126
|
+
new_inventory.load_products_list_from_source('FILE PATH', new_filepath)
|
127
|
+
|
128
|
+
raise StandardError if new_inventory.products_list.nil?
|
129
|
+
|
130
|
+
@inventory = new_inventory
|
131
|
+
prompt.say("\nFile loaded successfully!")
|
132
|
+
done = true
|
133
|
+
rescue StandardError
|
134
|
+
prompt.say('Could not read this file!')
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
elsif main_menu_action_index == 1
|
139
|
+
done = false
|
140
|
+
until done
|
141
|
+
new_file_url = prompt.ask('Please enter the URL to the JSON file (or QUIT to cancel): ')
|
142
|
+
if new_file_url == 'QUIT'
|
143
|
+
done = true
|
144
|
+
else
|
145
|
+
spinner = TTY::Spinner.new("[:spinner] fetching file from #{new_file_url}", format: :pulse_2, clear: true)
|
146
|
+
spinner.auto_spin
|
147
|
+
begin
|
148
|
+
new_inventory.load_products_list_from_source('URL', new_file_url)
|
149
|
+
raise StandardError if new_inventory.products_list.nil?
|
150
|
+
|
151
|
+
@inventory = new_inventory
|
152
|
+
prompt.say("\nFile fetched successfully!")
|
153
|
+
done = true
|
154
|
+
rescue StandardError
|
155
|
+
prompt.say('Could not fetch this file!')
|
156
|
+
end
|
157
|
+
spinner.stop
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
elsif main_menu_action_index == 2
|
162
|
+
spinner = TTY::Spinner.new('[:spinner] fetching default file from url', format: :pulse_2, clear: true)
|
163
|
+
spinner.auto_spin
|
164
|
+
begin
|
165
|
+
new_inventory.load_products_list_from_default
|
166
|
+
raise StandardError if new_inventory.products_list.nil?
|
167
|
+
|
168
|
+
@inventory = new_inventory
|
169
|
+
prompt.say("\nDefault file fetched successfully!")
|
170
|
+
rescue StandardError
|
171
|
+
prompt.say('Could not fetch the default file!')
|
172
|
+
end
|
173
|
+
spinner.stop
|
174
|
+
end
|
175
|
+
execute_set_inventory_file
|
176
|
+
end
|
177
|
+
|
178
|
+
def execute_init_query
|
179
|
+
if @inventory.nil?
|
180
|
+
puts 'Inventory was never loaded! Redirection to product list config menu.'.colorize(:red)
|
181
|
+
return 1
|
182
|
+
end
|
183
|
+
args = []
|
184
|
+
|
185
|
+
puts ''
|
186
|
+
puts ''
|
187
|
+
type_effect("First, I'll need the first query argument. This is where you specify the product line.")
|
188
|
+
product_type_arg = read_product_type_arg
|
189
|
+
return 0 if product_type_arg == 'QUIT'
|
190
|
+
|
191
|
+
args.push(product_type_arg)
|
192
|
+
puts "Product Type: #{product_type_arg}".upcase.colorize(:light_blue)
|
193
|
+
puts ''
|
194
|
+
|
195
|
+
type_effect("Next, I'll need the the options query arguments. This is where you specify the product options.")
|
196
|
+
options_args = read_options_args
|
197
|
+
return 0 if options_args == 'QUIT'
|
198
|
+
|
199
|
+
args += options_args
|
200
|
+
puts "Options: #{options_args}".upcase.colorize(:light_blue)
|
201
|
+
puts ''
|
202
|
+
puts ''
|
203
|
+
|
204
|
+
new_query = Query.new(args)
|
205
|
+
query_with_results = @inventory.handle_query(new_query)
|
206
|
+
loading_animation('Calculating...', 2)
|
207
|
+
puts 'Results:'.colorize(:yellow)
|
208
|
+
puts query_with_results.results
|
209
|
+
@queries.push(query_with_results.formatted_results)
|
210
|
+
|
211
|
+
0
|
212
|
+
end
|
213
|
+
|
214
|
+
def read_product_type_arg
|
215
|
+
loop do
|
216
|
+
arg_input = prompt.ask('Please enter product type argument for this query (or QUIT to cancel): ')
|
217
|
+
return arg_input unless arg_input.empty?
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def read_options_args
|
222
|
+
options_args = []
|
223
|
+
loop do
|
224
|
+
arg_input = prompt.ask('Please enter an option argument for this query (or QUIT to cancel, NONE for no arguments, DONE when you are finished): ')
|
225
|
+
|
226
|
+
return 'QUIT' if arg_input == 'QUIT'
|
227
|
+
return [] if arg_input == 'NONE'
|
228
|
+
return options_args if arg_input == 'DONE'
|
229
|
+
|
230
|
+
options_args.push(arg_input) unless arg_input.empty?
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def execute_view_queries
|
235
|
+
if @queries.empty?
|
236
|
+
type_effect("You haven't performed any queries yet!")
|
237
|
+
else
|
238
|
+
puts @queries
|
239
|
+
end
|
240
|
+
|
241
|
+
0
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
████████╗██╗ ██╗███████╗
|
2
|
+
╚══██╔══╝██║ ██║██╔════╝
|
3
|
+
██║ ███████║█████╗
|
4
|
+
██║ ██╔══██║██╔══╝
|
5
|
+
██║ ██║ ██║███████╗
|
6
|
+
╚═╝ ╚═╝ ╚═╝╚══════╝
|
7
|
+
████████╗███████╗███████╗██████╗ ██╗ ██╗██████╗ ██╗ ██╗ ██████╗
|
8
|
+
╚══██╔══╝██╔════╝██╔════╝██╔══██╗██║ ██║██╔══██╗██║ ██║██╔════╝
|
9
|
+
██║ █████╗ █████╗ ██████╔╝██║ ██║██████╔╝██║ ██║██║
|
10
|
+
██║ ██╔══╝ ██╔══╝ ██╔═══╝ ██║ ██║██╔══██╗██║ ██║██║
|
11
|
+
██║ ███████╗███████╗██║ ╚██████╔╝██████╔╝███████╗██║╚██████╗
|
12
|
+
╚═╝ ╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝
|
13
|
+
██████╗ ██████╗ ██████╗ ███████╗
|
14
|
+
██╔════╝██╔═══██╗██╔══██╗██╔════╝
|
15
|
+
██║ ██║ ██║██║ ██║█████╗
|
16
|
+
██║ ██║ ██║██║ ██║██╔══╝
|
17
|
+
╚██████╗╚██████╔╝██████╔╝███████╗
|
18
|
+
╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
|
19
|
+
██████╗██╗ ██╗ █████╗ ██╗ ██╗ ███████╗███╗ ██╗ ██████╗ ███████╗
|
20
|
+
██╔════╝██║ ██║██╔══██╗██║ ██║ ██╔════╝████╗ ██║██╔════╝ ██╔════╝
|
21
|
+
██║ ███████║███████║██║ ██║ █████╗ ██╔██╗ ██║██║ ███╗█████╗
|
22
|
+
██║ ██╔══██║██╔══██║██║ ██║ ██╔══╝ ██║╚██╗██║██║ ██║██╔══╝
|
23
|
+
╚██████╗██║ ██║██║ ██║███████╗███████╗███████╗██║ ╚████║╚██████╔╝███████╗
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
require 'date'
|
6
|
+
|
7
|
+
class Inventory
|
8
|
+
@@DEFAULT_PRODUCTS_LIST_URL = 'https://gist.githubusercontent.com/michaelporter/b2743e0cdad0664fa9517c0a6b82cdda/raw/67e4606007391f678c9330ee3a77a9024fce4e64/products.json'
|
9
|
+
attr_reader :source_type, :source_uri, :products_list
|
10
|
+
def initialize
|
11
|
+
@source_type = nil
|
12
|
+
@source_uri = nil
|
13
|
+
@products_list = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def handle_query(query)
|
17
|
+
remaining_props_seen = {}
|
18
|
+
remaining_props = []
|
19
|
+
|
20
|
+
@products_list.each do |product|
|
21
|
+
next if product['product_type'] != query.product_type
|
22
|
+
|
23
|
+
search_start = query.options.length
|
24
|
+
option_types = product['options'].keys
|
25
|
+
|
26
|
+
(search_start..option_types.length - 1).each do |i|
|
27
|
+
option_type = option_types[i]
|
28
|
+
option_value = product['options'][option_type]
|
29
|
+
|
30
|
+
if remaining_props_seen[option_type].nil?
|
31
|
+
remaining_props_seen[option_type] = {}
|
32
|
+
remaining_props_seen[option_type][option_value] = true
|
33
|
+
remaining_props.push("#{option_type.capitalize}: #{option_value}")
|
34
|
+
elsif !remaining_props_seen[option_type][option_value]
|
35
|
+
remaining_props_seen[option_type][option_value] = true
|
36
|
+
remaining_props[i - search_start] += ", #{option_value}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
query.performed_at = DateTime.now
|
42
|
+
query.results = remaining_props
|
43
|
+
|
44
|
+
query
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_products_list_from_source(source_type, source_uri)
|
48
|
+
if source_type == 'FILE PATH'
|
49
|
+
load_from_file_path(source_uri)
|
50
|
+
elsif source_type == 'URL'
|
51
|
+
load_from_file_url(source_uri)
|
52
|
+
end
|
53
|
+
|
54
|
+
unless @products_list.nil?
|
55
|
+
@source_type = source_type
|
56
|
+
@source_uri = source_uri
|
57
|
+
end
|
58
|
+
|
59
|
+
@products_list
|
60
|
+
end
|
61
|
+
|
62
|
+
def load_products_list_from_default
|
63
|
+
load_from_file_url(@@DEFAULT_PRODUCTS_LIST_URL)
|
64
|
+
|
65
|
+
unless @products_list.nil?
|
66
|
+
@source_type = 'DEFAULT'
|
67
|
+
@source_uri = @@DEFAULT_PRODUCTS_LIST_URL
|
68
|
+
end
|
69
|
+
|
70
|
+
@products_list
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def load_from_file_path(file_path)
|
76
|
+
products_list_file = File.open(file_path)
|
77
|
+
products_list_file_content = products_list_file.read
|
78
|
+
products_list_hash = JSON.parse(products_list_file_content)
|
79
|
+
|
80
|
+
@products_list = products_list_hash
|
81
|
+
rescue StandardError
|
82
|
+
@products_list = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
def load_from_file_url(url)
|
86
|
+
request_uri = URI(url)
|
87
|
+
begin
|
88
|
+
request_response = Net::HTTP.get_response(request_uri)
|
89
|
+
request_response_content = request_response.body
|
90
|
+
products_list_hash = JSON.parse(request_response_content)
|
91
|
+
|
92
|
+
@products_list = products_list_hash
|
93
|
+
rescue StandardError
|
94
|
+
@products_list = nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Query
|
4
|
+
attr_reader :product_type, :options, :results
|
5
|
+
attr_writer :performed_at, :results
|
6
|
+
|
7
|
+
def initialize(query_args)
|
8
|
+
@product_type = query_args[0]
|
9
|
+
@options = query_args.slice(1, query_args.length)
|
10
|
+
@performed_at = nil
|
11
|
+
@results = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def formatted_results
|
15
|
+
results_str = "Performed At #{@performed_at}\n"
|
16
|
+
results_str += " Product Type Arg: #{@product_type}\n"
|
17
|
+
results_str += " Options Args: #{@options.join(', ')}\n"
|
18
|
+
results_str += " Results:\n"
|
19
|
+
results_str += " #{@results.join("\n ")}"
|
20
|
+
|
21
|
+
results_str
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tty-progressbar'
|
4
|
+
require 'tty-spinner'
|
5
|
+
require 'lolcat'
|
6
|
+
|
7
|
+
module Animation
|
8
|
+
def intro_loading_bars_animation
|
9
|
+
bars = TTY::ProgressBar::Multi.new('Prepping for awesomeness 😎 [:bar] :percent', head: '>')
|
10
|
+
bar1 = bars.register('Crossing my fingers I get a job :percent [:bar] ', total: 30, head: '>', output: $stdout)
|
11
|
+
bar2 = bars.register("Doin' a motivation dance :percent [:bar] ", total: 30, head: '>', output: $stdout)
|
12
|
+
|
13
|
+
bars.start
|
14
|
+
|
15
|
+
th1 = Thread.new { 30.times { sleep(0.075); bar1.advance } }
|
16
|
+
th2 = Thread.new { 30.times { sleep(0.05); bar2.advance } }
|
17
|
+
[th1, th2].each(&:join)
|
18
|
+
end
|
19
|
+
|
20
|
+
def intro_title_animation
|
21
|
+
path_dirs = __dir__.split('/')
|
22
|
+
path_dirs.pop
|
23
|
+
|
24
|
+
greeting_title_ascii_file = File.open("#{path_dirs.join('/')}/text/greeting_title_ascii.txt")
|
25
|
+
greeting_title_ascii_str = greeting_title_ascii_file.read
|
26
|
+
system "echo '#{greeting_title_ascii_str}' | lolcat -a -d 1"
|
27
|
+
end
|
28
|
+
|
29
|
+
def loading_animation(text = 'Loading...', delay = 3)
|
30
|
+
spinner = TTY::Spinner.new("[:spinner] #{text}", format: :pulse_2, clear: true)
|
31
|
+
spinner.auto_spin
|
32
|
+
sleep(delay)
|
33
|
+
spinner.stop
|
34
|
+
end
|
35
|
+
|
36
|
+
def type_effect(text)
|
37
|
+
i = 0
|
38
|
+
text.length.times do
|
39
|
+
print text[i]
|
40
|
+
i += 1
|
41
|
+
sleep(0.025)
|
42
|
+
end
|
43
|
+
puts ' '
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
#
|
@@ -0,0 +1 @@
|
|
1
|
+
#
|
metadata
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: coding_challenge
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jorge Navarro
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-07-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.8.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.8.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: lolcat
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '100.0'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 100.0.1
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '100.0'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 100.0.1
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: thor
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.0'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.0.1
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '1.0'
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 1.0.1
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: tty-progressbar
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 0.17.0
|
74
|
+
type: :runtime
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 0.17.0
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: tty-prompt
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: 0.21.0
|
88
|
+
type: :runtime
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - "~>"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 0.21.0
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: tty-spinner
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - "~>"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 0.9.3
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - "~>"
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: 0.9.3
|
109
|
+
description: A Ruby CLI app made for hiring coding challenge.
|
110
|
+
email:
|
111
|
+
- jnavarr56@gmail.com
|
112
|
+
executables:
|
113
|
+
- coding_challenge
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- ".gitignore"
|
118
|
+
- ".rspec"
|
119
|
+
- ".travis.yml"
|
120
|
+
- CODE_OF_CONDUCT.md
|
121
|
+
- Gemfile
|
122
|
+
- Gemfile.lock
|
123
|
+
- LICENSE.txt
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- bin/console
|
127
|
+
- bin/setup
|
128
|
+
- coding_challenge.gemspec
|
129
|
+
- exe/coding_challenge
|
130
|
+
- lib/coding_challenge.rb
|
131
|
+
- lib/coding_challenge/cli.rb
|
132
|
+
- lib/coding_challenge/command.rb
|
133
|
+
- lib/coding_challenge/commands/start.rb
|
134
|
+
- lib/coding_challenge/commands/text/greeting_title_ascii.txt
|
135
|
+
- lib/coding_challenge/commands/util/Inventory.rb
|
136
|
+
- lib/coding_challenge/commands/util/Query.rb
|
137
|
+
- lib/coding_challenge/commands/util/animation.rb
|
138
|
+
- lib/coding_challenge/templates/.gitkeep
|
139
|
+
- lib/coding_challenge/templates/start/.gitkeep
|
140
|
+
- lib/coding_challenge/version.rb
|
141
|
+
homepage: https://github.com/Jnavarr56/REDACTED-3-coding-challenge
|
142
|
+
licenses:
|
143
|
+
- MIT
|
144
|
+
metadata:
|
145
|
+
homepage_uri: https://github.com/Jnavarr56/REDACTED-3-coding-challenge
|
146
|
+
source_code_uri: https://github.com/Jnavarr56/REDACTED-3-coding-challenge
|
147
|
+
changelog_uri: https://github.com/Jnavarr56/REDACTED-3-coding-challenge
|
148
|
+
post_install_message:
|
149
|
+
rdoc_options: []
|
150
|
+
require_paths:
|
151
|
+
- lib
|
152
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: 2.3.0
|
157
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
requirements: []
|
163
|
+
rubygems_version: 3.1.2
|
164
|
+
signing_key:
|
165
|
+
specification_version: 4
|
166
|
+
summary: A hiring coding challenge.
|
167
|
+
test_files: []
|