worklog 0.1.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/.github/workflows/main.yml +18 -0
- data/.gitignore +9 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +24 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/worklog +9 -0
- data/lib/worklog.rb +54 -0
- data/lib/worklog/cli.rb +88 -0
- data/lib/worklog/decorator.rb +71 -0
- data/lib/worklog/dsl.rb +81 -0
- data/lib/worklog/entities.rb +7 -0
- data/lib/worklog/entities/sheet.rb +65 -0
- data/lib/worklog/entities/track.rb +38 -0
- data/lib/worklog/printer.rb +65 -0
- data/lib/worklog/services.rb +8 -0
- data/lib/worklog/services/get_timelogs.rb +32 -0
- data/lib/worklog/services/new_timelog.rb +40 -0
- data/lib/worklog/services/service.rb +25 -0
- data/lib/worklog/version.rb +5 -0
- data/worklog.gemspec +38 -0
- metadata +90 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: 1584302fdc1291767a62be5b3873534bec302870088fcb1ca7d707ac149dd339
         | 
| 4 | 
            +
              data.tar.gz: 4bd2a089e9fc389f3f9b91345ba1207138713b3aae5950c64d5f3b59af5518dc
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 37c9fc32fd5e5bf1ae4ede3c57bf449b9df7aad707165846f54e7960f59ec42c0172e2aca6b97ac923bbab5c778db31964806a05422d9921ef8b47d8a631d2c6
         | 
| 7 | 
            +
              data.tar.gz: e49fa11b381af12402e76488c3432e8140fe41e28fcf820283abfe8780675ff0c6e3ad97543ce1b0bca3f2dcc647e3f270e89ff39b29134848ff065d723bf0a7
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            name: Ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            on: [push,pull_request]
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            jobs:
         | 
| 6 | 
            +
              build:
         | 
| 7 | 
            +
                runs-on: ubuntu-latest
         | 
| 8 | 
            +
                steps:
         | 
| 9 | 
            +
                - uses: actions/checkout@v2
         | 
| 10 | 
            +
                - name: Set up Ruby
         | 
| 11 | 
            +
                  uses: ruby/setup-ruby@v1
         | 
| 12 | 
            +
                  with:
         | 
| 13 | 
            +
                    ruby-version: 3.0.1
         | 
| 14 | 
            +
                - name: Run the default task
         | 
| 15 | 
            +
                  run: |
         | 
| 16 | 
            +
                    gem install bundler -v 2.2.15
         | 
| 17 | 
            +
                    bundle install
         | 
| 18 | 
            +
                    bundle exec rake
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/CHANGELOG.md
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/Gemfile.lock
    ADDED
    
    | @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            PATH
         | 
| 2 | 
            +
              remote: .
         | 
| 3 | 
            +
              specs:
         | 
| 4 | 
            +
                worklog (0.1.0)
         | 
| 5 | 
            +
                  thor (~> 1.0.1)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            GEM
         | 
| 8 | 
            +
              remote: https://rubygems.org/
         | 
| 9 | 
            +
              specs:
         | 
| 10 | 
            +
                minitest (5.14.4)
         | 
| 11 | 
            +
                rake (13.0.6)
         | 
| 12 | 
            +
                thor (1.0.1)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            PLATFORMS
         | 
| 15 | 
            +
              x64-mingw32
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            DEPENDENCIES
         | 
| 18 | 
            +
              minitest (~> 5.0)
         | 
| 19 | 
            +
              rake (~> 13.0)
         | 
| 20 | 
            +
              thor (~> 1.0.1)
         | 
| 21 | 
            +
              worklog!
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            BUNDLED WITH
         | 
| 24 | 
            +
               2.2.15
         | 
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            The MIT License (MIT)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) 2021 Nikolay Voynov
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            +
            of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            +
            all copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 20 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
         | 
| 21 | 
            +
            THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            # Worklog
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/worklog`. To experiment with that code, run `bin/console` for an interactive prompt.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            TODO: Delete this and the text above, and describe your gem
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ## Installation
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Add this line to your application's Gemfile:
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ```ruby
         | 
| 12 | 
            +
            gem 'worklog'
         | 
| 13 | 
            +
            ```
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            And then execute:
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                $ bundle install
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            Or install it yourself as:
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                $ gem install worklog
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ## Usage
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            TODO: Write usage instructions here
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ## Development
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            ## Contributing
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/worklog.
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            ## License
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/bin/console
    ADDED
    
    | @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require "bundler/setup"
         | 
| 5 | 
            +
            require "worklog"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            # You can add fixtures and/or initialization code here to make experimenting
         | 
| 8 | 
            +
            # with your gem easier. You can also use a different console, if you like.
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            # (If you use this, don't forget to add pry to your Gemfile!)
         | 
| 11 | 
            +
            # require "pry"
         | 
| 12 | 
            +
            # Pry.start
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require "irb"
         | 
| 15 | 
            +
            IRB.start(__FILE__)
         | 
    
        data/bin/setup
    ADDED
    
    
    
        data/exe/worklog
    ADDED
    
    
    
        data/lib/worklog.rb
    ADDED
    
    | @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "worklog/version"
         | 
| 4 | 
            +
            require_relative "worklog/entities"
         | 
| 5 | 
            +
            require_relative "worklog/services"
         | 
| 6 | 
            +
            require_relative "worklog/dsl"
         | 
| 7 | 
            +
            require_relative "worklog/cli"
         | 
| 8 | 
            +
            require_relative "worklog/decorator"
         | 
| 9 | 
            +
            require_relative "worklog/printer"
         | 
| 10 | 
            +
            require "yaml"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            module Worklog
         | 
| 13 | 
            +
              class Error < StandardError; end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              # FILE_MASK = "*.timelog".freeze
         | 
| 16 | 
            +
              LOGMASK = "*.worklog".freeze
         | 
| 17 | 
            +
              GEMHOME = ".worklog".freeze
         | 
| 18 | 
            +
              STORAGE = "index.yml".freeze
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              def params
         | 
| 21 | 
            +
                @params ||= read_params
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def indexd
         | 
| 25 | 
            +
                return if Dir.glob(LOGMASK).empty?
         | 
| 26 | 
            +
                return if params[:index].include? Dir.pwd
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                params[:index] << Dir.pwd
         | 
| 29 | 
            +
                home = File.join(Dir.home, GEMHOME)
         | 
| 30 | 
            +
                Dir.mkdir(home) unless Dir.exist?(home)
         | 
| 31 | 
            +
                File.write(
         | 
| 32 | 
            +
                  File.join(home, STORAGE),
         | 
| 33 | 
            +
                  YAML.dump(params)
         | 
| 34 | 
            +
                )
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              protected
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              def read_params
         | 
| 40 | 
            +
                para = {}
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                home = File.join(Dir.home, GEMHOME)
         | 
| 43 | 
            +
                Dir.mkdir(home) unless Dir.exist?(home)
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                file = File.join(home, 'index.yml')
         | 
| 46 | 
            +
                para = YAML.load(File.read(file)) if File.exist?(file)
         | 
| 47 | 
            +
                para[:index] ||= []
         | 
| 48 | 
            +
                para
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              def save_params
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            end
         | 
    
        data/lib/worklog/cli.rb
    ADDED
    
    | @@ -0,0 +1,88 @@ | |
| 1 | 
            +
            # encoding: UTF-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'thor'
         | 
| 4 | 
            +
            require_relative 'services'
         | 
| 5 | 
            +
            include Worklog::Services
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Worklog
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              class CLI < Thor
         | 
| 10 | 
            +
                include Thor::Actions
         | 
| 11 | 
            +
                namespace :worklog
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                no_commands {
         | 
| 14 | 
            +
                  def under_construction
         | 
| 15 | 
            +
                    say "this feature is under construction at the moment"
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def get_logs
         | 
| 19 | 
            +
                    if Dir.glob(Worklog::LOGMASK).empty?
         | 
| 20 | 
            +
                      say "No *.worklog files were found in this directory"
         | 
| 21 | 
            +
                      say "Run '$ worklog new' or create it yourself"
         | 
| 22 | 
            +
                      exit
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    Worklog.indexd
         | 
| 26 | 
            +
                    GetWorklogs.()
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                }
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                desc "version", "Show Worklog version"
         | 
| 31 | 
            +
                def version
         | 
| 32 | 
            +
                  puts "Worklog v#{Worklog::VERSION}"
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
                map %w[--version -v] => :version
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                desc "new [LOGNAME]", "Create timelog file LOGNAME the working directory"
         | 
| 37 | 
            +
                def new(logname = File.basename(Dir.pwd))
         | 
| 38 | 
            +
                  filename = CreateLog.(logname)
         | 
| 39 | 
            +
                  exit if filename.empty?
         | 
| 40 | 
            +
                  say "Worklog #{filename} created"
         | 
| 41 | 
            +
                  Worklog.indexd
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                desc "get", "Show timelog report of the working directory"
         | 
| 45 | 
            +
                def get
         | 
| 46 | 
            +
                  get_logs.each{|sh| Printer.new(Decorator.new(sh)).tracks()}
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                desc "this [PERIOD=month]", "Show timelog for this PERIOD (month/week)"
         | 
| 50 | 
            +
                def this(period = "month")
         | 
| 51 | 
            +
                  get_logs.each do |sh|
         | 
| 52 | 
            +
                    printer = Printer.new(Decorator.new(sh))
         | 
| 53 | 
            +
                    case period
         | 
| 54 | 
            +
                    when 'month'
         | 
| 55 | 
            +
                      printer.this_month
         | 
| 56 | 
            +
                    when 'week'
         | 
| 57 | 
            +
                      printer.this_week
         | 
| 58 | 
            +
                    else
         | 
| 59 | 
            +
                      raise ArgumentError, "Unknown period '#{period}'"
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                desc "prev [PERIOD=month]", "Show timelog for prev PERIOD (month/week)"
         | 
| 65 | 
            +
                def prev(period = "month")
         | 
| 66 | 
            +
                  get_logs.each do |sh|
         | 
| 67 | 
            +
                    printer = Printer.new(Decorator.new(sh))
         | 
| 68 | 
            +
                    case period
         | 
| 69 | 
            +
                    when 'month'
         | 
| 70 | 
            +
                      printer.prev_month
         | 
| 71 | 
            +
                    when 'week'
         | 
| 72 | 
            +
                      printer.prev_week
         | 
| 73 | 
            +
                    else
         | 
| 74 | 
            +
                      raise ArgumentError, "Unknown period '#{period}'"
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                desc "index", "Show all known timelog directories"
         | 
| 80 | 
            +
                def index
         | 
| 81 | 
            +
                  str = Worklog.params[:index].map{|i| "- #{i}"}.join(?n)
         | 
| 82 | 
            +
                  say "Worklogs were noticed in the following places:\n#{str}"
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
             | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            end
         | 
| @@ -0,0 +1,71 @@ | |
| 1 | 
            +
            # encoding: UTF-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'delegate'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Worklog
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              class Decorator < SimpleDelegator
         | 
| 8 | 
            +
                def initialize(sheet)
         | 
| 9 | 
            +
                  @sheet = sheet
         | 
| 10 | 
            +
                  super
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                Person = Struct.new(:date, :spent, :task, :reward)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def tracks(filter = ->(x,_){true})
         | 
| 16 | 
            +
                  items = super().select(&filter)
         | 
| 17 | 
            +
                    .each_with_object([]) do |(date, tary), out|
         | 
| 18 | 
            +
                      out << Person.new(date,
         | 
| 19 | 
            +
                        tary.map(&:spent).inject(:+),
         | 
| 20 | 
            +
                        tary.map(&:task).join(?\n),
         | 
| 21 | 
            +
                        tary.map(&:reward).inject(:+)
         | 
| 22 | 
            +
                      )
         | 
| 23 | 
            +
                    end.sort!{|a, b| a.date <=> b.date}
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  {}.tap do |out|
         | 
| 26 | 
            +
                    out[:title] = title
         | 
| 27 | 
            +
                    out[:items] = items
         | 
| 28 | 
            +
                    out[:spent] = items.map(&:spent).inject(:+)
         | 
| 29 | 
            +
                    out[:total] = items.map(&:reward).inject(:+)
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                # TODO: this year, previous year?
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def this_month
         | 
| 36 | 
            +
                  tracks(-> (x,_) { x >= start_of_month(Date.today) })
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def prev_month
         | 
| 40 | 
            +
                  till = start_of_month(Date.today) - 1
         | 
| 41 | 
            +
                  from = start_of_month(till)
         | 
| 42 | 
            +
                  tracks(-> (x,_) { x >= from && x <= till })
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            # FIXME: incorrect for sunday 1
         | 
| 46 | 
            +
                def this_week
         | 
| 47 | 
            +
                  today = Date.today
         | 
| 48 | 
            +
                  from = today - today.wday + 1
         | 
| 49 | 
            +
                  tracks(-> (x,_) { x >= from })
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            # FIXME: incorrect for sunday 1
         | 
| 53 | 
            +
                def prev_week
         | 
| 54 | 
            +
                  today = Date.today
         | 
| 55 | 
            +
                  till = today - today.wday # last sunday
         | 
| 56 | 
            +
                  from = till  - 6          # prev monday
         | 
| 57 | 
            +
                  tracks(-> (x,_) { x >= from && x <= till })
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                protected
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def start_of_month(date)
         | 
| 63 | 
            +
                  Date.new(date.year, date.month, 1)
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def end_of_month(date)
         | 
| 67 | 
            +
                  Date.new(date.year, date.month, -1)
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            end
         | 
    
        data/lib/worklog/dsl.rb
    ADDED
    
    | @@ -0,0 +1,81 @@ | |
| 1 | 
            +
            # encoding: UTF-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'entities'
         | 
| 4 | 
            +
            include Worklog::Entities
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Worklog
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              # This class provides DSL for timelogging
         | 
| 9 | 
            +
              #
         | 
| 10 | 
            +
              # @example
         | 
| 11 | 
            +
              #   sheet = DSL.build() do
         | 
| 12 | 
            +
              #     title "Worklog"
         | 
| 13 | 
            +
              #     date_format "%Y-%m-%d"
         | 
| 14 | 
            +
              #     hourly_rate 20
         | 
| 15 | 
            +
              #
         | 
| 16 | 
            +
              #     track '2021-07-10', spent: '8h', task: 'work on func requirements'
         | 
| 17 | 
            +
              #     track '2021-07-11', spent: '8h', task: 'work on func requirements'
         | 
| 18 | 
            +
              #     track '2021-07-12', spent: '8h30m', task: 'work on use cases'
         | 
| 19 | 
            +
              #     track '2021-07-13', spent: '30m', task: 'work on user stories'
         | 
| 20 | 
            +
              #     track '2021-07-13', spent: '7h30m', task: 'administrator user stories'
         | 
| 21 | 
            +
              #   end
         | 
| 22 | 
            +
              #
         | 
| 23 | 
            +
              # @example
         | 
| 24 | 
            +
              #   sheet = DSL.build() { read File.read('project.timesheet') }
         | 
| 25 | 
            +
              class DSL
         | 
| 26 | 
            +
                attr_reader :sheet
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def self.build(&block)
         | 
| 29 | 
            +
                  dsl = new
         | 
| 30 | 
            +
                  dsl.instance_eval(&block) if block_given?
         | 
| 31 | 
            +
                  dsl.sheet
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                private_class_method :new
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def initialize
         | 
| 37 | 
            +
                  @sheet = Sheet.new
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def title(title)
         | 
| 41 | 
            +
                  @sheet.title = title
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def date_format(fmt)
         | 
| 45 | 
            +
                  @sheet.date_format = fmt
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def hourly_rate(rate)
         | 
| 49 | 
            +
                  @sheet.hourly_rate = rate
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def read(text)
         | 
| 53 | 
            +
                  instance_eval text
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def read_file(filename)
         | 
| 57 | 
            +
                  text = File.read(filename)
         | 
| 58 | 
            +
                  read text
         | 
| 59 | 
            +
                  @sheet.source = filename
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def track(date, spent:, task: '', rate: nil)
         | 
| 63 | 
            +
                  @sheet.track(
         | 
| 64 | 
            +
                    Date.strptime(date, @sheet.date_format),
         | 
| 65 | 
            +
                    spent: parse_spent(spent),
         | 
| 66 | 
            +
                    rate: rate,
         | 
| 67 | 
            +
                    task: task
         | 
| 68 | 
            +
                  )
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                def parse_spent(time)
         | 
| 72 | 
            +
                  regx = /^((\d{1,2})[hH])?\s?((\d{1,2})[mM])?$/
         | 
| 73 | 
            +
                  data = time.match(regx)
         | 
| 74 | 
            +
                  raise ArgumentError, "\"spent\" format \"\d{1,2}[Hh]d{1,2}[Mm]\"" unless data
         | 
| 75 | 
            +
                  h = data[2] || 0
         | 
| 76 | 
            +
                  m = data[4] || 0
         | 
| 77 | 
            +
                  h.to_i * 60 + m.to_i
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            end
         | 
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            # encoding: UTF-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'bigdecimal'
         | 
| 4 | 
            +
            require 'date'
         | 
| 5 | 
            +
            require_relative 'track'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Worklog
         | 
| 8 | 
            +
              module Entities
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                # This class represent sheet of tracks collection
         | 
| 11 | 
            +
                class Sheet
         | 
| 12 | 
            +
                  attr_reader :tracks
         | 
| 13 | 
            +
                  attr_accessor :source
         | 
| 14 | 
            +
                  attr_accessor :date_format
         | 
| 15 | 
            +
                  attr_accessor :hourly_rate
         | 
| 16 | 
            +
                  attr_accessor :title
         | 
| 17 | 
            +
             | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def initialize
         | 
| 20 | 
            +
                    @tracks = {}
         | 
| 21 | 
            +
                    @date_format = "%Y-%m-%d"
         | 
| 22 | 
            +
                    @hourly_rate = 0
         | 
| 23 | 
            +
                    @title = ""
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  # This method adds new track to the sheet
         | 
| 27 | 
            +
                  # It will pring warning, when total spent for one date exceeds 24h
         | 
| 28 | 
            +
                  # @param date [Date] the track date
         | 
| 29 | 
            +
                  # @param spent [Number] the track spent time in minutes
         | 
| 30 | 
            +
                  # @param task [String] the tracking task description
         | 
| 31 | 
            +
                  # @param rate [Number] the track hourly rate, optional; provide it when the track hourly rate differs from specified for whole sheet (weekends, overtime, or sth)
         | 
| 32 | 
            +
                  # @return [Track] the track registered
         | 
| 33 | 
            +
                  def track(date, spent:, task:, rate: nil)
         | 
| 34 | 
            +
                    @tracks[date] ||= []
         | 
| 35 | 
            +
                    @tracks[date] << Track.new(
         | 
| 36 | 
            +
                      date,
         | 
| 37 | 
            +
                      spent: spent,
         | 
| 38 | 
            +
                      task: task,
         | 
| 39 | 
            +
                      rate: rate || @hourly_rate
         | 
| 40 | 
            +
                    )
         | 
| 41 | 
            +
                    if @tracks[date].size > 1 && spent_for(date) > Track::MAX_MINUTES
         | 
| 42 | 
            +
                      puts "Warning! Total spent for #{date} greater than 24 hours"
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                    @tracks[date].last
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  # The method calculates sum(spent) for provided date
         | 
| 48 | 
            +
                  # @param date [Date] the date for calculation
         | 
| 49 | 
            +
                  # @retun [Number] sum of spent for the date
         | 
| 50 | 
            +
                  def spent_for(date)
         | 
| 51 | 
            +
                    @tracks[date].map(&:spent).inject(:+)
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  # The method merges tracks from sheet passed as param
         | 
| 55 | 
            +
                  def merge!(sheet)
         | 
| 56 | 
            +
                    @tracks.merge!(sheet.tracks) do |date, these_tracks, merge_tracks|
         | 
| 57 | 
            +
                      puts "Warning! Date duplication detected in '#{@source}' and '#{sheet.source}'"
         | 
| 58 | 
            +
                      puts "Please, check these files manually for '#{date.to_s}'"
         | 
| 59 | 
            +
                      these_tracks + merge_tracks
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            # encoding: UTF-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'bigdecimal'
         | 
| 4 | 
            +
            require 'date'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Worklog
         | 
| 7 | 
            +
              module Entities
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                # This class represent the abstraction of a timelog item
         | 
| 10 | 
            +
                class Track
         | 
| 11 | 
            +
                  attr_reader :date
         | 
| 12 | 
            +
                  attr_reader :rate
         | 
| 13 | 
            +
                  attr_reader :task
         | 
| 14 | 
            +
                  attr_reader :spent
         | 
| 15 | 
            +
                  attr_reader :reward
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  # @param date [Time] the date of the track
         | 
| 18 | 
            +
                  # @param rate [Float] the rate of the track
         | 
| 19 | 
            +
                  # @param spent [Integer] minutes spent of the track
         | 
| 20 | 
            +
                  # @param task [String] the task descritpion of the track
         | 
| 21 | 
            +
                  def initialize(date, spent:,  rate: 0, task: "")
         | 
| 22 | 
            +
                    # the spent cannot be more than a day of 24 hours
         | 
| 23 | 
            +
                    raise ArgumentError, ":spent exceeds 24 hours" if spent > MAX_MINUTES
         | 
| 24 | 
            +
                    # the date cannot be in the future
         | 
| 25 | 
            +
                    date = date.to_date unless date.is_a? Date
         | 
| 26 | 
            +
                    raise ArgumentError, ":date in the future" if date > Date.today
         | 
| 27 | 
            +
                    @date = date
         | 
| 28 | 
            +
                    @rate = rate
         | 
| 29 | 
            +
                    @task = task
         | 
| 30 | 
            +
                    @spent = spent
         | 
| 31 | 
            +
                    reward = BigDecimal(spent.to_s) / BigDecimal(60) * BigDecimal(rate.to_s)
         | 
| 32 | 
            +
                    @reward = reward.round(2, :half_down)
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  MAX_MINUTES = 1440
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            end
         | 
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            # encoding: UTF-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'delegate'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Worklog
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              class Printer < SimpleDelegator
         | 
| 8 | 
            +
                def initialize(sheet)
         | 
| 9 | 
            +
                  @sheet = sheet
         | 
| 10 | 
            +
                  super
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                # TODO: header for this/prev month/week
         | 
| 14 | 
            +
                # TODO: grouping by months if there are more than one
         | 
| 15 | 
            +
                def print(report, subtitle = "")
         | 
| 16 | 
            +
                  puts "-= #{report[:title]} #{subtitle} =-"
         | 
| 17 | 
            +
                  puts %w(Date Year Month Week Spent Reward Task).join(?\t)
         | 
| 18 | 
            +
                  report[:items].each do |track|
         | 
| 19 | 
            +
                    row = [].tap do |r|
         | 
| 20 | 
            +
                      r << track.date.to_s
         | 
| 21 | 
            +
                      r << track.date.year
         | 
| 22 | 
            +
                      r << track.date.strftime('%B')
         | 
| 23 | 
            +
                      r << track.date.cweek
         | 
| 24 | 
            +
                      r << format_spent(track.spent)
         | 
| 25 | 
            +
                      r << track.reward.to_s(?F)
         | 
| 26 | 
            +
                      r << track.task
         | 
| 27 | 
            +
                    end.join(?\t)
         | 
| 28 | 
            +
                    puts row
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                  puts
         | 
| 31 | 
            +
                  puts "Total time\t#{format_spent(report[:spent])}"
         | 
| 32 | 
            +
                  puts "Total\t%.2f" % report[:total]
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def tracks(filter = ->(x,_){true})
         | 
| 36 | 
            +
                  print(super(&filter))
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def this_month
         | 
| 40 | 
            +
                  subtitle = "for #{Date.today.strftime("%B %Y")}"
         | 
| 41 | 
            +
                  print(super(), subtitle);
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def prev_month
         | 
| 45 | 
            +
                  subtitle = "for #{(Date.today << 1).strftime("%B %Y")}"
         | 
| 46 | 
            +
                  print(super(), subtitle)
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def this_week; print(super()); end
         | 
| 50 | 
            +
                def prev_week; print(super()); end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                protected
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def format_spent(spent)
         | 
| 55 | 
            +
                    h = spent / 60
         | 
| 56 | 
            +
                    m = spent % 60
         | 
| 57 | 
            +
                    [].tap do |out|
         | 
| 58 | 
            +
                      out << "#{h}h" if h > 0
         | 
| 59 | 
            +
                      out << "#{m}m" if m > 0
         | 
| 60 | 
            +
                    end.join(" ")
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            end
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            # encoding: UTF-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'service'
         | 
| 4 | 
            +
            require_relative '../dsl'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Worklog
         | 
| 7 | 
            +
              module Services
         | 
| 8 | 
            +
                include Worklog
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                # This service loads all timelogs in the working directory. Several
         | 
| 11 | 
            +
                # timelogs that share the same title will be merged to single sheet
         | 
| 12 | 
            +
                # with the shared title
         | 
| 13 | 
            +
                # @example
         | 
| 14 | 
            +
                #   GetLogsWorkDir.() # => Array<Sheet>
         | 
| 15 | 
            +
                class GetWorklogs < Service
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def call
         | 
| 18 | 
            +
                    [].tap do |logs|
         | 
| 19 | 
            +
                      Dir.glob(::LOGMASK) do |f|
         | 
| 20 | 
            +
                        puts "Processing #{f}.."
         | 
| 21 | 
            +
                        flog = DSL.build() { read_file(f) }
         | 
| 22 | 
            +
                        # merge by sheet title
         | 
| 23 | 
            +
                        mlog = logs.find{|i| i.title == flog.title}
         | 
| 24 | 
            +
                        mlog.nil? ? logs << flog : mlog.merge!(flog)
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| @@ -0,0 +1,40 @@ | |
| 1 | 
            +
            # encoding: UTF-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'service'
         | 
| 4 | 
            +
            require 'date'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Worklog
         | 
| 7 | 
            +
              module Services
         | 
| 8 | 
            +
                include Worklog
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                # This service creates timelog file in the working directory.
         | 
| 11 | 
            +
                #  and returns name of the created file '<folder_name>.timelog'
         | 
| 12 | 
            +
                #  If file with this name exits
         | 
| 13 | 
            +
                # @example
         | 
| 14 | 
            +
                class NewWorklog < Service
         | 
| 15 | 
            +
                  def call
         | 
| 16 | 
            +
                    filename = @logname + Worklog::LOGMASK[1..-1]
         | 
| 17 | 
            +
                    if File.exist? filename
         | 
| 18 | 
            +
                      puts "Operation canceled. File with name '#{filename}' already exists"
         | 
| 19 | 
            +
                      return ''
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
                    File.write(filename, LOGCONTENT % [@logname, Date.today.to_s])
         | 
| 22 | 
            +
                    filename
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def initialize(logname)
         | 
| 26 | 
            +
                    @logname = logname
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  LOGCONTENT = <<~EOF
         | 
| 30 | 
            +
                    title %s
         | 
| 31 | 
            +
                    hourly_rate 20
         | 
| 32 | 
            +
                    date_format '%%Y-%%m-%%d'
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    track '%s', spent: '1h', task: 'created timelog ..'
         | 
| 35 | 
            +
                  EOF
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            # encoding: UTF-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Worklog
         | 
| 4 | 
            +
              module Services
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                class Service
         | 
| 7 | 
            +
                  if RUBY_VERSION =~ /3\.[[:digit:]]+\.[[:digit:]]/
         | 
| 8 | 
            +
                    def self.call(*args, **opts)
         | 
| 9 | 
            +
                      new(*args, **opts).call
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
                  else
         | 
| 12 | 
            +
                    def self.call(*args)
         | 
| 13 | 
            +
                      new(*args).call
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  private_class_method :new
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  # Should be implemented in subclasses
         | 
| 20 | 
            +
                  def call
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
    
        data/worklog.gemspec
    ADDED
    
    | @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "lib/worklog/version"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Gem::Specification.new do |spec|
         | 
| 6 | 
            +
              spec.name          = "worklog"
         | 
| 7 | 
            +
              spec.version       = Worklog::VERSION
         | 
| 8 | 
            +
              spec.authors       = ["Nikolay Voynov"]
         | 
| 9 | 
            +
              spec.email         = ["nvoynov@gmail.com"]
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              spec.summary       = "Time logging DSL and CLI for extended reporting"
         | 
| 12 | 
            +
              spec.description   = %q(The Worklog gem provides a simple domain-specific language (DSL) for tracking work time and a command-line interface (CLI) for processing the time spent. It'll answer some basic questions like "how much time I've spent on this project this month?" or "how much I've earned the previous week?")
         | 
| 13 | 
            +
              spec.homepage      = "https://github.com/nvoynov/worklog"
         | 
| 14 | 
            +
              spec.license       = "MIT"
         | 
| 15 | 
            +
              spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              spec.metadata["allowed_push_host"] = "https://rubygems.org"
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              spec.metadata["homepage_uri"] = spec.homepage
         | 
| 20 | 
            +
              spec.metadata["source_code_uri"] = "https://github.com/nvoynov/worklog"
         | 
| 21 | 
            +
              spec.metadata["changelog_uri"] = "https://github.com/nvoynov/worklog/CHANGELOG.md"
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              # Specify which files should be added to the gem when it is released.
         | 
| 24 | 
            +
              # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
         | 
| 25 | 
            +
              spec.files = Dir.chdir(File.expand_path(__dir__)) do
         | 
| 26 | 
            +
                `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
              spec.bindir        = "exe"
         | 
| 29 | 
            +
              spec.executables   = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
         | 
| 30 | 
            +
              spec.require_paths = ["lib"]
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              # Uncomment to register a new dependency of your gem
         | 
| 33 | 
            +
              # spec.add_dependency "example-gem", "~> 1.0"
         | 
| 34 | 
            +
              spec.add_dependency "thor", "~> 1.0.1"
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              # For more information and examples about making a new gem, checkout our
         | 
| 37 | 
            +
              # guide at: https://bundler.io/guides/creating_gem.html
         | 
| 38 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: worklog
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Nikolay Voynov
         | 
| 8 | 
            +
            autorequire:
         | 
| 9 | 
            +
            bindir: exe
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2021-07-24 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: thor
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: 1.0.1
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: 1.0.1
         | 
| 27 | 
            +
            description: The Worklog gem provides a simple domain-specific language (DSL) for
         | 
| 28 | 
            +
              tracking work time and a command-line interface (CLI) for processing the time spent.
         | 
| 29 | 
            +
              It'll answer some basic questions like "how much time I've spent on this project
         | 
| 30 | 
            +
              this month?" or "how much I've earned the previous week?"
         | 
| 31 | 
            +
            email:
         | 
| 32 | 
            +
            - nvoynov@gmail.com
         | 
| 33 | 
            +
            executables:
         | 
| 34 | 
            +
            - worklog
         | 
| 35 | 
            +
            extensions: []
         | 
| 36 | 
            +
            extra_rdoc_files: []
         | 
| 37 | 
            +
            files:
         | 
| 38 | 
            +
            - ".github/workflows/main.yml"
         | 
| 39 | 
            +
            - ".gitignore"
         | 
| 40 | 
            +
            - CHANGELOG.md
         | 
| 41 | 
            +
            - Gemfile
         | 
| 42 | 
            +
            - Gemfile.lock
         | 
| 43 | 
            +
            - LICENSE.txt
         | 
| 44 | 
            +
            - README.md
         | 
| 45 | 
            +
            - Rakefile
         | 
| 46 | 
            +
            - bin/console
         | 
| 47 | 
            +
            - bin/setup
         | 
| 48 | 
            +
            - exe/worklog
         | 
| 49 | 
            +
            - lib/worklog.rb
         | 
| 50 | 
            +
            - lib/worklog/cli.rb
         | 
| 51 | 
            +
            - lib/worklog/decorator.rb
         | 
| 52 | 
            +
            - lib/worklog/dsl.rb
         | 
| 53 | 
            +
            - lib/worklog/entities.rb
         | 
| 54 | 
            +
            - lib/worklog/entities/sheet.rb
         | 
| 55 | 
            +
            - lib/worklog/entities/track.rb
         | 
| 56 | 
            +
            - lib/worklog/printer.rb
         | 
| 57 | 
            +
            - lib/worklog/services.rb
         | 
| 58 | 
            +
            - lib/worklog/services/get_timelogs.rb
         | 
| 59 | 
            +
            - lib/worklog/services/new_timelog.rb
         | 
| 60 | 
            +
            - lib/worklog/services/service.rb
         | 
| 61 | 
            +
            - lib/worklog/version.rb
         | 
| 62 | 
            +
            - worklog.gemspec
         | 
| 63 | 
            +
            homepage: https://github.com/nvoynov/worklog
         | 
| 64 | 
            +
            licenses:
         | 
| 65 | 
            +
            - MIT
         | 
| 66 | 
            +
            metadata:
         | 
| 67 | 
            +
              allowed_push_host: https://rubygems.org
         | 
| 68 | 
            +
              homepage_uri: https://github.com/nvoynov/worklog
         | 
| 69 | 
            +
              source_code_uri: https://github.com/nvoynov/worklog
         | 
| 70 | 
            +
              changelog_uri: https://github.com/nvoynov/worklog/CHANGELOG.md
         | 
| 71 | 
            +
            post_install_message:
         | 
| 72 | 
            +
            rdoc_options: []
         | 
| 73 | 
            +
            require_paths:
         | 
| 74 | 
            +
            - lib
         | 
| 75 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 76 | 
            +
              requirements:
         | 
| 77 | 
            +
              - - ">="
         | 
| 78 | 
            +
                - !ruby/object:Gem::Version
         | 
| 79 | 
            +
                  version: 2.4.0
         | 
| 80 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 81 | 
            +
              requirements:
         | 
| 82 | 
            +
              - - ">="
         | 
| 83 | 
            +
                - !ruby/object:Gem::Version
         | 
| 84 | 
            +
                  version: '0'
         | 
| 85 | 
            +
            requirements: []
         | 
| 86 | 
            +
            rubygems_version: 3.2.15
         | 
| 87 | 
            +
            signing_key:
         | 
| 88 | 
            +
            specification_version: 4
         | 
| 89 | 
            +
            summary: Time logging DSL and CLI for extended reporting
         | 
| 90 | 
            +
            test_files: []
         |