ftg 2.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 +15 -0
 - data/.idea/.ftg.iml +31 -0
 - data/.idea/misc.xml +14 -0
 - data/.idea/modules.xml +8 -0
 - data/.idea/vcs.xml +6 -0
 - data/.idea/workspace.xml +45 -0
 - data/.ruby-version +1 -0
 - data/Gemfile +17 -0
 - data/LICENSE.txt +21 -0
 - data/README.md +61 -0
 - data/Rakefile +1 -0
 - data/bin/console +14 -0
 - data/bin/ftg +7 -0
 - data/bin/setup +7 -0
 - data/config/private.json.example +6 -0
 - data/config/public.json +30 -0
 - data/ftg.gemspec +32 -0
 - data/lib/coffee.rb +79 -0
 - data/lib/colors.rb +38 -0
 - data/lib/ftg.rb +316 -0
 - data/lib/ftg_logger.rb +67 -0
 - data/lib/ftg_options.rb +28 -0
 - data/lib/ftg_stats.rb +135 -0
 - data/lib/ftg_sync.rb +112 -0
 - data/lib/idle_logger.rb +9 -0
 - data/lib/interactive.rb +115 -0
 - data/lib/migrations/create_tasks.rb +21 -0
 - data/lib/models/task.rb +5 -0
 - data/lib/task_formatter.rb +51 -0
 - data/lib/utils.rb +24 -0
 - metadata +105 -0
 
    
        data/lib/ftg.rb
    ADDED
    
    | 
         @@ -0,0 +1,316 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative './colors'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative './utils'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative './ftg_options'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative './ftg_logger'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'date'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            class Ftg
         
     | 
| 
      
 9 
     | 
    
         
            +
              include FtgOptions
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              def initialize
         
     | 
| 
      
 12 
     | 
    
         
            +
                @commands = {
         
     | 
| 
      
 13 
     | 
    
         
            +
                  help: { fn: -> { help }, aliases: [] },
         
     | 
| 
      
 14 
     | 
    
         
            +
                  gtfo: { fn: -> {
         
     | 
| 
      
 15 
     | 
    
         
            +
                    gtfo(day_option, get_option(['--restore']), get_option(['--reset'])) # option union
         
     | 
| 
      
 16 
     | 
    
         
            +
                  }, aliases: [:recap, :leave, :wrap_up] },
         
     | 
| 
      
 17 
     | 
    
         
            +
                  status: { fn: -> { status }, aliases: [:stack] },
         
     | 
| 
      
 18 
     | 
    
         
            +
                  current: { fn: -> { current }, aliases: [] },
         
     | 
| 
      
 19 
     | 
    
         
            +
                  git_stats: { fn: -> { git_stats }, aliases: [] },
         
     | 
| 
      
 20 
     | 
    
         
            +
                  start: { fn: -> { start(ARGV[1]) }, aliases: [] },
         
     | 
| 
      
 21 
     | 
    
         
            +
                  stop: { fn: -> { stop(get_option(['--all'])) }, aliases: [:end, :pop] },
         
     | 
| 
      
 22 
     | 
    
         
            +
                  pause: { fn: -> { pause }, aliases: [] },
         
     | 
| 
      
 23 
     | 
    
         
            +
                  resume: { fn: -> { resume }, aliases: [] },
         
     | 
| 
      
 24 
     | 
    
         
            +
                  edit: { fn: -> {
         
     | 
| 
      
 25 
     | 
    
         
            +
                    edit(day_option, get_option(['--restore']), get_option(['--reset'])) # option union
         
     | 
| 
      
 26 
     | 
    
         
            +
                  }, aliases: [] },
         
     | 
| 
      
 27 
     | 
    
         
            +
                  list: { fn: -> { list(get_option(['--day', '-d'])) }, aliases: [:ls, :history, :recent] },
         
     | 
| 
      
 28 
     | 
    
         
            +
                  sync: { fn: -> { sync }, aliases: [] },
         
     | 
| 
      
 29 
     | 
    
         
            +
                  config: { fn: -> { config }, aliases: [] },
         
     | 
| 
      
 30 
     | 
    
         
            +
                  touch: { fn: -> { touch(ARGV[1]) }, aliases: [] },
         
     | 
| 
      
 31 
     | 
    
         
            +
                  delete: { fn: -> { delete(ARGV[1]) }, aliases: [:remove] },
         
     | 
| 
      
 32 
     | 
    
         
            +
                  email: { fn: -> { email(day_option) }, aliases: [:mail] },
         
     | 
| 
      
 33 
     | 
    
         
            +
                  migrate: { fn: -> { migrate }, aliases: [] },
         
     | 
| 
      
 34 
     | 
    
         
            +
                  console: { fn: -> { console }, aliases: [:shell] },
         
     | 
| 
      
 35 
     | 
    
         
            +
                  coffee: { fn: -> { coffee(get_option(['--big'])) } }
         
     | 
| 
      
 36 
     | 
    
         
            +
                }
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                @ftg_dir = "#{ENV['HOME']}/.ftg"
         
     | 
| 
      
 39 
     | 
    
         
            +
                private_config = JSON.parse(File.open("#{@ftg_dir}/config/private.json", 'r').read)
         
     | 
| 
      
 40 
     | 
    
         
            +
                public_config = JSON.parse(File.open("#{@ftg_dir}/config/public.json", 'r').read)
         
     | 
| 
      
 41 
     | 
    
         
            +
                @config = public_config.deep_merge(private_config)
         
     | 
| 
      
 42 
     | 
    
         
            +
                @ftg_logger = FtgLogger.new(@ftg_dir)
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
              def require_models
         
     | 
| 
      
 47 
     | 
    
         
            +
                require 'active_record'
         
     | 
| 
      
 48 
     | 
    
         
            +
                require_relative './models/task'
         
     | 
| 
      
 49 
     | 
    
         
            +
                require_relative './migrations/create_tasks'
         
     | 
| 
      
 50 
     | 
    
         
            +
                require_relative './task_formatter'
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                ActiveRecord::Base.establish_connection(
         
     | 
| 
      
 53 
     | 
    
         
            +
                  adapter: 'sqlite3',
         
     | 
| 
      
 54 
     | 
    
         
            +
                  database: 'db/ftg.sqlite3'
         
     | 
| 
      
 55 
     | 
    
         
            +
                )
         
     | 
| 
      
 56 
     | 
    
         
            +
                fail('Cannot open task connection') unless Task.connection
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
              def run
         
     | 
| 
      
 60 
     | 
    
         
            +
                help(1) if ARGV[0].nil?
         
     | 
| 
      
 61 
     | 
    
         
            +
                cmd = get_command(ARGV[0])
         
     | 
| 
      
 62 
     | 
    
         
            +
                fail("Unknown command #{ARGV[0]}") if cmd.nil?
         
     | 
| 
      
 63 
     | 
    
         
            +
                cmd[1][:fn].call
         
     | 
| 
      
 64 
     | 
    
         
            +
              end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
            #####################################################################################
         
     | 
| 
      
 67 
     | 
    
         
            +
            ####################################### COMMANDS ####################################
         
     | 
| 
      
 68 
     | 
    
         
            +
            #####################################################################################
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
              def help(exit_code = 0)
         
     | 
| 
      
 71 
     | 
    
         
            +
                help = <<-HELP
         
     | 
| 
      
 72 
     | 
    
         
            +
            Usage: ftg <command> [arguments...]
         
     | 
| 
      
 73 
     | 
    
         
            +
            By default, the day param is the current day.
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
            Command list:
         
     | 
| 
      
 76 
     | 
    
         
            +
              start, stop, pause, resume <task>  Manage tasks
         
     | 
| 
      
 77 
     | 
    
         
            +
              gtfo                               Executes: edit, sync, mail
         
     | 
| 
      
 78 
     | 
    
         
            +
              edit <task> [-d <day>, --reset]    Manually edit times
         
     | 
| 
      
 79 
     | 
    
         
            +
              sync [-d <day>]                    Sync times with jira and toggl
         
     | 
| 
      
 80 
     | 
    
         
            +
              mail                               Send an email
         
     | 
| 
      
 81 
     | 
    
         
            +
              stats [-d <day>]                   Show time stats
         
     | 
| 
      
 82 
     | 
    
         
            +
              current                            Show current task
         
     | 
| 
      
 83 
     | 
    
         
            +
              pop                                Stop current task and resume previous one
         
     | 
| 
      
 84 
     | 
    
         
            +
              touch <task>                       Start and end a task right away
         
     | 
| 
      
 85 
     | 
    
         
            +
              remove <task>                      Delete a task
         
     | 
| 
      
 86 
     | 
    
         
            +
              list                               List of tasks/meetings of the day
         
     | 
| 
      
 87 
     | 
    
         
            +
              config                             Show config files
         
     | 
| 
      
 88 
     | 
    
         
            +
              console                            Open a console
         
     | 
| 
      
 89 
     | 
    
         
            +
                HELP
         
     | 
| 
      
 90 
     | 
    
         
            +
                puts help
         
     | 
| 
      
 91 
     | 
    
         
            +
                exit(exit_code)
         
     | 
| 
      
 92 
     | 
    
         
            +
              end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
              def config
         
     | 
| 
      
 95 
     | 
    
         
            +
                require 'ap'
         
     | 
| 
      
 96 
     | 
    
         
            +
                puts 'Settings are in the ./config folder:'
         
     | 
| 
      
 97 
     | 
    
         
            +
                puts '  public.json     default settings. Do not edit manually. Added to git'
         
     | 
| 
      
 98 
     | 
    
         
            +
                puts '  private.json    personal settings. This will overwrite public.json. Ignored in git'
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                puts "\nCurrent config:\n"
         
     | 
| 
      
 101 
     | 
    
         
            +
                ap @config
         
     | 
| 
      
 102 
     | 
    
         
            +
              end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
              def start(task)
         
     | 
| 
      
 105 
     | 
    
         
            +
                if task == 'auto' || task == 'current_branch'
         
     | 
| 
      
 106 
     | 
    
         
            +
                  task = `git rev-parse --abbrev-ref HEAD`.strip
         
     | 
| 
      
 107 
     | 
    
         
            +
                end
         
     | 
| 
      
 108 
     | 
    
         
            +
                if task.nil? || task == ''
         
     | 
| 
      
 109 
     | 
    
         
            +
                  fail('Enter a task. Eg: ftg start jt-1234')
         
     | 
| 
      
 110 
     | 
    
         
            +
                end
         
     | 
| 
      
 111 
     | 
    
         
            +
                if @ftg_logger.on_pause?
         
     | 
| 
      
 112 
     | 
    
         
            +
                  status
         
     | 
| 
      
 113 
     | 
    
         
            +
                  fail("\nCannot start a task while on pause. Use \"ftg resume\" first")
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
                if @ftg_logger.get_unclosed_logs.find { |l| l[:task_name] == task }
         
     | 
| 
      
 116 
     | 
    
         
            +
                  status
         
     | 
| 
      
 117 
     | 
    
         
            +
                  fail("\nTask #{task} already started")
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
                @ftg_logger.add_log('ftg_start', task)
         
     | 
| 
      
 120 
     | 
    
         
            +
                status
         
     | 
| 
      
 121 
     | 
    
         
            +
                @ftg_logger.update_current
         
     | 
| 
      
 122 
     | 
    
         
            +
              end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
              def stop(all)
         
     | 
| 
      
 125 
     | 
    
         
            +
                @ftg_logger.get_unclosed_logs.each do |log|
         
     | 
| 
      
 126 
     | 
    
         
            +
                  @ftg_logger.add_log('ftg_stop', log[:task_name])
         
     | 
| 
      
 127 
     | 
    
         
            +
                  break unless all
         
     | 
| 
      
 128 
     | 
    
         
            +
                end
         
     | 
| 
      
 129 
     | 
    
         
            +
                status
         
     | 
| 
      
 130 
     | 
    
         
            +
                @ftg_logger.update_current
         
     | 
| 
      
 131 
     | 
    
         
            +
              end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
              def pause
         
     | 
| 
      
 134 
     | 
    
         
            +
                if @ftg_logger.on_pause?
         
     | 
| 
      
 135 
     | 
    
         
            +
                  status
         
     | 
| 
      
 136 
     | 
    
         
            +
                  fail("\nAlready on pause")
         
     | 
| 
      
 137 
     | 
    
         
            +
                end
         
     | 
| 
      
 138 
     | 
    
         
            +
                @ftg_logger.add_log('ftg_start', 'pause')
         
     | 
| 
      
 139 
     | 
    
         
            +
                status
         
     | 
| 
      
 140 
     | 
    
         
            +
                @ftg_logger.update_current
         
     | 
| 
      
 141 
     | 
    
         
            +
              end
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
              def resume
         
     | 
| 
      
 144 
     | 
    
         
            +
                @ftg_logger.add_log('ftg_stop', 'pause')
         
     | 
| 
      
 145 
     | 
    
         
            +
                status
         
     | 
| 
      
 146 
     | 
    
         
            +
                @ftg_logger.update_current
         
     | 
| 
      
 147 
     | 
    
         
            +
              end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
              def touch(task)
         
     | 
| 
      
 150 
     | 
    
         
            +
                @ftg_logger.add_log('ftg_start', task)
         
     | 
| 
      
 151 
     | 
    
         
            +
                @ftg_logger.add_log('ftg_stop', task)
         
     | 
| 
      
 152 
     | 
    
         
            +
                status
         
     | 
| 
      
 153 
     | 
    
         
            +
              end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
              def delete(task)
         
     | 
| 
      
 156 
     | 
    
         
            +
                if task == '--all'
         
     | 
| 
      
 157 
     | 
    
         
            +
                  @ftg_logger.remove_all_logs
         
     | 
| 
      
 158 
     | 
    
         
            +
                end
         
     | 
| 
      
 159 
     | 
    
         
            +
                @ftg_logger.remove_logs(task)
         
     | 
| 
      
 160 
     | 
    
         
            +
                status
         
     | 
| 
      
 161 
     | 
    
         
            +
                @ftg_logger.update_current
         
     | 
| 
      
 162 
     | 
    
         
            +
              end
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
              def gtfo(day, restore, reset)
         
     | 
| 
      
 165 
     | 
    
         
            +
                edit(day, restore, reset)
         
     | 
| 
      
 166 
     | 
    
         
            +
                email(day)
         
     | 
| 
      
 167 
     | 
    
         
            +
                puts "sync soon..."
         
     | 
| 
      
 168 
     | 
    
         
            +
              end
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
              def status
         
     | 
| 
      
 171 
     | 
    
         
            +
                current_logs = @ftg_logger.get_unclosed_logs
         
     | 
| 
      
 172 
     | 
    
         
            +
                if current_logs.empty?
         
     | 
| 
      
 173 
     | 
    
         
            +
                  puts 'No current task'
         
     | 
| 
      
 174 
     | 
    
         
            +
                else
         
     | 
| 
      
 175 
     | 
    
         
            +
                  task_name = current_logs[0][:task_name]
         
     | 
| 
      
 176 
     | 
    
         
            +
                  puts(task_name == 'pause' ? 'On pause' : "Now working on: [#{task_name.cyan}]")
         
     | 
| 
      
 177 
     | 
    
         
            +
                  unless current_logs[1..-1].empty?
         
     | 
| 
      
 178 
     | 
    
         
            +
                    puts "next tasks: #{current_logs[1..-1].map { |l| l[:task_name].light_blue }.join(', ')}"
         
     | 
| 
      
 179 
     | 
    
         
            +
                  end
         
     | 
| 
      
 180 
     | 
    
         
            +
                end
         
     | 
| 
      
 181 
     | 
    
         
            +
              end
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
              def current
         
     | 
| 
      
 184 
     | 
    
         
            +
                puts `cat #{@ftg_dir}/current.txt`
         
     | 
| 
      
 185 
     | 
    
         
            +
              end
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
              def edit(day, restore, reset)
         
     | 
| 
      
 188 
     | 
    
         
            +
                require_relative './interactive'
         
     | 
| 
      
 189 
     | 
    
         
            +
                require_relative './ftg_stats'
         
     | 
| 
      
 190 
     | 
    
         
            +
                require_models
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
                ftg_stats = FtgStats.new(day == Time.now.strftime('%F'))
         
     | 
| 
      
 193 
     | 
    
         
            +
                tasks = []
         
     | 
| 
      
 194 
     | 
    
         
            +
                Hash[ftg_stats.stats][day].each do |branch, by_branch|
         
     | 
| 
      
 195 
     | 
    
         
            +
                  next if branch == 'unknown'
         
     | 
| 
      
 196 
     | 
    
         
            +
                  by_idle = Hash[by_branch]
         
     | 
| 
      
 197 
     | 
    
         
            +
                  scope = Task.where(day: day).where(name: branch)
         
     | 
| 
      
 198 
     | 
    
         
            +
                  scope.delete_all if reset
         
     | 
| 
      
 199 
     | 
    
         
            +
                  task = scope.first_or_create
         
     | 
| 
      
 200 
     | 
    
         
            +
                  task.duration = task.edited_at ? task.duration : by_idle[false].to_i
         
     | 
| 
      
 201 
     | 
    
         
            +
                  task.save
         
     | 
| 
      
 202 
     | 
    
         
            +
                  tasks << task if restore || !task.deleted_at
         
     | 
| 
      
 203 
     | 
    
         
            +
                end
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                deleted_tasks = Interactive.new.interactive_edit(tasks)
         
     | 
| 
      
 206 
     | 
    
         
            +
                tasks.each do |task|
         
     | 
| 
      
 207 
     | 
    
         
            +
                  task.deleted_at = nil if restore
         
     | 
| 
      
 208 
     | 
    
         
            +
                  task.save if restore || task.changed.include?('edited_at')
         
     | 
| 
      
 209 
     | 
    
         
            +
                end
         
     | 
| 
      
 210 
     | 
    
         
            +
                deleted_tasks.each do |task|
         
     | 
| 
      
 211 
     | 
    
         
            +
                  task.save if task.changed.include?('deleted_at')
         
     | 
| 
      
 212 
     | 
    
         
            +
                end
         
     | 
| 
      
 213 
     | 
    
         
            +
              end
         
     | 
| 
      
 214 
     | 
    
         
            +
             
     | 
| 
      
 215 
     | 
    
         
            +
              def sync
         
     | 
| 
      
 216 
     | 
    
         
            +
                require_relative './ftg_sync'
         
     | 
| 
      
 217 
     | 
    
         
            +
                abort('todo')
         
     | 
| 
      
 218 
     | 
    
         
            +
              end
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
      
 220 
     | 
    
         
            +
              def git_stats
         
     | 
| 
      
 221 
     | 
    
         
            +
                require_relative './ftg_stats'
         
     | 
| 
      
 222 
     | 
    
         
            +
                FtgStats.new(false).run
         
     | 
| 
      
 223 
     | 
    
         
            +
              end
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
              def list(days)
         
     | 
| 
      
 226 
     | 
    
         
            +
                days ||= 14
         
     | 
| 
      
 227 
     | 
    
         
            +
                begin_time = Time.now.to_i - (days.to_i * 24 * 3600)
         
     | 
| 
      
 228 
     | 
    
         
            +
             
     | 
| 
      
 229 
     | 
    
         
            +
                # Date.parse(day).to_time.to_i
         
     | 
| 
      
 230 
     | 
    
         
            +
                git_branches_raw = `git for-each-ref --sort=-committerdate --format='%(refname:short) | %(committerdate:iso)' refs/heads/` rescue nil
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
                git_branches = []
         
     | 
| 
      
 233 
     | 
    
         
            +
                git_branches_raw.split("\n").map do |b|
         
     | 
| 
      
 234 
     | 
    
         
            +
                  parts = b.split(' | ')
         
     | 
| 
      
 235 
     | 
    
         
            +
                  next if parts.count != 2
         
     | 
| 
      
 236 
     | 
    
         
            +
                  timestamp = DateTime.parse(parts[1]).to_time.to_i
         
     | 
| 
      
 237 
     | 
    
         
            +
                  if timestamp > begin_time
         
     | 
| 
      
 238 
     | 
    
         
            +
                    git_branches << [timestamp, parts[0]]
         
     | 
| 
      
 239 
     | 
    
         
            +
                  end
         
     | 
| 
      
 240 
     | 
    
         
            +
                end
         
     | 
| 
      
 241 
     | 
    
         
            +
             
     | 
| 
      
 242 
     | 
    
         
            +
                commands_log_path = "#{@ftg_dir}/log/commands.log"
         
     | 
| 
      
 243 
     | 
    
         
            +
                history_branches = []
         
     | 
| 
      
 244 
     | 
    
         
            +
                `tail -n #{days * 500} #{commands_log_path}`.split("\n").each do |command|
         
     | 
| 
      
 245 
     | 
    
         
            +
                  parts = command.split("\t")
         
     | 
| 
      
 246 
     | 
    
         
            +
                  time = parts[5].to_i
         
     | 
| 
      
 247 
     | 
    
         
            +
                  branch = parts[4]
         
     | 
| 
      
 248 
     | 
    
         
            +
                  if time > begin_time && branch != 'no_branch'
         
     | 
| 
      
 249 
     | 
    
         
            +
                    history_branches << [time, branch]
         
     | 
| 
      
 250 
     | 
    
         
            +
                  end
         
     | 
| 
      
 251 
     | 
    
         
            +
                end
         
     | 
| 
      
 252 
     | 
    
         
            +
                history_branches = history_branches.group_by { |e| e[1] }.map { |k, v| [v.last[0], k] }
         
     | 
| 
      
 253 
     | 
    
         
            +
             
     | 
| 
      
 254 
     | 
    
         
            +
                ftg_log_path = "#{@ftg_dir}/log/ftg.log"
         
     | 
| 
      
 255 
     | 
    
         
            +
                ftg_tasks = []
         
     | 
| 
      
 256 
     | 
    
         
            +
                `tail -n #{days * 100} #{ftg_log_path}`.split("\n").each do |log|
         
     | 
| 
      
 257 
     | 
    
         
            +
                  parts = log.split("\t")
         
     | 
| 
      
 258 
     | 
    
         
            +
                  task = parts[1]
         
     | 
| 
      
 259 
     | 
    
         
            +
                  time = parts[2].to_i
         
     | 
| 
      
 260 
     | 
    
         
            +
                  if time > begin_time
         
     | 
| 
      
 261 
     | 
    
         
            +
                    ftg_tasks << [time, task]
         
     | 
| 
      
 262 
     | 
    
         
            +
                  end
         
     | 
| 
      
 263 
     | 
    
         
            +
                end
         
     | 
| 
      
 264 
     | 
    
         
            +
                ftg_tasks = ftg_tasks.group_by { |e| e[1] }.map { |k, v| [v.last[0], k] }
         
     | 
| 
      
 265 
     | 
    
         
            +
             
     | 
| 
      
 266 
     | 
    
         
            +
                all_tasks = git_branches + history_branches + ftg_tasks
         
     | 
| 
      
 267 
     | 
    
         
            +
                all_tasks = all_tasks.sort_by { |e| -e[0] }.group_by { |e| e[1] }.map { |task, times| task }
         
     | 
| 
      
 268 
     | 
    
         
            +
                puts all_tasks.join("\n")
         
     | 
| 
      
 269 
     | 
    
         
            +
              end
         
     | 
| 
      
 270 
     | 
    
         
            +
             
     | 
| 
      
 271 
     | 
    
         
            +
              def migrate
         
     | 
| 
      
 272 
     | 
    
         
            +
                require_models
         
     | 
| 
      
 273 
     | 
    
         
            +
                CreateTasks.new.up
         
     | 
| 
      
 274 
     | 
    
         
            +
              end
         
     | 
| 
      
 275 
     | 
    
         
            +
             
     | 
| 
      
 276 
     | 
    
         
            +
              def render_email(day, tasks)
         
     | 
| 
      
 277 
     | 
    
         
            +
                max_len = TaskFormatter.max_length(tasks)
         
     | 
| 
      
 278 
     | 
    
         
            +
                content = "Salut,\n\n<Expliquer ici pourquoi le sprint ne sera pas fini à temps>\n\n#{day}\n"
         
     | 
| 
      
 279 
     | 
    
         
            +
                content += tasks.map do |task|
         
     | 
| 
      
 280 
     | 
    
         
            +
                  TaskFormatter.new.format(task, max_len).line_for_email
         
     | 
| 
      
 281 
     | 
    
         
            +
                end.join("\n")
         
     | 
| 
      
 282 
     | 
    
         
            +
                content
         
     | 
| 
      
 283 
     | 
    
         
            +
              end
         
     | 
| 
      
 284 
     | 
    
         
            +
             
     | 
| 
      
 285 
     | 
    
         
            +
              def email(day)
         
     | 
| 
      
 286 
     | 
    
         
            +
                require_models
         
     | 
| 
      
 287 
     | 
    
         
            +
                email = @config['ftg']['recap_mailto'].join(', ')
         
     | 
| 
      
 288 
     | 
    
         
            +
                week_days_fr = ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche']
         
     | 
| 
      
 289 
     | 
    
         
            +
                week_day_fr = week_days_fr[Date.parse(day).strftime('%u').to_i - 1]
         
     | 
| 
      
 290 
     | 
    
         
            +
                week_day_en = Time.now.strftime('%A').downcase
         
     | 
| 
      
 291 
     | 
    
         
            +
                greeting = @config['ftg']['greetings'][week_day_en] || nil
         
     | 
| 
      
 292 
     | 
    
         
            +
                subject = "Recap #{week_day_fr} #{day}"
         
     | 
| 
      
 293 
     | 
    
         
            +
             
     | 
| 
      
 294 
     | 
    
         
            +
                body = [render_email(day, Task.where(day: day).where(deleted_at: nil)), greeting].compact.join("\n\n")
         
     | 
| 
      
 295 
     | 
    
         
            +
                system('open', "mailto: #{email}?subject=#{subject}&body=#{body}")
         
     | 
| 
      
 296 
     | 
    
         
            +
              end
         
     | 
| 
      
 297 
     | 
    
         
            +
             
     | 
| 
      
 298 
     | 
    
         
            +
              def console
         
     | 
| 
      
 299 
     | 
    
         
            +
                require 'pry'
         
     | 
| 
      
 300 
     | 
    
         
            +
                require_relative './interactive'
         
     | 
| 
      
 301 
     | 
    
         
            +
                require_relative './ftg_stats'
         
     | 
| 
      
 302 
     | 
    
         
            +
                require_models
         
     | 
| 
      
 303 
     | 
    
         
            +
                binding.pry
         
     | 
| 
      
 304 
     | 
    
         
            +
              end
         
     | 
| 
      
 305 
     | 
    
         
            +
             
     | 
| 
      
 306 
     | 
    
         
            +
              def coffee(big = false)
         
     | 
| 
      
 307 
     | 
    
         
            +
                require_relative './coffee'
         
     | 
| 
      
 308 
     | 
    
         
            +
                puts(big ? Coffee.coffee2 : Coffee.coffee1)
         
     | 
| 
      
 309 
     | 
    
         
            +
                puts "\nHave a nice coffee !"
         
     | 
| 
      
 310 
     | 
    
         
            +
                puts '=========================================='
         
     | 
| 
      
 311 
     | 
    
         
            +
                pause
         
     | 
| 
      
 312 
     | 
    
         
            +
              end
         
     | 
| 
      
 313 
     | 
    
         
            +
            end
         
     | 
| 
      
 314 
     | 
    
         
            +
             
     | 
| 
      
 315 
     | 
    
         
            +
             
     | 
| 
      
 316 
     | 
    
         
            +
            # Ftg.new.run
         
     | 
    
        data/lib/ftg_logger.rb
    ADDED
    
    | 
         @@ -0,0 +1,67 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class FtgLogger
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              def initialize(ftg_dir)
         
     | 
| 
      
 4 
     | 
    
         
            +
                @ftg_dir = ftg_dir
         
     | 
| 
      
 5 
     | 
    
         
            +
                @log_file = "#{ftg_dir}/log/ftg.log"
         
     | 
| 
      
 6 
     | 
    
         
            +
              end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              def add_log(command, task)
         
     | 
| 
      
 9 
     | 
    
         
            +
                lines = [command, task, Time.now.getutc.to_i]
         
     | 
| 
      
 10 
     | 
    
         
            +
                `echo "#{lines.join('\t')}" >> #{@log_file}`
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              def remove_all_logs
         
     | 
| 
      
 14 
     | 
    
         
            +
                `echo "" > #{@log_file}`
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              def remove_logs(name)
         
     | 
| 
      
 18 
     | 
    
         
            +
                count = 0
         
     | 
| 
      
 19 
     | 
    
         
            +
                logs = get_logs
         
     | 
| 
      
 20 
     | 
    
         
            +
                logs.keep_if do |log|
         
     | 
| 
      
 21 
     | 
    
         
            +
                  cond = log[:task_name] != name || log[:timestamp].to_i <= Time.now.to_i - 24*3600
         
     | 
| 
      
 22 
     | 
    
         
            +
                  count += 1 unless cond
         
     | 
| 
      
 23 
     | 
    
         
            +
                  cond
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                File.open(@log_file, 'w') do |f|
         
     | 
| 
      
 27 
     | 
    
         
            +
                  f.write(logs.map{|l| l.values.join("\t")}.join("\n") + "\n")
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                puts "Removed #{count} entries"
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              def get_logs
         
     | 
| 
      
 34 
     | 
    
         
            +
                File.open(@log_file, File::RDONLY|File::CREAT) do |file|
         
     | 
| 
      
 35 
     | 
    
         
            +
                  file.read.split("\n").map do |e|
         
     | 
| 
      
 36 
     | 
    
         
            +
                    parts = e.split("\t")
         
     | 
| 
      
 37 
     | 
    
         
            +
                    { command: parts[0], task_name: parts[1], timestamp: parts[2] }
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
              def on_pause?
         
     | 
| 
      
 43 
     | 
    
         
            +
                unclosed_logs = get_unclosed_logs
         
     | 
| 
      
 44 
     | 
    
         
            +
                unclosed_logs[0] && unclosed_logs[0][:task_name] == 'pause'
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
              def get_unclosed_logs
         
     | 
| 
      
 48 
     | 
    
         
            +
                unclosed_logs = []
         
     | 
| 
      
 49 
     | 
    
         
            +
                closed = {}
         
     | 
| 
      
 50 
     | 
    
         
            +
                get_logs.reverse.each do |log|
         
     | 
| 
      
 51 
     | 
    
         
            +
                  if log[:command] == 'ftg_stop'
         
     | 
| 
      
 52 
     | 
    
         
            +
                    closed[log[:task_name]] = true
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
                  if log[:command] == 'ftg_start' && !closed[log[:task_name]]
         
     | 
| 
      
 55 
     | 
    
         
            +
                    unclosed_logs << log
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
                unclosed_logs
         
     | 
| 
      
 59 
     | 
    
         
            +
              end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
              def update_current
         
     | 
| 
      
 62 
     | 
    
         
            +
                current = ''
         
     | 
| 
      
 63 
     | 
    
         
            +
                current_logs = get_unclosed_logs
         
     | 
| 
      
 64 
     | 
    
         
            +
                current = current_logs[0][:task_name] unless current_logs.empty?
         
     | 
| 
      
 65 
     | 
    
         
            +
                `echo "#{current}" > #{@ftg_dir}/current.txt`
         
     | 
| 
      
 66 
     | 
    
         
            +
              end
         
     | 
| 
      
 67 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/ftg_options.rb
    ADDED
    
    | 
         @@ -0,0 +1,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module FtgOptions
         
     | 
| 
      
 2 
     | 
    
         
            +
              def get_option(names)
         
     | 
| 
      
 3 
     | 
    
         
            +
                ARGV.each_with_index do |opt_name, i|
         
     | 
| 
      
 4 
     | 
    
         
            +
                  return (ARGV[i + 1] || 1) if names.include?(opt_name)
         
     | 
| 
      
 5 
     | 
    
         
            +
                end
         
     | 
| 
      
 6 
     | 
    
         
            +
                nil
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              # day, not gay
         
     | 
| 
      
 10 
     | 
    
         
            +
              def day_option
         
     | 
| 
      
 11 
     | 
    
         
            +
                day_option = get_option(['-d', '--day'])
         
     | 
| 
      
 12 
     | 
    
         
            +
                day_option ||= '0'
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                Utils.is_integer?(day_option) ?
         
     | 
| 
      
 15 
     | 
    
         
            +
                  Time.at(Time.now.to_i - day_option.to_i * 86400).strftime('%F') :
         
     | 
| 
      
 16 
     | 
    
         
            +
                  Date.parse(day_option).strftime('%F')
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              def get_command(name)
         
     | 
| 
      
 20 
     | 
    
         
            +
                @commands.find { |cmd_name, _| cmd_name.to_s.start_with?(name) } ||
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @commands.find { |_, cmd| cmd[:aliases] && cmd[:aliases].any? { |a| a.to_s.start_with?(name) } }
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              def fail(message = nil)
         
     | 
| 
      
 25 
     | 
    
         
            +
                STDERR.puts message if message
         
     | 
| 
      
 26 
     | 
    
         
            +
                exit(1)
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/ftg_stats.rb
    ADDED
    
    | 
         @@ -0,0 +1,135 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class FtgStats
         
     | 
| 
      
 2 
     | 
    
         
            +
              IDLE_THRESHOLD = 5 * 60
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              attr_accessor :stats
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              def initialize(only_last_day)
         
     | 
| 
      
 7 
     | 
    
         
            +
                load_data(only_last_day)
         
     | 
| 
      
 8 
     | 
    
         
            +
                crunch
         
     | 
| 
      
 9 
     | 
    
         
            +
                group
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              def run
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                display
         
     | 
| 
      
 15 
     | 
    
         
            +
                # sync_toggl
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              def search_idle_key(timestamp)
         
     | 
| 
      
 19 
     | 
    
         
            +
                (0..10).each do |k|
         
     | 
| 
      
 20 
     | 
    
         
            +
                  key = timestamp + k
         
     | 
| 
      
 21 
     | 
    
         
            +
                  return key if @idle_parts[key]
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
                # puts("not found #{Utils.format_time(timestamp)}")
         
     | 
| 
      
 24 
     | 
    
         
            +
                nil
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              def load_data(only_last_day)
         
     | 
| 
      
 28 
     | 
    
         
            +
                home = `echo $HOME`.strip
         
     | 
| 
      
 29 
     | 
    
         
            +
                ftg_dir = "#{home}/.ftg"
         
     | 
| 
      
 30 
     | 
    
         
            +
                commands_log_path = "#{ftg_dir}/log/commands.log"
         
     | 
| 
      
 31 
     | 
    
         
            +
                idle_log_path = "#{ftg_dir}/log/idle.log"
         
     | 
| 
      
 32 
     | 
    
         
            +
                records_to_load = only_last_day ? 24 * 360 : 0
         
     | 
| 
      
 33 
     | 
    
         
            +
                @commands = {}
         
     | 
| 
      
 34 
     | 
    
         
            +
                @idle_parts = {}
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                # sample row:
         
     | 
| 
      
 37 
     | 
    
         
            +
                # pinouchon       fg      no_alias        /Users/pinouchon/.ftg   no_branch       1438867098
         
     | 
| 
      
 38 
     | 
    
         
            +
                (only_last_day ?
         
     | 
| 
      
 39 
     | 
    
         
            +
                  `tail -n #{records_to_load} #{commands_log_path}`.split("\n") :
         
     | 
| 
      
 40 
     | 
    
         
            +
                  File.foreach(commands_log_path)).each do |line|
         
     | 
| 
      
 41 
     | 
    
         
            +
                  parts = line.split("\t")
         
     | 
| 
      
 42 
     | 
    
         
            +
                  next if !parts[5] || parts[5].empty?
         
     | 
| 
      
 43 
     | 
    
         
            +
                  @commands[parts[5].strip.to_i] = { :user => parts[0],
         
     | 
| 
      
 44 
     | 
    
         
            +
                                                     :command => parts[1],
         
     | 
| 
      
 45 
     | 
    
         
            +
                                                     :alias => parts[2], :dir => parts[3], :branch => parts[4] }
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                (only_last_day ?
         
     | 
| 
      
 49 
     | 
    
         
            +
                  `tail -n #{records_to_load} #{idle_log_path}`.split("\n") :
         
     | 
| 
      
 50 
     | 
    
         
            +
                  File.foreach(idle_log_path)).each do |line|
         
     | 
| 
      
 51 
     | 
    
         
            +
                  parts = line.split("\t")
         
     | 
| 
      
 52 
     | 
    
         
            +
                  next if !parts[1] || parts[1].empty?
         
     | 
| 
      
 53 
     | 
    
         
            +
                  @idle_parts[parts[1].strip.to_i] = { :time_elapsed => parts[0] }
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
              end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
              def crunch
         
     | 
| 
      
 58 
     | 
    
         
            +
                # tagging branches in idle_parts
         
     | 
| 
      
 59 
     | 
    
         
            +
                @commands.each do |timestamp, command_info|
         
     | 
| 
      
 60 
     | 
    
         
            +
                  if (key = search_idle_key(timestamp))
         
     | 
| 
      
 61 
     | 
    
         
            +
                    @idle_parts[key][:branch] = command_info[:branch]
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                # filling branches in idle_parts
         
     | 
| 
      
 66 
     | 
    
         
            +
                # tagging thresholds in idle_parts
         
     | 
| 
      
 67 
     | 
    
         
            +
                last_branch = 'unknown'
         
     | 
| 
      
 68 
     | 
    
         
            +
                @idle_parts.each do |timestamp, part|
         
     | 
| 
      
 69 
     | 
    
         
            +
                  if part[:branch] && part[:branch] != '' && part[:branch] != 'no_branch'
         
     | 
| 
      
 70 
     | 
    
         
            +
                    last_branch = part[:branch]
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
                  # puts "setting to #{last_branch} (#{Time.at(timestamp).strftime('%Y/%m/%d at %I:%M%p')})"
         
     | 
| 
      
 73 
     | 
    
         
            +
                  @idle_parts[timestamp][:branch] = last_branch
         
     | 
| 
      
 74 
     | 
    
         
            +
                  @idle_parts[timestamp][:idle] = part[:time_elapsed].to_i > IDLE_THRESHOLD
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
              end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
              def group
         
     | 
| 
      
 79 
     | 
    
         
            +
                @stats = @idle_parts.group_by { |ts, _| Time.at(ts).strftime('%F') }.map do |day, parts_by_day|
         
     | 
| 
      
 80 
     | 
    
         
            +
                  [
         
     | 
| 
      
 81 
     | 
    
         
            +
                    day,
         
     | 
| 
      
 82 
     | 
    
         
            +
                    parts_by_day.group_by { |_, v| v[:branch] }.map do |branch, parts_by_branch|
         
     | 
| 
      
 83 
     | 
    
         
            +
                      [
         
     | 
| 
      
 84 
     | 
    
         
            +
                        branch,
         
     | 
| 
      
 85 
     | 
    
         
            +
                        parts_by_branch.group_by { |_, v| v[:idle] }.map { |k, v| [k, v.count*10] }
         
     | 
| 
      
 86 
     | 
    
         
            +
                      ]
         
     | 
| 
      
 87 
     | 
    
         
            +
                    end
         
     | 
| 
      
 88 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              def display
         
     | 
| 
      
 93 
     | 
    
         
            +
                Hash[@stats].each do |day, by_day|
         
     | 
| 
      
 94 
     | 
    
         
            +
                  puts "#{day}:"
         
     | 
| 
      
 95 
     | 
    
         
            +
                  Hash[by_day].each do |branch, by_branch|
         
     | 
| 
      
 96 
     | 
    
         
            +
                    by_idle = Hash[by_branch]
         
     | 
| 
      
 97 
     | 
    
         
            +
                    idle_str = by_idle[true] ? "(and #{Utils.format_time(by_idle[true])} idle)" : ''
         
     | 
| 
      
 98 
     | 
    
         
            +
                    puts "  #{branch}: #{Utils.format_time(by_idle[false]) || '00:00:00'} #{idle_str}"
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
                end
         
     | 
| 
      
 101 
     | 
    
         
            +
              end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
              def sync_toggl
         
     | 
| 
      
 104 
     | 
    
         
            +
                require 'pry'
         
     | 
| 
      
 105 
     | 
    
         
            +
                sync = FtgSync.new
         
     | 
| 
      
 106 
     | 
    
         
            +
                i = 0
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                Hash[@stats].each do |day, by_day|
         
     | 
| 
      
 109 
     | 
    
         
            +
                  puts "#{day}:"
         
     | 
| 
      
 110 
     | 
    
         
            +
                  Hash[by_day].each do |branch, by_branch|
         
     | 
| 
      
 111 
     | 
    
         
            +
                    by_idle = Hash[by_branch]
         
     | 
| 
      
 112 
     | 
    
         
            +
                    idle_str = by_idle[true] ? "(and #{by_idle[true]} idle)" : ''
         
     | 
| 
      
 113 
     | 
    
         
            +
                    puts "  #{branch}: #{by_idle[false] || '00:00:00'} #{idle_str}"
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                    if branch =~ /jt-/ && by_idle[false]
         
     | 
| 
      
 116 
     | 
    
         
            +
                      ps = day.split('-')
         
     | 
| 
      
 117 
     | 
    
         
            +
                      time = Time.new(ps[0], ps[1], ps[2], 12,0,0)
         
     | 
| 
      
 118 
     | 
    
         
            +
                      begining_of_day = Time.new(ps[0], ps[1], ps[2], 0,0,0)
         
     | 
| 
      
 119 
     | 
    
         
            +
                      end_of_day = begining_of_day + (24*3600)
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                      jt = branch[/(jt-[0-9]+)/]
         
     | 
| 
      
 122 
     | 
    
         
            +
                      duration_parts = by_idle[false].split(':')
         
     | 
| 
      
 123 
     | 
    
         
            +
                      duration = duration_parts[0].to_i * 3600 + duration_parts[1].to_i * 60 + duration_parts[2].to_i
         
     | 
| 
      
 124 
     | 
    
         
            +
                      type = sync.maintenance?(jt) ? :maintenance : :sprint
         
     | 
| 
      
 125 
     | 
    
         
            +
                      sync.create_entry("#{branch} [via FTG]", duration, time, type)
         
     | 
| 
      
 126 
     | 
    
         
            +
                      i += 1
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                      puts "logging #{branch}: #{by_idle[false]}"
         
     | 
| 
      
 129 
     | 
    
         
            +
                    end
         
     | 
| 
      
 130 
     | 
    
         
            +
                  end
         
     | 
| 
      
 131 
     | 
    
         
            +
                end
         
     | 
| 
      
 132 
     | 
    
         
            +
                puts "total: #{i}"
         
     | 
| 
      
 133 
     | 
    
         
            +
              end
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
            end
         
     |