├── .travis.yml ├── README.md ├── Rakefile.rb ├── bin └── motivator ├── lib ├── colour_output.rb ├── command_parser.rb ├── file_watcher.rb ├── motivator.rb └── quote.yml ├── motivator.gemspec └── tests ├── require.rb ├── stubs └── .txt ├── test_colour_output.rb ├── test_file_watcher.rb └── test_motivator.rb /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | - 2.0.0 5 | - 2.1.8 6 | - 2.2.4 7 | sudo: required 8 | before_install: 9 | - gem build motivator.gemspec 10 | - gem install --dev motivator-1.0.2.gem 11 | deploy: 12 | provider: rubygems 13 | api_key: 14 | secure: PQQP0Vu1T07vaS0HaViDLvVPIQKqCiHdyyPp4I2EOaSEh4gxuYka0mBPZp8P3NlKZ94RSpNIET1yT49R5EvR7nVd9NkGCZ4hI4+nvVGaZ8TB5AeTM57KExXSVbxcE1kmHcEAdVyQo/sO5eEBC4nS5nsG/hVVKN7xqEDDwLyqVxKNRNP/lVTQxqbYLIpwhZWV83aHP2wwlvbQZ6wfooGvaiLVF5sZjev+Unl47j2LeRayQ0UWZAzNFFQBmYRLCeMZ+0FYoKyC9M9yHUZFJuVLXm5yR297ZWCXDBgC23SVvyUUoh0ewvFdl+RKQjjhxl8PtZe72heQaYmJCgyvtDH+7GgoHQ06CbVEXyIV49HfbcqaV6pj9vbxJkDNdJ1/Ij/okNddfZkKY9GI2CL9KHnsal8R/7c2sqGlFb2dnAisxDFBOj//HMnEIFCPXiD677XwIWN+oB0b9SrLV1vOdA14bPw7WONy199V/jDl4gMdFe5TsIg404+s8xBkBmkZ2cJZKLZm2l0DRqCaZF/UuU4dfM2ivbEQO2rzN62ONy0D/50yzsjOU0JsyeajSx2b9eOXg5jeCJMfInFzn/vtxrhtQQPZM6On7XEywmnHwpo9hCz+MWCVqzMsXxl9+4ne5LjppMAZu9zsdOmzy5O4DF/5gR8T3IY3XVVWpuP8pg3AhyM= 15 | gem: motivator 16 | on: 17 | tags: true 18 | repo: hugorut/motivation 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![alt text](https://travis-ci.org/hugorut/motivation.svg?branch=master "build status") 2 | # Motivator 3 | Ruby gem for command line motivation 4 | 5 | Install the gem with 6 | ```sh 7 | gem install motivator 8 | ``` 9 | 10 | Then you can run the gem from your command line. With the motivator executable. Passing the motivator the `motivate` flag as below 11 | 12 | ```sh 13 | motivator --motivate 14 | ``` 15 | 16 | will print out a random quote 17 | 18 | ![alt text](http://s8.postimg.cc/bfhgxz611/Screen_Shot_2015_12_24_at_11_04_42.png "quote") 19 | 20 | The motivate command also takes a second parameter so that you can specify a author. This parameter is an underscored name of the author such as `steve_jobs` or `c_s_lewis` 21 | 22 | Motivation gem also allows you to watch files in a directory and print out a quote when something changes. Usefull for getting through those late night coding sessions. You can run: 23 | 24 | ```sh 25 | motivator --watch [glob,glob] 26 | ``` 27 | The second parameter after the watch flag is a list of globs of the files you wish to watch, e.g. `./**/*` or `./folder/*` 28 | 29 | Then you can have an output similar to this: 30 | ![alt text](http://s13.postimg.cc/4edzmqguf/ezgif_com_resize.gif "watching") 31 | -------------------------------------------------------------------------------- /Rakefile.rb: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | test = Rake::TestTask.new do |t| 4 | t.libs << 'tests' 5 | t.test_files = FileList['tests/*.rb'] 6 | t.verbose = true 7 | end 8 | 9 | desc "Run tests" 10 | task :default => :test -------------------------------------------------------------------------------- /bin/motivator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'motivator' 4 | require 'yaml' 5 | require 'colour_output' 6 | require 'optparse' 7 | require 'command_parser' 8 | require 'file_watcher' 9 | 10 | # get arguments from the command line 11 | parser = CommandParser.new 12 | options = parser.collect(ARGV) 13 | 14 | # set path to quotefile 15 | path_to_quotefile = File.dirname(__FILE__)+'/../lib/quote.yml' 16 | 17 | # use custom quotefile name if provided 18 | path_to_quotefile = options[:quotes] if options[:quotes] 19 | 20 | if File.exist?(path_to_quotefile) 21 | # load the quotes from the yaml file 22 | quotes = YAML.load_file(path_to_quotefile) 23 | # initialise the motivation class 24 | motivator = Motivator.new(quotes['quotes'], ColourOutput.new, FileWatcher.new) 25 | 26 | # set quotes for CommandParser#output_authors 27 | parser.quotes = quotes['quotes'] 28 | 29 | # send the arguments to the motivation object and print the output 30 | # to the console 31 | puts motivator.send(options[:method], options[:arguments]) 32 | else 33 | puts "Could not find quotefile at '#{path_to_quotefile}'." 34 | end 35 | -------------------------------------------------------------------------------- /lib/colour_output.rb: -------------------------------------------------------------------------------- 1 | class ColourOutput 2 | attr_accessor :colours, :default 3 | 4 | # initialize and yield self in order to give a hook to modify class 5 | def initialize 6 | @colours = default_colours 7 | @default = "\033[92m" 8 | 9 | yield self if block_given? 10 | end 11 | 12 | # default colors hash 13 | def default_colours 14 | {primary: "\033[95m", blue: "\033[94m", green: "\033[92m", warning: "\033[93m", } 15 | end 16 | 17 | # print out a random color 18 | def random(string) 19 | random_key = @colours.keys.sample 20 | 21 | self.send(random_key, string) 22 | end 23 | 24 | # if the method is missing we need to wrap the argument in a 25 | # color from the method call 26 | def method_missing(name, *args) 27 | if @colours.include? name 28 | "#{@colours[name]}#{args[0]}\033[0m" 29 | else 30 | "#{@default}#{args[0]}\033[0m" 31 | end 32 | end 33 | end -------------------------------------------------------------------------------- /lib/command_parser.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | 3 | # wrapper class for options parser 4 | class CommandParser 5 | 6 | attr_writer :quotes 7 | 8 | def initialize 9 | @options = options 10 | end 11 | 12 | # default options 13 | def options 14 | { method: nil, arguments: nil} 15 | end 16 | 17 | # collect the command arguments via the option parser class 18 | def collect(arguments) 19 | # build the class options from options parser instance 20 | parser = OptionParser.new do |opts| 21 | opts.banner = "Options for output grasshopper" 22 | opts.separator "\n" 23 | opts.separator "Specifics:" 24 | 25 | opts.on('-m [name]', '--motivate [name]', String, "# Output a motivational quote") do |m| 26 | @options[:method] = 'motivate' 27 | @options[:arguments] = { author: m } 28 | end 29 | 30 | opts.on('-w [glob1,glob2] ', '--watch [glob1,glob2]', Array, '# Watch files & output a message on file/folder change event') do |w| 31 | @options[:method] = 'watch' 32 | @options[:arguments] = { files: w } 33 | end 34 | 35 | opts.on('-q [quotefile]', '--quotes [quotefile]', String, '# Use a specific quotefile') do |q| 36 | @options[:quotes] = q 37 | end 38 | 39 | opts.on_tail('-h', '--help', '# Output command line help options') do 40 | puts opts 41 | exit 42 | end 43 | end 44 | 45 | # set the options from calling the parser if there are any exceptions 46 | # we need to catch them and output the help message 47 | begin 48 | parser.parse!(arguments) 49 | rescue Exception => e 50 | puts parser 51 | exit(1) 52 | end 53 | 54 | # if no valid methods were set we can't execute and therefore we exit and 55 | # put the parser message to the console 56 | if @options[:method].nil? 57 | puts parser 58 | exit(1) 59 | end 60 | 61 | #return the options 62 | @options 63 | end 64 | 65 | #output a pretty list of authors 66 | def output_authors 67 | authors = [] 68 | 69 | quotes.each do |author| 70 | authors << author[0] 71 | end 72 | 73 | authors.join(', ') 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/file_watcher.rb: -------------------------------------------------------------------------------- 1 | class FileWatcher 2 | attr_reader :snapshot, :changed, :event 3 | 4 | def initialize(files = nil, options = {}) 5 | @files = files || ['./*'] 6 | @changed = nil 7 | @event = nil 8 | if not @files.empty? 9 | @snapshot = snapshot_filesystem 10 | end 11 | @options = options 12 | end 13 | 14 | def set_files(files) 15 | @files = files 16 | @snapshot = snapshot_filesystem 17 | end 18 | 19 | # take a snapshot of the given files so we can compare at a given date 20 | def snapshot_filesystem 21 | mtimes = {} 22 | 23 | files = @files.map { |file| Dir[file] }.flatten.uniq 24 | 25 | files.each do |file| 26 | if File.exists? file 27 | mtimes[file] = File.stat(file).mtime 28 | end 29 | end 30 | 31 | mtimes 32 | end 33 | 34 | # have the given files changed if they have then 35 | # set the changed file and return true 36 | def files_changed? 37 | # initialise variables for the 38 | new_snapshot = snapshot_filesystem 39 | has_changed = false 40 | 41 | # take a new snapshot and subtract this from the old snapshot in order to get forward changes 42 | # then add the snapshot to the oposite subtraction to get backward changes 43 | changes = (@snapshot.to_a - new_snapshot.to_a) + (new_snapshot.to_a - @snapshot.to_a) 44 | 45 | # determine the event for each change 46 | changes.each do |change| 47 | if @snapshot.keys.include? change[0] 48 | @changed = {change[0] => change[1]} 49 | @event = (new_snapshot.keys.include? change[0]) ? :change : :delete 50 | has_changed = true 51 | else 52 | @changed = {change[0] => change[1]} 53 | @event = :new 54 | has_changed = true 55 | end 56 | end 57 | 58 | # lets reset the snapshot so that we don't create a forever loop 59 | @snapshot = new_snapshot 60 | 61 | return has_changed 62 | end 63 | 64 | # watching the files given in initialize if the files change call the proc 65 | def watch(have_changed, waiting = nil) 66 | @watching = true 67 | 68 | while @watching 69 | if files_changed? 70 | @watching = have_changed.call(@changed, @event) 71 | end 72 | 73 | sleep(1) 74 | 75 | waiting.call if waiting 76 | end 77 | end 78 | 79 | end -------------------------------------------------------------------------------- /lib/motivator.rb: -------------------------------------------------------------------------------- 1 | class Motivator 2 | attr_accessor :quotes 3 | 4 | def initialize(quotes, colourer, watcher) 5 | @quotes = quotes 6 | @colourer = colourer 7 | @watcher = watcher 8 | end 9 | 10 | # output a string of motivation to the console prepended by the 11 | # quotes author and coloured with ansi character codes 12 | def motivate(opts = {}) 13 | author = (opts[:author].nil?) ? @quotes.keys.sample : opts[:author] 14 | 15 | readable(author) + @colourer.random(" ~ " + get_quote(author, opts[:id])) 16 | end 17 | 18 | # watch the files in the system and call the files changed method as a 19 | # proc when there is an event that we need to hook into 20 | def watch(opts) 21 | @watcher.set_files(opts[:files]) if opts[:files] 22 | @watcher.watch(method(:files_changed)) 23 | end 24 | 25 | # the callable method which contains the logic once files are changed 26 | def files_changed(file, event) 27 | puts motivate 28 | # return true so that the watch loop keeps running 29 | return true 30 | end 31 | 32 | # get a quote, either from random or from specified author or index 33 | def get_quote(author, index = nil) 34 | if index.nil? 35 | quote_length = (@quotes[author].length) - 1 36 | index = Random.rand(0..quote_length) 37 | end 38 | 39 | @quotes[author][index] 40 | end 41 | 42 | # convert from underscores to spaces and capitalise 43 | # so the name is readable in the console 44 | def readable(string) 45 | string.gsub('_', ' ').split.map(&:capitalize).join(' ') 46 | end 47 | end -------------------------------------------------------------------------------- /lib/quote.yml: -------------------------------------------------------------------------------- 1 | #main quotes 2 | quotes: 3 | bruce_lee: 4 | - "Mistakes are always forgivable, if one has the courage to admit them" 5 | - "If you spend too much time thinking about a thing, you'll never get it done" 6 | - "A wise man can learn more from a foolish question than a fool can learn from a wise answer" 7 | steve_jobs: 8 | - "Design is not just what it looks like and feels like. Design is how it works" 9 | - "Sometimes when you innovate, you make mistakes. It is best to admit them quickly, and get on with improving your other innovations" 10 | - "Innovation distinguishes between a leader and a follower" 11 | abraham_lincoln: 12 | - "Nearly all men can stand adversity, but if you want to test a man's character, give him power" 13 | - "Whatever you are, be a good one" 14 | - "Always bear in mind that your own resolution to succeed is more important than any other" 15 | c_s_lewis: 16 | - "I have found a desire within myself that no experience in this world can satisfy; the most probable explanation is that I was made for another world" 17 | - "Friendship is born at that moment when one person says to another: What! You too? I thought I was the only one" 18 | - "You are never too old to set another goal or to dream a new dream" 19 | thomas_edison: 20 | - "I have not failed. I've just found 10,000 ways that won't work" 21 | - "Many of life's failures are people who did not realize how close they were to success when they gave up" 22 | - "Our greatest weakness lies in giving up. The most certain way to succeed is always to try just one more time" 23 | eleanor_roosevelt: 24 | - "No one can make you feel inferior without your consent" 25 | - "Great minds discuss ideas; average minds discuss events; small minds discuss people" 26 | - "The future belongs to those who believe in the beauty of their dreams" 27 | mark_twain: 28 | - "Don't go around saying the world owes you a living. The world owes you nothing. It was here first" 29 | - "I have never let my schooling interfere with my education" 30 | - "If you tell the truth, you don't have to remember anything" 31 | albert_einstein: 32 | - "Two things are infinite: the universe and human stupidity; and I'm not sure about the universe" 33 | - "The important thing is not to stop questioning. Curiosity has its own reason for existing" 34 | - "Science without religion is lame, religion without science is blind" 35 | winston_churchill: 36 | - "Success consists of going from failure to failure without loss of enthusiasm" 37 | - "If you're going through hell, keep going" 38 | - "You have enemies? Good. That means you've stood up for something, sometime in your life" 39 | walt_disney: 40 | - "All our dreams can come true, if we have the courage to pursue them" 41 | - "It's kind of fun to do the impossible" 42 | - "We keep moving forward, opening new doors, and doing new things, because we're curious and curiosity keeps leading us down new paths" 43 | walt_whitman: 44 | - "Nothing exterior to me will ever take command of me." 45 | ralph_waldo_emerson: 46 | - "Do not go where the path may lead, go instead where there is no path and leave a trail." 47 | - "The task ahead of us is never as great as the power behind us." 48 | aristotle: 49 | - "We are what we repeatedly do. Excellence, then, is not an act, but a habit." 50 | frederick_douglas: 51 | - "If there are no struggles, there is no progress." 52 | alexander_the_great: 53 | - "Nothing is impossible to him who will try." 54 | sir_ken_robinson: 55 | - "If you are not prepared to be wrong, you will not come up with anything original." 56 | brian_kernighan: 57 | - "Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?" 58 | tupac_shakur: 59 | - "For every dark night there's a brighter day." 60 | anon: 61 | - "If you want to achieve greatness stop asking for permission." 62 | - "To live a creative life, we must lose our fear of being wrong." 63 | - "Trust because you are willing to accept the risk, not because it's safe or certain." 64 | - "If you do what you always did, you will get what you always got." 65 | - "Good things come to people who wait, but better things come to those who go out and get them." 66 | - "The meaning of life is to find your gift. The purpose of life is to give it away." 67 | - "You can do anything, but not everything." 68 | wade_wilson: 69 | - "Maximum effort" 70 | -------------------------------------------------------------------------------- /motivator.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'motivator' 3 | s.version = '1.0.2' 4 | s.date = '2015-12-16' 5 | s.summary = "Command line motivator" 6 | s.description = "Quotes to motivate from the command line" 7 | s.authors = ["Hugo Rut"] 8 | s.homepage = "https://rubygems.org/gems/motivator" 9 | s.email = 'hugorut@gmail.com' 10 | s.files = Dir["./lib/*"] 11 | s.license = 'MIT' 12 | s.executables << 'motivator' 13 | s.add_development_dependency('mocha', '~> 1.1.0', '>= 1.1.0') 14 | s.add_development_dependency('minitest', '~> 5.8.3', '>= 5.8.3') 15 | end -------------------------------------------------------------------------------- /tests/require.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | gem 'minitest' 3 | gem 'mocha' 4 | require 'minitest/autorun' 5 | require 'mocha/mini_test' -------------------------------------------------------------------------------- /tests/stubs/.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugorut/motivation/a9a4adce31a790672fce599b16e43c1b4e500c09/tests/stubs/.txt -------------------------------------------------------------------------------- /tests/test_colour_output.rb: -------------------------------------------------------------------------------- 1 | require_relative 'require' 2 | require './lib/colour_output' 3 | 4 | class TestColourOutput < MiniTest::Test 5 | 6 | def test_yields_self_on_init 7 | ColourOutput.new do |color| 8 | assert_kind_of(ColourOutput, color) 9 | end 10 | end 11 | 12 | def test_string_wrapped_in_specified_color_from_missing_method_call 13 | color = "\033[91m" 14 | ending = "\033[0m" 15 | string = 'this is a test string' 16 | 17 | colourer = ColourOutput.new do |c| 18 | c.colours[:test] = color 19 | end 20 | 21 | output = colourer.test(string) 22 | assert_equal(color + string + ending, output) 23 | end 24 | 25 | def test_string_wrapped_in_default_color_from_if_name_does_not_exist_in_hash 26 | default = "\033[91m" 27 | ending = "\033[0m" 28 | string = 'this is a test string' 29 | 30 | colourer = ColourOutput.new do |c| 31 | c.default = default 32 | end 33 | 34 | output = colourer.test(string) 35 | assert_equal(default + string + ending, output) 36 | end 37 | 38 | def test_random_calls_a_random_colour_to_wrap_around_string 39 | string = 'this is a test string' 40 | colourer = ColourOutput.new 41 | 42 | output = colourer.random(string) 43 | assert_includes(output, string) 44 | end 45 | 46 | end -------------------------------------------------------------------------------- /tests/test_file_watcher.rb: -------------------------------------------------------------------------------- 1 | require_relative 'require' 2 | require './lib/file_watcher' 3 | 4 | class TestFileWatcher < MiniTest::Test 5 | 6 | def setup 7 | @files = ['./tests/stubs/*'] 8 | File.open("./tests/stubs/changes.stub", "w") { |file| file.write('test') } 9 | @file_watcher = FileWatcher.new(@files) 10 | end 11 | 12 | def teardown 13 | if File.exists? "./tests/stubs/changes.stub" 14 | File.delete("./tests/stubs/changes.stub") 15 | end 16 | end 17 | 18 | def test_snapshot_returns_hash_of_mtime_of_files 19 | @file_watcher.snapshot_filesystem 20 | snapshot = @file_watcher.snapshot 21 | 22 | assert_includes(snapshot.keys, './tests/stubs/changes.stub') 23 | end 24 | 25 | def test_files_changed_returns_files_changes 26 | # sleep so that the files are not saved in the same second 27 | sleep(1) 28 | 29 | File.open("./tests/stubs/changes.stub", "w") { |file| file.write('tedddst') } 30 | @file_watcher.files_changed? 31 | 32 | assert(@file_watcher.changed['./tests/stubs/changes.stub']) 33 | assert_equal(@file_watcher.event, :change) 34 | end 35 | 36 | def test_files_added_returns_new_file_event 37 | File.open("./tests/stubs/changes2.stub", "w") { |file| file.write('tedddst') } 38 | @file_watcher.files_changed? 39 | 40 | assert(@file_watcher.changed['./tests/stubs/changes2.stub']) 41 | assert_equal(@file_watcher.event, :new) 42 | 43 | File.delete("./tests/stubs/changes2.stub") 44 | end 45 | 46 | def test_files_added_returns_delete_file_event 47 | sleep(1) 48 | 49 | File.delete("./tests/stubs/changes.stub") 50 | @file_watcher.files_changed? 51 | 52 | assert(@file_watcher.changed['./tests/stubs/changes.stub']) 53 | assert_equal(@file_watcher.event, :delete) 54 | end 55 | 56 | def test_files_changed_returns_nil_as_none_changed 57 | @file_watcher.files_changed? 58 | 59 | assert_nil(@file_watcher.changed) 60 | end 61 | 62 | def test_watch_method_envokes_proc 63 | callable = Proc.new do |file, event| 64 | key = file.has_key? "./tests/stubs/changes.stub" 65 | assert(true, key) 66 | assert(event, :change) 67 | return false 68 | end 69 | 70 | sleep(1) 71 | File.open("./tests/stubs/changes.stub", "w") { |file| file.write('teafdsdddst') } 72 | 73 | @file_watcher.watch(callable) 74 | end 75 | end -------------------------------------------------------------------------------- /tests/test_motivator.rb: -------------------------------------------------------------------------------- 1 | require './lib/motivator' 2 | require_relative 'require' 3 | require 'yaml' 4 | 5 | class TestMotivator < MiniTest::Test 6 | 7 | def setup 8 | @quotes = YAML.load_file('./lib/quote.yml') 9 | @colourer = mock('ColourOutput') 10 | @watcher = mock('FileWatcher') 11 | @motivation = Motivator.new(@quotes['quotes'], @colourer, @watcher) 12 | end 13 | 14 | def test_motivation_class_exists 15 | assert_kind_of(Motivator, @motivation) 16 | end 17 | 18 | def test_readable_returns_a_titilized_split_name 19 | actual = @motivation.readable('steve_jobs') 20 | assert_equal('Steve Jobs', actual) 21 | end 22 | 23 | def test_get_specified_author 24 | jobs_quotes = @quotes['quotes']['steve_jobs'] 25 | assert_includes(jobs_quotes, @motivation.get_quote('steve_jobs')) 26 | end 27 | 28 | def test_get_specified_author_and_index 29 | quote = @quotes['quotes']['steve_jobs'][1] 30 | assert_same(quote, @motivation.get_quote('steve_jobs', 1)) 31 | end 32 | 33 | def test_motivate_string_wrapped_in_string_and_color 34 | quote = @quotes['quotes']['steve_jobs'][1] 35 | @colourer.expects('random').once.with(' ~ ' + quote).returns(' ~ ' + quote) 36 | 37 | output = @motivation.motivate({author: 'steve_jobs', id: 1}) 38 | assert_equal('Steve Jobs ~ ' + quote, output) 39 | end 40 | 41 | end --------------------------------------------------------------------------------