Class: Ftg

Inherits:
Object
  • Object
show all
Includes:
FtgOptions
Defined in:
lib/ftg/ftg.rb

Instance Method Summary collapse

Methods included from FtgOptions

#day_option, #fail, #get_command, #get_option

Constructor Details

#initializeFtg

Returns a new instance of Ftg.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/ftg/ftg.rb', line 11

def initialize
  @commands = {
    help: { fn: -> { help }, aliases: [] },
    gtfo: { fn: -> {
      gtfo(day_option, get_option(['--restore']), get_option(['--reset'])) # option union
    }, aliases: [:recap, :leave, :wrap_up] },
    status: { fn: -> { status }, aliases: [:stack] },
    current: { fn: -> { current }, aliases: [] },
    git_stats: { fn: -> { git_stats }, aliases: [] },
    start: { fn: -> { start(ARGV[1]) }, aliases: [] },
    stop: { fn: -> { stop(get_option(['--all'])) }, aliases: [:end, :pop] },
    pause: { fn: -> { pause }, aliases: [] },
    resume: { fn: -> { resume }, aliases: [] },
    edit: { fn: -> {
      edit(day_option, get_option(['--restore']), get_option(['--reset'])) # option union
    }, aliases: [] },
    list: { fn: -> { list(get_option(['--day', '-d'])) }, aliases: [:ls, :history, :recent] },
    sync: { fn: -> { sync }, aliases: [] },
    config: { fn: -> { config }, aliases: [] },
    touch: { fn: -> { touch(ARGV[1]) }, aliases: [] },
    delete: { fn: -> { delete(ARGV[1]) }, aliases: [:remove] },
    email: { fn: -> { email(day_option) }, aliases: [:mail] },
    migrate: { fn: -> { migrate }, aliases: [] },
    console: { fn: -> { console }, aliases: [:shell] },
    coffee: { fn: -> { coffee(get_option(['--big'])) } }
  }

  @ftg_dir = "#{ENV['HOME']}/.ftg"
  private_config = JSON.parse(File.open("#{@ftg_dir}/config/private.json", 'r').read)
  public_config = JSON.parse(File.open("#{@ftg_dir}/config/public.json", 'r').read)
  @config = public_config.deep_merge(private_config)
  @ftg_logger = FtgLogger.new(@ftg_dir)
end

Instance Method Details

#coffee(big = false) ⇒ Object



305
306
307
308
309
310
311
# File 'lib/ftg/ftg.rb', line 305

def coffee(big = false)
  require_relative '../coffee'
  puts(big ? Coffee.coffee2 : Coffee.coffee1)
  puts "\nHave a nice coffee !"
  puts '=========================================='
  pause
end

#configObject



93
94
95
96
97
98
99
100
101
# File 'lib/ftg/ftg.rb', line 93

def config
  require 'ap'
  puts 'Settings are in the ./config folder:'
  puts '  public.json     default settings. Do not edit manually. Added to git'
  puts '  private.json    personal settings. This will overwrite public.json. Ignored in git'

  puts "\nCurrent config:\n"
  ap @config
end

#consoleObject



297
298
299
300
301
302
303
# File 'lib/ftg/ftg.rb', line 297

def console
  require 'pry'
  require_relative '../interactive'
  require_relative './ftg_stats'
  require_models
  binding.pry
end

#currentObject



182
183
184
# File 'lib/ftg/ftg.rb', line 182

def current
  puts `cat #{@ftg_dir}/current.txt`
end

#delete(task) ⇒ Object



154
155
156
157
158
159
160
161
# File 'lib/ftg/ftg.rb', line 154

def delete(task)
  if task == '--all'
    @ftg_logger.remove_all_logs
  end
  @ftg_logger.remove_logs(task)
  status
  @ftg_logger.update_current
end

#edit(day, restore, reset) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/ftg/ftg.rb', line 186

def edit(day, restore, reset)
  require_relative '../interactive'
  require_relative './ftg_stats'
  require_models

  ftg_stats = FtgStats.new(day == Time.now.strftime('%F'))
  tasks = []
  Hash[ftg_stats.stats][day].each do |branch, by_branch|
    next if branch == 'unknown'
    by_idle = Hash[by_branch]
    scope = Task.where(day: day).where(name: branch)
    scope.delete_all if reset
    task = scope.first_or_create
    task.duration = task.edited_at ? task.duration : by_idle[false].to_i
    task.save
    tasks << task if restore || !task.deleted_at
  end

  deleted_tasks = Interactive.new.interactive_edit(tasks, day)
  tasks.each do |task|
    task.deleted_at = nil if restore
    task.save if restore || task.changed.include?('edited_at')
  end
  deleted_tasks.each do |task|
    task.save if task.changed.include?('deleted_at')
  end
end

#email(day) ⇒ Object



284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/ftg/ftg.rb', line 284

def email(day)
  require_models
  email = @config['ftg']['recap_mailto'].join(', ')
  week_days_fr = ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche']
  week_day_fr = week_days_fr[Date.parse(day).strftime('%u').to_i - 1]
  week_day_en = Time.now.strftime('%A').downcase
  greeting = @config['ftg']['greetings'][week_day_en] || nil
  subject = "Recap #{week_day_fr} #{day}"

  body = [render_email(day, Task.where(day: day).where(deleted_at: nil)), greeting].compact.join("\n\n")
  system('open', "mailto: #{email}?subject=#{subject}&body=#{body}")
end

#git_statsObject



219
220
221
222
# File 'lib/ftg/ftg.rb', line 219

def git_stats
  require_relative './ftg_stats'
  FtgStats.new(false).run
end

#gtfo(day, restore, reset) ⇒ Object



163
164
165
166
167
# File 'lib/ftg/ftg.rb', line 163

def gtfo(day, restore, reset)
  edit(day, restore, reset)
  email(day)
  puts "sync soon..."
end

#help(exit_code = 0) ⇒ Object

COMMANDS ####################################



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/ftg/ftg.rb', line 70

def help(exit_code = 0)
  help = <<-HELP
Usage: ftg <command> [arguments...]
By default, the day param is the current day.

Command list:
start, stop, pause, resume <task>  Manage tasks
gtfo                               Executes: edit, sync, mail
edit <task> [-d <day>, --reset]    Manually edit times
sync [-d <day>]                    Sync times with jira and toggl
mail                               Send an email
stats [-d <day>]                   Show time stats
current                            Show current task
touch <task>                       Start and end a task right away
remove <task>                      Delete a task
list                               List of tasks/meetings of the day
config                             Show config files
console                            Open a console
  HELP
  puts help
  exit(exit_code)
end

#list(days) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/ftg/ftg.rb', line 224

def list(days)
  days ||= 14
  begin_time = Time.now.to_i - (days.to_i * 24 * 3600)

  # Date.parse(day).to_time.to_i
  git_branches_raw = `git for-each-ref --sort=-committerdate --format='%(refname:short) | %(committerdate:iso)' refs/heads/` rescue nil

  git_branches = []
  git_branches_raw.split("\n").map do |b|
    parts = b.split(' | ')
    next if parts.count != 2
    timestamp = DateTime.parse(parts[1]).to_time.to_i
    if timestamp > begin_time
      git_branches << [timestamp, parts[0]]
    end
  end

  commands_log_path = "#{@ftg_dir}/log/commands.log"
  history_branches = []
  `tail -n #{days * 500} #{commands_log_path}`.split("\n").each do |command|
    parts = command.split("\t")
    time = parts[5].to_i
    branch = parts[4]
    if time > begin_time && branch != 'no_branch'
      history_branches << [time, branch]
    end
  end
  history_branches = history_branches.group_by { |e| e[1] }.map { |k, v| [v.last[0], k] }

  ftg_log_path = "#{@ftg_dir}/log/ftg.log"
  ftg_tasks = []
  `tail -n #{days * 100} #{ftg_log_path}`.split("\n").each do |log|
    parts = log.split("\t")
    task = parts[1]
    time = parts[2].to_i
    if time > begin_time
      ftg_tasks << [time, task]
    end
  end
  ftg_tasks = ftg_tasks.group_by { |e| e[1] }.map { |k, v| [v.last[0], k] }

  all_tasks = git_branches + history_branches + ftg_tasks
  all_tasks = all_tasks.sort_by { |e| -e[0] }.group_by { |e| e[1] }.map { |task, times| task }
  puts all_tasks.join("\n")
end

#migrateObject



270
271
272
273
# File 'lib/ftg/ftg.rb', line 270

def migrate
  require_models
  CreateTasks.new.up
end

#pauseObject



132
133
134
135
136
137
138
139
140
# File 'lib/ftg/ftg.rb', line 132

def pause
  if @ftg_logger.on_pause?
    status
    fail("\nAlready on pause")
  end
  @ftg_logger.add_log('ftg_start', 'pause')
  status
  @ftg_logger.update_current
end

#render_email(day, tasks) ⇒ Object



275
276
277
278
279
280
281
282
# File 'lib/ftg/ftg.rb', line 275

def render_email(day, tasks)
  max_len = TaskFormatter.max_length(tasks)
  content = "Salut,\n\n<Expliquer ici pourquoi le sprint ne sera pas fini à temps>\n\n#{day}\n"
  content += tasks.map do |task|
    TaskFormatter.new.format(task, max_len).line_for_email
  end.join("\n")
  content
end

#require_modelsObject



46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/ftg/ftg.rb', line 46

def require_models
  require 'active_record'
  require_relative '../models/task'
  require_relative '../migrations/create_tasks'
  require_relative '../task_formatter'

  ActiveRecord::Base.establish_connection(
    adapter: 'sqlite3',
    database: "#{@ftg_dir}/db/ftg.sqlite3"
  )
  fail('Cannot open task connection') unless Task.connection
end

#resumeObject



142
143
144
145
146
# File 'lib/ftg/ftg.rb', line 142

def resume
  @ftg_logger.add_log('ftg_stop', 'pause')
  status
  @ftg_logger.update_current
end

#runObject



59
60
61
62
63
64
# File 'lib/ftg/ftg.rb', line 59

def run
  help(1) if ARGV[0].nil?
  cmd = get_command(ARGV[0])
  fail("Unknown command #{ARGV[0]}") if cmd.nil?
  cmd[1][:fn].call
end

#start(task) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/ftg/ftg.rb', line 103

def start(task)
  if task == 'auto' || task == 'current_branch'
    task = `git rev-parse --abbrev-ref HEAD`.strip
  end
  if task.nil? || task == ''
    fail('Enter a task. Eg: ftg start jt-1234')
  end
  if @ftg_logger.on_pause?
    status
    fail("\nCannot start a task while on pause. Use \"ftg resume\" first")
  end
  if @ftg_logger.get_unclosed_logs.find { |l| l[:task_name] == task }
    status
    fail("\nTask #{task} already started")
  end
  @ftg_logger.add_log('ftg_start', task)
  status
  @ftg_logger.update_current
end

#statusObject



169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/ftg/ftg.rb', line 169

def status
  current_logs = @ftg_logger.get_unclosed_logs
  if current_logs.empty?
    puts 'No current task'
  else
    task_name = current_logs[0][:task_name]
    puts(task_name == 'pause' ? 'On pause' : "Now working on: [#{task_name.cyan}]")
    unless current_logs[1..-1].empty?
      puts "next tasks: #{current_logs[1..-1].map { |l| l[:task_name].light_blue }.join(', ')}"
    end
  end
end

#stop(all) ⇒ Object



123
124
125
126
127
128
129
130
# File 'lib/ftg/ftg.rb', line 123

def stop(all)
  @ftg_logger.get_unclosed_logs.each do |log|
    @ftg_logger.add_log('ftg_stop', log[:task_name])
    break unless all
  end
  status
  @ftg_logger.update_current
end

#syncObject



214
215
216
217
# File 'lib/ftg/ftg.rb', line 214

def sync
  require_relative './ftg_sync'
  abort('todo')
end

#touch(task) ⇒ Object



148
149
150
151
152
# File 'lib/ftg/ftg.rb', line 148

def touch(task)
  @ftg_logger.add_log('ftg_start', task)
  @ftg_logger.add_log('ftg_stop', task)
  status
end