ifocustt 0.0.1a

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 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: []