ifocustt 0.0.1a

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 853ef834580edb4e7919a959fe054a533b3d1e6b
4
+ data.tar.gz: 42ed12e13f63862715ab0df293659d1bd3ffe7a2
5
+ SHA512:
6
+ metadata.gz: cacb2b8a84e8e0fd33a0c9e44bcb69789ad5f2dbcbe5d4517419d37f7cb3e174dd55a434b226f70b2f72f29bdee2899a9a48261548d0072bf734eecde457391e
7
+ data.tar.gz: 522c829bd63e1162f07147eeafcb9097dc7aae06c4305181b6a882c82ff1c6b746a777deaa15eefd70b11f6922c97d1c7704fa2b603b037e2530711a853bd7d8
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2017 John Mason
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Binary file
data/bin/focus ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ file = File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__)
4
+ $LOAD_PATH.unshift(File.expand_path("../lib", file))
5
+
6
+ require "focus"
7
+
8
+ Focus::CLI.run! ARGV
data/bin/focus.sh ADDED
@@ -0,0 +1,136 @@
1
+ #!/bin/sh
2
+ #
3
+ # USAGE:
4
+ #
5
+ # focus [minutes=25] [target=?|JIRA ticket in git branch]
6
+
7
+ git_branch_name() { git rev-parse --abbrev-ref HEAD 2>/dev/null; }
8
+ jira_ticket() {
9
+ local branch=$(git_branch_name)
10
+ local ticket=$(echo $branch | sed 's/.*\([a-zA-Z]\{2\}-[0-9]\{2\}\)/\1/') # TODO
11
+ if [ "$ticket" == "$branch" ]
12
+ then
13
+ echo "?"
14
+ else
15
+ echo "$ticket"
16
+ fi
17
+ }
18
+
19
+ FOCUS_MINUTES="${1-25}"
20
+ FOCUS_TARGET="${2-$(jira_ticket)}"
21
+ FOCUS_HISTORY_FILE="$HOME/.focus_history"
22
+
23
+ info() { printf -- "\033[00;34m..\033[0m %s " "$*"; }
24
+ ok() { echo "\033[00;32m✓\033[0m"; }
25
+ abort() { echo "!!! $*" >&2; exit 1; }
26
+ epoch() { date +%s; }
27
+ print_line() { printf "%-15s %-15s %-15s\n" "$1" "$2" "$3"; }
28
+ title() { print_line "EPOCH" "FOCUS_TIME" "TARGET"; }
29
+ event() { print_line "$(epoch)" "$FOCUS_MINUTES" "$@"; }
30
+ add_to_history() { "$@" >> "$FOCUS_HISTORY_FILE"; }
31
+
32
+ log_focus() {
33
+ info "Updating ~/.focus_history"
34
+ if [ ! -f "$FOCUS_HISTORY_FILE" ]; then add_to_history title; fi
35
+ add_to_history event "$FOCUS_TARGET"
36
+ ok
37
+ }
38
+
39
+ quietly() {
40
+ "$@" &>/dev/null
41
+ }
42
+
43
+ SLACK_TOKEN=$(cat ~/.secrets/slack-token 2>/dev/null)
44
+ SLACK_AWAY="away"
45
+ SLACK_AVAILABLE="auto"
46
+
47
+ change_slack_presence() {
48
+ if [ -n "$SLACK_TOKEN" ]
49
+ then
50
+ local presence="$1"
51
+ info "Changing slack presence to $presence"
52
+ local setpresenceurl="https://slack.com/api/users.setPresence?token=$SLACK_TOKEN&presence=$presence&pretty=1"
53
+ curl -X POST "$setpresenceurl" &>/dev/null && ok
54
+ fi
55
+ }
56
+
57
+ BLINK_PORT="8754"
58
+ BLINK_SERVER="http://localhost:$BLINK_PORT/blink1"
59
+ BLINK_RED="23EA5B5B"
60
+ BLINK_GREEN="233AF23A"
61
+ BLINK_ORANGE="23FFBF00"
62
+ start_blink_server() {
63
+ if ! command -v blink1-server &> /dev/null
64
+ then
65
+ abort 'Blink server not installed. Run `npm install -g node-blink1-server`.'
66
+ else
67
+ quietly curl "$BLINK_SERVER" || blink1-server "$BLINK_PORT" &
68
+ fi
69
+ }
70
+
71
+ change_blink_color() {
72
+ local color="$1"
73
+ info "Changing blink to $color"
74
+ curl "$BLINK_SERVER/fadeToRGB?rgb=%$color" &>/dev/null && ok
75
+ }
76
+
77
+ strobe_blink_color() {
78
+ local color="$1"
79
+ local seconds="$2"
80
+ local blink_rate="0.5" # both on and off together finishes in one second
81
+ curl "$BLINK_SERVER/blink?rgb=%$color&time=$blink_rate&repeats=$seconds" &>/dev/null && ok
82
+ }
83
+
84
+ IFTTT_MAKER_KEY=$(cat ~/.secrets/ifttt-maker 2>/dev/null)
85
+ start_rescue_time() {
86
+ if [ -n "$IFTTT_MAKER_KEY" ]
87
+ then
88
+ local event="rescue_time_focus_start"
89
+ info "Firing $event blink(1) event to IFTT"
90
+ curl -X POST https://maker.ifttt.com/trigger/${event}/with/key/${IFTTT_MAKER_KEY} &>/dev/null && ok
91
+ fi
92
+ }
93
+
94
+ start_slack_do_not_disturb() {
95
+ if [ -n "$SLACK_TOKEN" ]
96
+ then
97
+ local url="https://slack.com/api/dnd.setSnooze?token=$SLACK_TOKEN&num_minutes=$FOCUS_MINUTES&pretty=1"
98
+ info "Changing slack to do not disturb for $FOCUS_MINUTES minutes"
99
+ curl -X POST "$url" &>/dev/null && ok
100
+ fi
101
+ }
102
+
103
+ disable_mac_notification_center() {
104
+ if [ "$(uname)" == "Darwin" ]
105
+ then
106
+ info "Disabling Mac OSX notification center"
107
+ quietly launchctl unload -w /System/Library/LaunchAgents/com.apple.notificationcenterui.plist
108
+ ok
109
+ fi
110
+ }
111
+
112
+ enable_mac_notification_center() {
113
+ if [ "$(uname)" == "Darwin" ]
114
+ then
115
+ launchctl load -w /System/Library/LaunchAgents/com.apple.notificationcenterui.plist
116
+ fi
117
+ }
118
+
119
+ cleanup(){
120
+ local focus_seconds=$(echo "$FOCUS_MINUTES * 60" | bc)
121
+ local break_seconds=$(echo "$focus_seconds * .2" | bc)
122
+ sleep "$focus_seconds" && quietly strobe_blink_color "$BLINK_ORANGE" "$break_seconds"
123
+ sleep "$break_seconds" && \
124
+ quietly change_slack_presence "$SLACK_AVAILABLE"
125
+ quietly enable_mac_notification_center
126
+ quietly change_blink_color "$BLINK_GREEN"
127
+ }
128
+
129
+ disable_mac_notification_center
130
+ start_rescue_time
131
+ start_blink_server
132
+ change_slack_presence "$SLACK_AWAY"
133
+ start_slack_do_not_disturb
134
+ log_focus
135
+ change_blink_color "$BLINK_RED"
136
+ cleanup &
@@ -0,0 +1,13 @@
1
+ ---
2
+ SecretsFile: "~/.secrets"
3
+
4
+ OnFocus:
5
+ ToggleMacNotificationCenter:
6
+ Enabled: false
7
+ LogFocusTime:
8
+
9
+ OnBreak:
10
+
11
+ OnCompletion:
12
+ ToggleMacNotificationCenter:
13
+ Enabled: true
@@ -0,0 +1,39 @@
1
+ require "httparty"
2
+ require "interactor"
3
+
4
+ module Focus
5
+ class Action
6
+ include Interactor
7
+
8
+ private
9
+
10
+ def focus_target
11
+ context.focus_target || context.target || "?"
12
+ end
13
+
14
+ def focus_minutes
15
+ context.focus_minutes || context.minutes.to_i
16
+ end
17
+
18
+ def focus_seconds
19
+ context.minutes.to_f * 60
20
+ end
21
+
22
+ def break_seconds
23
+ focus_seconds * 0.2
24
+ end
25
+
26
+ def evaluate_step(klass, args)
27
+ action = klass.to_s.gsub(/^.*::/, "")
28
+ step = "Running #{action}..."
29
+
30
+ Focus::STDOUT.step(step, quiet: context.quiet) do
31
+ klass.call(args)
32
+ end
33
+ end
34
+
35
+ def debug_output(*args)
36
+ Focus::STDOUT.debug_output args
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ module Focus
2
+ class ChangeBlinkColor < Action
3
+ def call
4
+ context.fail! unless perform
5
+ end
6
+
7
+ private
8
+
9
+ def perform
10
+ url = "#{Config.blink_server}/fadeToRGB?rgb=%#{context.color}"
11
+ res = HTTParty.get(url)
12
+ res.code == 200
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ module Focus
2
+ class ChangeSlackDoNotDisturb < Action
3
+ def call
4
+ context.fail! unless perform
5
+ end
6
+
7
+ private
8
+
9
+ def perform
10
+ Config.slack_token ? HTTParty.post(url) : true
11
+ end
12
+
13
+ def url
14
+ if context.enabled
15
+ "#{base_url}&num_minutes=#{focus_minutes}"
16
+ else
17
+ base_url
18
+ end
19
+ end
20
+
21
+ def base_url
22
+ "#{Config.slack_api_url}/#{action}?token=#{Config.slack_token}"
23
+ end
24
+
25
+ def action
26
+ context.enabled ? "dnd.setSnooze" : "dnd.endSnooze"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ module Focus
2
+ class ChangeSlackPresence < Action
3
+ def call
4
+ context.fail! unless perform
5
+ end
6
+
7
+ private
8
+
9
+ def perform
10
+ token = Config.slack_token
11
+ if token
12
+ url = "#{Config.slack_api_url}/users.setPresence?token=#{token}&presence=#{presence}"
13
+ res = HTTParty.post url
14
+ res.code == 200
15
+ else
16
+ true
17
+ end
18
+ end
19
+
20
+ def presence
21
+ context.presence
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,40 @@
1
+ require "fileutils"
2
+ module Focus
3
+ class LogFocusTime < Action
4
+ def call
5
+ update_log
6
+ end
7
+
8
+ private
9
+
10
+ def update_log
11
+ file = File.expand_path Config.focus_history_file
12
+ new_file = File.exist?(file)
13
+
14
+ File.open(file, "a") do |fo|
15
+ fo.puts title unless new_file
16
+ fo.puts entry(Time.now.to_i, focus_minutes, focus_target)
17
+ end
18
+ end
19
+
20
+ def entry(epoch, min, target)
21
+ line % [epoch, min, target]
22
+ end
23
+
24
+ def line
25
+ "%-20s %-20s %s"
26
+ end
27
+
28
+ def title
29
+ entry "EPOCH", "FOCUS_MINUTES", "TARGET"
30
+ end
31
+
32
+ def focus_target
33
+ jira_ticket || super
34
+ end
35
+
36
+ def jira_ticket
37
+ @ticket ||= Utils::ParseJiraTicketFromGitBranch.call.jira_ticket
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,38 @@
1
+ module Focus
2
+ class PostWorkLogToJira < Action
3
+ def call
4
+ return unless jira_ticket && focus_seconds >= 60
5
+ HTTParty.post(issue_url, options)
6
+ end
7
+
8
+ private
9
+
10
+ def issue_url
11
+ "#{Config.jira_url}/issue/#{jira_ticket}/worklog"
12
+ end
13
+
14
+ def options
15
+ {
16
+ body: body.to_json,
17
+ basic_auth: auth,
18
+ headers: headers
19
+ }
20
+ end
21
+
22
+ def body
23
+ { timeSpentSeconds: focus_seconds, comment: context.comment.to_s }
24
+ end
25
+
26
+ def auth
27
+ { username: Config.jira_username, password: Config.jira_password }
28
+ end
29
+
30
+ def headers
31
+ { "Content-Type" => "application/json" }
32
+ end
33
+
34
+ def jira_ticket
35
+ context.jira_ticket ||= Utils::ParseJiraTicketFromGitBranch.call.jira_ticket
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,7 @@
1
+ module Focus
2
+ class RunShellCommand < Action
3
+ def call
4
+ system(context.cmd)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ module Focus
2
+ class StartBlinkServer < Action
3
+ def call
4
+ verify_blink_server_installed
5
+ HTTParty.get(Config.blink_server)
6
+ rescue Errno::ECONNREFUSED
7
+ start_blink_server
8
+ end
9
+
10
+ private
11
+
12
+ def verify_blink_server_installed
13
+ error_msg = \
14
+ "Blink server not installed. Run `npm install -g node-blink1-server`."
15
+
16
+ raise ArgumentError, error_msg unless blink_server_installed?
17
+ end
18
+
19
+ def blink_server_installed?
20
+ system("command -v blink1-server &>/dev/null")
21
+ end
22
+
23
+ def start_blink_server
24
+ fork do
25
+ system("blink1-server #{Config.blink_port} &>/dev/null")
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,136 @@
1
+ require "yaml"
2
+ require "tty"
3
+
4
+ module Focus
5
+ class StartFocusTime < Action
6
+ DEFAULT_CONTEXT_KEYS = %i(minutes target quiet daemonize).freeze
7
+
8
+ attr_reader :action
9
+
10
+ def call
11
+ context.actions = ConfigLoader.load("actions")
12
+ focus
13
+ take_break
14
+ cleanup
15
+ happy_message
16
+ end
17
+
18
+ private
19
+
20
+ def focus
21
+ @action = :focus
22
+
23
+ if context.daemonize
24
+ fork { perform_actions "OnFocus" }
25
+ else
26
+ perform_actions "OnFocus"
27
+ handle_progress_bar
28
+ end
29
+ end
30
+
31
+ def take_break
32
+ @action = :break
33
+ if context.daemonize
34
+ fork { perform_actions "OnBreak" }
35
+ else
36
+ perform_actions "OnBreak"
37
+ handle_progress_bar
38
+ end
39
+ end
40
+
41
+ def cleanup
42
+ if context.daemonize
43
+ fork { perform_actions "OnCompletion" }
44
+ else
45
+ perform_actions "OnCompletion"
46
+ end
47
+ end
48
+
49
+ def progress_bar
50
+ bar_type = @bar ? @bar.format.split.first : :not_defined
51
+ return @bar if bar_type == action.to_s
52
+
53
+ @bar = build_progress_bar
54
+ end
55
+
56
+ def build_progress_bar
57
+ TTY::ProgressBar.new "#{action} [:bar] :elapsed" do |config|
58
+ config.total = seconds_for_action
59
+ config.interval = 1
60
+ config.width = 40
61
+ end
62
+ end
63
+
64
+ def seconds_for_action
65
+ case action
66
+ when :focus then focus_seconds
67
+ when :break then break_seconds
68
+ else
69
+ raise "Unknown action: '#{action}'"
70
+ end
71
+ end
72
+
73
+ def every_second
74
+ end_time = Time.now + seconds_for_action
75
+
76
+ while Time.now < end_time
77
+ timestamp = Time.now
78
+ yield
79
+ interval = 1 - (Time.now - timestamp)
80
+ sleep(interval) if interval.positive?
81
+ end
82
+ end
83
+
84
+ def handle_progress_bar
85
+ every_second { progress_bar.advance unless context.daemonize }
86
+ end
87
+
88
+ def perform_actions(event)
89
+ actions = context.actions.shift[event]
90
+ return unless actions
91
+ Focus::STDOUT.print_line "Starting #{event}...\r", quiet: context.quiet
92
+
93
+ actions.each do |hsh|
94
+ hsh.each do |action, keyword_arguments|
95
+ klass = constantize(action)
96
+ args = downcase(keyword_arguments)
97
+
98
+ evaluate_step klass, with_default_context(args)
99
+ end
100
+ end
101
+ end
102
+
103
+ def with_default_context(args)
104
+ hsh = args.to_h
105
+ hsh.merge default_hsh
106
+ end
107
+
108
+ def default_hsh
109
+ slice_hash context.to_h, DEFAULT_CONTEXT_KEYS
110
+ end
111
+
112
+ def slice_hash(hsh, *args)
113
+ keys = args.flatten
114
+ hsh.select { |k, _v| keys.include? k }
115
+ end
116
+
117
+ def downcase(thing)
118
+ return unless thing
119
+ return thing.underscore if thing.respond_to? :underscore
120
+
121
+ thing.each_with_object({}) do |(key, value), obj|
122
+ obj[key.underscore] = value
123
+ end
124
+ end
125
+
126
+ def happy_message
127
+ return if context.quiet
128
+ Focus::STDOUT.puts_line nil
129
+ Focus::STDOUT.puts_line "Complete!"
130
+ end
131
+
132
+ def constantize(str)
133
+ Object.const_get "Focus::#{str}"
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,23 @@
1
+ module Focus
2
+ class StartRescueTime < Action
3
+ def call
4
+ context.fail! unless perform
5
+ end
6
+
7
+ private
8
+
9
+ def perform
10
+ token = Config.ifttt_maker_key
11
+ if token
12
+ url = "https://maker.ifttt.com/trigger/#{event}/with/key/#{token}"
13
+ HTTParty.post url
14
+ else
15
+ true
16
+ end
17
+ end
18
+
19
+ def event
20
+ "rescue_time_focus_start"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module Focus
2
+ class StrobeBlinkColor < Action
3
+ def call
4
+ context.fail! unless perform
5
+ end
6
+
7
+ private
8
+
9
+ def perform
10
+ url = "#{Config.blink_server}/pattern?rgb=%#{context.color}&time=1.5&repeats=0"
11
+ res = HTTParty.get(url)
12
+ res.code == 200
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ module Focus
2
+ class ToggleMacNotificationCenter < Action
3
+ def call
4
+ toggle if uname == "Darwin"
5
+ end
6
+
7
+ private
8
+
9
+ def uname
10
+ `uname`.chomp
11
+ end
12
+
13
+ def toggle
14
+ `launchctl #{setting} -w /System/Library/LaunchAgents/com.apple.notificationcenterui.plist &>/dev/null`
15
+
16
+ if context.enabled?
17
+ `killall NotificationCenter &>/dev/null`
18
+ else
19
+ `open -a NotificationCenter &>/dev/null`
20
+ end
21
+ end
22
+
23
+ def setting
24
+ context.enabled ? "load" : "unload"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ actions = Dir.glob("../focus/lib/focus/actions/*").map do |file|
2
+ File.expand_path file
3
+ end
4
+
5
+ actions.each { |file| require file }
data/lib/focus/cli.rb ADDED
@@ -0,0 +1,59 @@
1
+ require "optparse"
2
+ require "ostruct"
3
+ require "pp"
4
+
5
+ module Focus
6
+ class Parser
7
+ DEFAULT_VALUES = {
8
+ minutes: 25,
9
+ quiet: true
10
+ }.freeze
11
+
12
+ class << self
13
+ def parse(options)
14
+ args = OpenStruct.new(DEFAULT_VALUES)
15
+ parser = build_parser(args)
16
+ parser.parse!(options)
17
+ args
18
+ end
19
+
20
+ private
21
+
22
+ def build_parser(args) # rubocop:disable MethodLength
23
+ OptionParser.new do |opts|
24
+ opts.banner = "Usage: example.rb [options]"
25
+
26
+ opts.on("-v", "--verbose", "Run focus with more verbose STDOUT") do
27
+ args.quiet = false
28
+ end
29
+
30
+ opts.on("-d", "--daemonize", "Allow focus to be forked to the background") do
31
+ args.daemonize = true
32
+ end
33
+
34
+ opts.on("-tTIME", "--time=TIME", "Alias to minutes") do |t|
35
+ args.minutes = t
36
+ end
37
+
38
+ opts.on("-mMINUTES", "--minutes=MINUTES", "How many minutes to focus.") do |m|
39
+ args.minutes = m
40
+ end
41
+
42
+ opts.on("-h", "--help", "Prints this help") do
43
+ puts opts
44
+ exit
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ class CLI
52
+ class << self
53
+ def run!(argv)
54
+ options = Parser.parse(argv)
55
+ StartFocusTime.call(options.to_h)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,65 @@
1
+ require "dotenv"
2
+
3
+ module Focus
4
+ class Config < OpenStruct
5
+ class << self
6
+ def method_missing(m, *args, &block) # rubocop:disable MethodMissing
7
+ ENV[m.to_s.upcase] || config.send(m, *args, &block)
8
+ end
9
+
10
+ def config
11
+ @config ||= new
12
+ end
13
+ end
14
+
15
+ def initialize
16
+ source_env
17
+ super(configurations)
18
+ ingest _hardcoded
19
+ end
20
+
21
+ private
22
+
23
+ def configurations
24
+ defaults.merge(custom_config)
25
+ end
26
+
27
+ def ingest(hsh)
28
+ hsh.each do |key, value|
29
+ send("#{key}=", value)
30
+ end
31
+ end
32
+
33
+ def custom_config
34
+ @custom_config ||= Focus::ConfigLoader.load("config")
35
+ end
36
+
37
+ def defaults
38
+ {
39
+ "blink_port" => 8754,
40
+ "focus_history_file" => "#{ENV['HOME']}/.focus_history",
41
+ "focus_minutes" => 25,
42
+ "slack_api_url" => "https://slack.com/api"
43
+ }
44
+ end
45
+
46
+ def _hardcoded
47
+ raise "`blink_port` was not defined" unless blink_port
48
+
49
+ {
50
+ "blink_server" => "http://localhost:#{blink_port}/blink1",
51
+ "slack_available" => "auto",
52
+ "slack_away" => "away"
53
+ }
54
+ end
55
+
56
+ def source_env
57
+ return unless custom_config.respond_to? :keys
58
+ relative_path = custom_config["env_file"] || ".env"
59
+ path = File.expand_path relative_path
60
+
61
+ return unless File.exist?(path)
62
+ Dotenv.load path
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,29 @@
1
+ module Focus
2
+ class ConfigLoader
3
+ class << self
4
+ def load(config)
5
+ YAML.load_file(config_file)[config]
6
+ end
7
+
8
+ private
9
+
10
+ def config_file
11
+ opts = {
12
+ project_file: "./.focus.yml",
13
+ focus_repo_file: "#{Focus.root}/.focus.yml",
14
+ home_file: "#{ENV['HOME']}/.focus.yml",
15
+ default_file: "#{Focus.root}/config/default.yml"
16
+ }
17
+
18
+ opts.each do |_k, file|
19
+ break file if exist?(file)
20
+ end
21
+ end
22
+
23
+ def exist?(path)
24
+ full_path = File.expand_path(path)
25
+ File.exist?(full_path) || File.symlink?(full_path)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ require "paint"
2
+
3
+ module Focus
4
+ module Formatter
5
+ module_function
6
+
7
+ def step(step)
8
+ blue("==>") + " #{step}"
9
+ end
10
+
11
+ def error(error)
12
+ red("!!! Error:") + " #{error}"
13
+ end
14
+
15
+ def blue(string)
16
+ Paint[string, :blue]
17
+ end
18
+
19
+ def red(string)
20
+ Paint[string, :red, :bright]
21
+ end
22
+
23
+ def ok
24
+ " " + Paint["OK", :green]
25
+ end
26
+
27
+ def pastel
28
+ @pastel ||= Pastel.new
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ module Focus
2
+ class STDOUT
3
+ class << self
4
+ def puts_line(str, opts = { quiet: false })
5
+ quiet = opts[:quiet]
6
+ ::STDOUT.puts str unless quiet
7
+ end
8
+
9
+ def print_line(str, opts = { quiet: false })
10
+ quiet = opts[:quiet]
11
+ ::STDOUT.print str unless quiet
12
+ end
13
+
14
+ def step(string, opts = { quiet: false })
15
+ quiet = opts[:quiet]
16
+ print_line(Focus::Formatter.step(string), quiet: quiet)
17
+ yield
18
+ puts_line Focus::Formatter.ok, quiet: quiet
19
+ end
20
+
21
+ def debug_output(str)
22
+ ::STDOUT.puts str if $DEBUG
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ class String
2
+ def underscore
3
+ gsub(/::/, "/")
4
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
5
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
6
+ .tr("-", "_")
7
+ .downcase
8
+ end
9
+ end
data/lib/focus/util.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Focus
2
+ class Util
3
+ include Interactor
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ module Focus
2
+ module Utils
3
+ class ParseJiraTicketFromGitBranch < Util
4
+ JIRA_TICKET_RGX = /[a-z]+-\d+/i
5
+
6
+ def call
7
+ context.jira_ticket = jira_ticket
8
+ end
9
+
10
+ private
11
+
12
+ def jira_ticket
13
+ git_branch = parse_git_branch
14
+ git_branch.scan(JIRA_TICKET_RGX).first
15
+ end
16
+
17
+ def parse_git_branch
18
+ branch = `git rev-parse --abbrev-ref HEAD 2>/dev/null`.chomp
19
+ branch.empty? ? nil : branch
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ utils = Dir.glob("../focus/lib/focus/utils/*").map do |file|
2
+ File.expand_path file
3
+ end
4
+
5
+ utils.each { |file| require file }
@@ -0,0 +1,3 @@
1
+ module Focus
2
+ VERSION = "0.0.1a".freeze
3
+ end
data/lib/focus.rb ADDED
@@ -0,0 +1,19 @@
1
+ require "focus/action"
2
+ require "focus/actions"
3
+ require "focus/cli"
4
+ require "focus/config"
5
+ require "focus/config_loader"
6
+ require "focus/formatter"
7
+ require "focus/stdout"
8
+ require "focus/string"
9
+ require "focus/util"
10
+ require "focus/utils"
11
+ require "focus/version"
12
+
13
+ module Focus
14
+ class << self
15
+ def root
16
+ File.expand_path("../..", __FILE__)
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ifocustt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1a
5
+ platform: ruby
6
+ authors:
7
+ - John Mason
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.14'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tty
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.6.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: paint
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dotenv
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: interactor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: |2
84
+ If Focus, Then That.
85
+ Do stuff when you need to focus.
86
+ email: binarymason@users.noreply.github.com
87
+ executables:
88
+ - focus
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - LICENSE.txt
93
+ - assets/example.png
94
+ - bin/focus
95
+ - bin/focus.sh
96
+ - config/default.yml
97
+ - lib/focus.rb
98
+ - lib/focus/action.rb
99
+ - lib/focus/actions.rb
100
+ - lib/focus/actions/change_blink_color.rb
101
+ - lib/focus/actions/change_slack_do_not_disturb.rb
102
+ - lib/focus/actions/change_slack_presence.rb
103
+ - lib/focus/actions/log_focus_time.rb
104
+ - lib/focus/actions/log_work_for_jira.rb
105
+ - lib/focus/actions/run_shell_command.rb
106
+ - lib/focus/actions/start_blink_server.rb
107
+ - lib/focus/actions/start_focus_time.rb
108
+ - lib/focus/actions/start_rescue_time.rb
109
+ - lib/focus/actions/strobe_blink_color.rb
110
+ - lib/focus/actions/toggle_mac_notification_center.rb
111
+ - lib/focus/cli.rb
112
+ - lib/focus/config.rb
113
+ - lib/focus/config_loader.rb
114
+ - lib/focus/formatter.rb
115
+ - lib/focus/stdout.rb
116
+ - lib/focus/string.rb
117
+ - lib/focus/util.rb
118
+ - lib/focus/utils.rb
119
+ - lib/focus/utils/parse_jira_ticket_from_git_branch.rb
120
+ - lib/focus/version.rb
121
+ homepage: http://github.com/binarymason/ifocustt
122
+ licenses:
123
+ - MIT
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: 2.0.0
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">"
137
+ - !ruby/object:Gem::Version
138
+ version: 1.3.1
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.6.10
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: If Focus, Then That
145
+ test_files: []