fewald-worklog 0.2.37 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8a2feb90b202dad33157a6dfb35bc78d980efce61ec0a9cf369fb45b552308b
4
- data.tar.gz: 9ff8b56050dca9ec76ea27d24da8ba21efde809da211d9db7429906eaf106799
3
+ metadata.gz: 22137019cd21b55db99d610f3183f545caefeffd123b6964db91809f2573ea54
4
+ data.tar.gz: fbca2b87bb511aea6fb101541ad9ec769709a8f5a9fe1aa92f89525fa450ee51
5
5
  SHA512:
6
- metadata.gz: dcf962ca95d50c20cd715c29a562b259c93c0f292ecc7c3820d40321f02bf73b8c9a58f48bebdc8eefef3a83286676e999ca3a24e5cb690e9d34f1713a1bec90
7
- data.tar.gz: b730874e4acf1840e1704924b062eeca2a2163394f481e2efc4fd5ef1c5dbd997fa8a1062e1c1f1dafb9f45af48148b6adc27c0e401186399a68de2c63cc0781
6
+ metadata.gz: ce13695b6dce75f1b55a672ab6441cd84d6ef5190ff72ff1c4b6b02a81acb808be599a0bf574fbe03396424a658f182bf243ab77c6b3ac3edbcdfb529135fc3f
7
+ data.tar.gz: 71a111514f86249cb239c40247c6951cf24c72aa1b67dbdf9983d30da7d60cf3c6ec6f6484eb206089e01ff86fa9d9ed5eb77905aae3efe9e3bfad5c56958e82
data/.version CHANGED
@@ -1 +1 @@
1
- 0.2.37
1
+ 0.3.1
data/lib/cli.rb CHANGED
@@ -71,6 +71,12 @@ class WorklogCLI < Thor
71
71
  worklog.edit(options)
72
72
  end
73
73
 
74
+ desc 'github', 'Fetch latest events from GitHub for a specified user and add them to the work log.'
75
+ def github
76
+ worklog = Worklog::Worklog.new
77
+ worklog.github(options)
78
+ end
79
+
74
80
  desc 'remove', 'Remove last entry from the log'
75
81
  option :date, type: :string, default: Time.now.strftime('%Y-%m-%d')
76
82
  def remove
data/lib/configuration.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tzinfo'
3
4
  require 'worklogger'
4
5
  require 'yaml'
5
6
 
@@ -13,9 +14,72 @@ module Worklog
13
14
  # @!attribute [rw] webserver_port
14
15
  # @return [Integer] The port on which the web server runs.
15
16
  # Default is 3000.
17
+ # @!attribute [rw] project
18
+ # @return [Configuration::ProjectConfig] Project related configuration.
19
+ # @!attribute [rw] github
20
+ # @return [Configuration::GithubConfig] Github related configuration.
21
+ #
22
+ # @example Example ~/.worklog.yaml
23
+ # storage_path: /Users/username/.worklog
24
+ # log_level: debug
25
+ # timezone: 'America/Los_Angeles'
26
+ # webserver_port: 4000
27
+ #
28
+ # project:
29
+ # show_last: 3
30
+ #
31
+ # github:
32
+ # api_key: 123abc
33
+ # username: sample-user
16
34
  class Configuration
17
- attr_accessor :storage_path, :log_level, :webserver_port
35
+ attr_accessor :storage_path, :log_level, :timezone, :webserver_port, :project, :github
18
36
 
37
+ # Configuration for projects
38
+ # @!attribute [rw] show_last
39
+ # @return [Integer] Number of last projects to show in the project list.
40
+ class ProjectConfig
41
+ attr_accessor :show_last
42
+
43
+ # Initialize with default values, parameters can be overridden via hash
44
+ # @example
45
+ # ProjectConfig.new({'show_last' => 5})
46
+ def initialize(params = {})
47
+ return if params.nil?
48
+
49
+ params.each do |key, value|
50
+ instance_variable_set("@#{key}", value) if respond_to?("#{key}=")
51
+ end
52
+ end
53
+ end
54
+
55
+ # Configuration for Github API access.
56
+ # @!attribute [rw] api_key
57
+ # @return [String] The API key for Github access.
58
+ # @!attribute [rw] username
59
+ # @return [String] The Github username.
60
+ class GithubConfig
61
+ attr_accessor :api_key, :username
62
+
63
+ # Initialize with default values, parameters can be overridden via hash
64
+ # @example
65
+ # GithubConfig.new({'api_key' => '123abc', 'username' => 'sample-user'})
66
+ def initialize(params = {})
67
+ return if params.nil?
68
+
69
+ params.each do |key, value|
70
+ instance_variable_set("@#{key}", value) if respond_to?("#{key}=")
71
+ end
72
+ end
73
+ end
74
+
75
+ # Initialize configuration with optional block for setting attributes.
76
+ # If no block is given, default values are used.
77
+ # @example
78
+ # Configuration.new do |config|
79
+ # config.storage_path = '/custom/path'
80
+ # config.log_level = :debug
81
+ # config.timezone = 'America/Los_Angeles'
82
+ # end
19
83
  def initialize(&block)
20
84
  block.call(self) if block_given?
21
85
 
@@ -23,7 +87,11 @@ module Worklog
23
87
  @storage_path ||= File.join(Dir.home, '.worklog')
24
88
  @log_level = log_level || :info
25
89
  @log_level = @log_level.to_sym if @log_level.is_a?(String)
90
+ @timezone ||= 'America/Los_Angeles'
91
+ @timezone = TZInfo::Timezone.get(@timezone) if @timezone.is_a?(String)
26
92
  @webserver_port ||= 3000
93
+ @project = ProjectConfig.new
94
+ @github ||= GithubConfig.new
27
95
  end
28
96
 
29
97
  # Load configuration from a YAML file in the user's home directory.
@@ -37,6 +105,9 @@ module Worklog
37
105
  config.storage_path = file_cfg['storage_path'] if file_cfg['storage_path']
38
106
  config.log_level = file_cfg['log_level'].to_sym if file_cfg['log_level']
39
107
  config.webserver_port = file_cfg['webserver_port'] if file_cfg['webserver_port']
108
+
109
+ config.project = ProjectConfig.new(file_cfg['project'])
110
+ config.github = GithubConfig.new(file_cfg['github'])
40
111
  else
41
112
  WorkLogger.debug "Configuration file does not exist in #{file_path}, using defaults."
42
113
  end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+ require 'github/pull_request_details'
5
+ require 'github/pull_request_event'
6
+ require 'github/pull_request_review_event'
7
+ require 'github/push_event'
8
+ require 'worklogger'
9
+
10
+ module Worklog
11
+ module Github
12
+ # Client to interact with GitHub API
13
+ class Client
14
+ class GithubAPIError < StandardError; end
15
+
16
+ EVENT_FILTER = Set.new(%w[
17
+ PullRequestEvent
18
+ PullRequestReviewEvent
19
+
20
+ ]).freeze
21
+
22
+ def initialize(configuration)
23
+ @configuration = configuration
24
+ end
25
+
26
+ # Fetch events for a given user from Github API
27
+ def fetch_events
28
+ verify_token!
29
+
30
+ WorkLogger.debug 'Fetching most recent GitHub events...'
31
+ responses = fetch_event_pages
32
+ responses.filter_map { |event| create_event(event) }
33
+ end
34
+
35
+ def pull_request_data(repo, pr_number)
36
+ response = HTTParty.get("https://api.github.com/repos/#{repo}/pulls/#{pr_number}",
37
+ headers: { 'Authorization' => "token #{TOKEN}" })
38
+ response.parsed_response
39
+ end
40
+
41
+ def pull_request_comments(repo, pr_number)
42
+ response = HTTParty.get("https://api.github.com/repos/#{repo}/pulls/#{pr_number}/comments",
43
+ headers: { 'Authorization' => "token #{TOKEN}" })
44
+ response.parsed_response
45
+ end
46
+
47
+ private
48
+
49
+ def create_event(event)
50
+ return unless EVENT_FILTER.include?(event['type'])
51
+
52
+ case event['type']
53
+ when 'PullRequestEvent'
54
+ create_pull_request_event(event)
55
+ when 'PullRequestReviewEvent'
56
+ create_pull_request_review_event(event)
57
+ end
58
+ end
59
+
60
+ def create_pull_request_event(event)
61
+ payload = event['payload']
62
+ repo = event['repo']
63
+
64
+ # Retrieve details for the specific pull request
65
+ pr_details = pull_request_details(repo['name'], payload['number'])
66
+
67
+ PullRequestEvent.new(
68
+ repository: repo['name'],
69
+ number: payload['number'],
70
+ **pr_details.to_h.slice(:url, :title, :description, :created_at, :merged_at, :closed_at)
71
+ )
72
+ end
73
+
74
+ def create_pull_request_review_event(event)
75
+ repo_name = event['repo']['name']
76
+ payload = event['payload']
77
+ pr_number = payload['pull_request']['number']
78
+
79
+ review = payload['review']
80
+ review_state = review['state']
81
+ url = review['html_url']
82
+ created_at = to_local_time(review['submitted_at'])
83
+
84
+ pr_details = pull_request_details(repo_name, pr_number)
85
+
86
+ PullRequestReviewEvent.new(
87
+ repository: repo_name,
88
+ number: pr_number,
89
+ url:,
90
+ title: pr_details.title,
91
+ description: pr_details.description,
92
+ creator: pr_details.creator,
93
+ state: review_state,
94
+ created_at: created_at
95
+ )
96
+ end
97
+
98
+ # Get detailed information about a specific pull request
99
+ # @param repo [String] Repository name in the format 'owner/repo'
100
+ # @param pr_number [Integer] Pull request number
101
+ # @return [PullRequestDetails] Struct containing pull request details
102
+ def pull_request_details(repo, pr_number)
103
+ WorkLogger.debug "Fetching details for PR ##{pr_number} in #{repo}..."
104
+ response = HTTParty.get("https://api.github.com/repos/#{repo}/pulls/#{pr_number}",
105
+ headers: { 'Authorization' => "token #{@configuration.github.api_key}" })
106
+
107
+ if response.code == 403
108
+ raise GithubAPIError,
109
+ 'Failed to fetch PR details: Are you connected to the corporate VPN? (HTTP 403 Forbidden)'
110
+ elsif response.code != 200
111
+ raise GithubAPIError, "Failed to fetch PR details: HTTPCode #{response.code}"
112
+ end
113
+
114
+ pr = response.parsed_response
115
+ PullRequestDetails.new(
116
+ title: pr['title'],
117
+ description: pr['body'],
118
+ creator: pr['user'] ? pr['user']['login'] : nil,
119
+ url: pr['html_url'],
120
+ state: pr['state'],
121
+ merged: pr['merged'],
122
+ created_at: to_local_time(pr['created_at']),
123
+ merged_at: to_local_time(pr['merged_at']),
124
+ closed_at: to_local_time(pr['closed_at'])
125
+ )
126
+ end
127
+
128
+ # Generic method to perform GET requests to GitHub API
129
+ def github_api_get(url)
130
+ response = HTTParty.get(url, headers: { 'Authorization' => "token #{@configuration.github.api_key}" })
131
+ raise GithubAPIError, "GitHub API request failed with code #{response.code}" unless response.code == 200
132
+
133
+ # TODO: Respect rate limit headers
134
+ # headers = response.headers
135
+ # puts "Remaining: #{headers['X-RateLimit-Remaining']}"
136
+ # puts "Reset: #{headers['X-RateLimit-Reset']}"
137
+ # puts "Limit: #{headers['X-RateLimit-Limit']}"
138
+ # puts "Used: #{headers['X-RateLimit-Used']}"
139
+ response.parsed_response
140
+ end
141
+
142
+ # Fetch the maximum number of events with pagination for the configured user
143
+ # @return [Array<Hash>] Array of event hashes
144
+ def fetch_event_pages
145
+ responses = []
146
+ (1..3).each do |page|
147
+ responses += github_api_get("https://api.github.com/users/#{@configuration.github.username}/events?per_page=100&page=#{page}")
148
+ end
149
+ responses
150
+ end
151
+
152
+ # Convert a DateTime to local time based on configuration timezone
153
+ # @param time [Time, String] The Time to convert
154
+ # @return [Time, nil] The converted Time in local time, or nil if input is nil
155
+ def to_local_time(time)
156
+ return nil if time.nil?
157
+
158
+ time = Time.parse(time) if time.is_a?(String)
159
+
160
+ @configuration.timezone.utc_to_local(time)
161
+ end
162
+
163
+ # Verify that the GitHub API token is present in the configuration
164
+ # @raise [GithubAPIError] if the API token is missing
165
+ def verify_token!
166
+ WorkLogger.debug 'Verifying GitHub API token presence'
167
+ @configuration.github.api_key || raise(GithubAPIError,
168
+ 'GitHub API key is not configured. Please set it in the configuration.')
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Worklog
4
+ module Github
5
+ # Details of a pull request, used internally
6
+ PullRequestDetails = Struct.new('PullRequestDetails', :title, :description, :creator, :url, :state, :merged,
7
+ :created_at, :merged_at, :closed_at, keyword_init: true) do
8
+ def merged?
9
+ state == 'closed' && merged == true
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hasher'
4
+ require 'log_entry'
5
+ require 'worklogger'
6
+
7
+ module Worklog
8
+ module Github
9
+ # An event representing a pull request
10
+ # @!attribute [rw] repository
11
+ # @return [String] the repository name
12
+ # @!attribute [rw] number
13
+ # @return [Integer] the pull request number
14
+ # @!attribute [rw] url
15
+ # @return [String] the URL of the pull request
16
+ # @!attribute [rw] title
17
+ # @return [String] the title of the pull request
18
+ # @!attribute [rw] description
19
+ # @return [String] the description of the pull request
20
+ # @!attribute [rw] created_at
21
+ # @return [Time] the creation time of the pull request
22
+ # @!attribute [rw] merged_at
23
+ # @return [Time, nil] the time the pull request was merged, or nil if not merged
24
+ # @!attribute [rw] closed_at
25
+ # @return [Time, nil] the time the pull request was closed, or nil if not closed
26
+ class PullRequestEvent
27
+ attr_accessor :repository, :number, :url, :title, :description, :created_at, :merged_at, :closed_at
28
+
29
+ def initialize(params = {})
30
+ params.each do |key, value|
31
+ send("#{key}=", value) if respond_to?("#{key}=")
32
+ end
33
+ end
34
+
35
+ # Returns true if the pull request was merged.
36
+ # Usually, a merged pull request is also closed.
37
+ # @return [Boolean] true if merged, false otherwise
38
+ def merged?
39
+ !merged_at.nil?
40
+ end
41
+
42
+ # Returns true if the pull request was closed.
43
+ # A closed pull request may or may not be merged.
44
+ # @return [Boolean] true if closed, false otherwise
45
+ # @see #merged?
46
+ def closed?
47
+ # Treat merged pull requests as closed
48
+ !closed_at.nil? || (merged? && closed_at.nil?)
49
+ end
50
+
51
+ # Convert the PullRequestEvent to a LogEntry
52
+ # @return [LogEntry]
53
+ def to_log_entry
54
+ message = if merged?
55
+ 'Merged PR '
56
+ elsif closed?
57
+ 'Closed PR '
58
+ else
59
+ 'Created PR '
60
+ end
61
+ message += title
62
+
63
+ # If merged, use merged_at time; if closed, use closed_at time; otherwise, use created_at time
64
+ time = if merged?
65
+ merged_at
66
+ elsif closed?
67
+ closed_at
68
+ else
69
+ created_at
70
+ end
71
+ key = Hasher.sha256("#{repository}#{number}#{merged?}#{closed?}")
72
+ LogEntry.new(
73
+ key:,
74
+ source: 'github',
75
+ time:,
76
+ message:,
77
+ url: url,
78
+ epic: false,
79
+ ticket: nil
80
+ )
81
+ end
82
+
83
+ # String representation of the PullRequestEvent
84
+ # @return [String]
85
+ def to_s
86
+ short_url = url.length > 10 ? "...#{url[-10..]}" : url
87
+ unless description.nil?
88
+ short_description = description.gsub(/\n+/, ' ')
89
+ short_description = "#{short_description[0..16]}..." if short_description.length > 20
90
+ end
91
+ "#<PullRequestEvent repository=#{repository} number=#{number} url=#{short_url} title=#{title} " \
92
+ "description=#{short_description} " \
93
+ "created_at=#{created_at} merged_at=#{merged_at} closed_at=#{closed_at}>"
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hasher'
4
+ require 'log_entry'
5
+ require 'worklogger'
6
+
7
+ module Worklog
8
+ module Github
9
+ # Event representing a pull request review
10
+ # @!attribute [rw] repository
11
+ # @return [String] the repository name
12
+ # @!attribute [rw] number
13
+ # @return [Integer] the pull request number
14
+ # @!attribute [rw] url
15
+ # @return [String] the URL of the pull request review
16
+ # @!attribute [rw] title
17
+ # @return [String] the title of the pull request
18
+ # @!attribute [rw] description
19
+ # @return [String] the description of the pull request
20
+ # @!attribute [rw] creator
21
+ # @return [String] the username of the pull request creator
22
+ # @!attribute [rw] created_at
23
+ # @return [Time] the creation time of the pull request review, not the pull request itself
24
+ # @!attribute [rw] state
25
+ # @return [String] the state of the review (e.g., 'approved', 'changes_requested', etc.)
26
+ class PullRequestReviewEvent
27
+ attr_accessor :repository, :number, :url, :title, :description, :creator, :created_at, :state
28
+
29
+ def initialize(params = {})
30
+ params.each do |key, value|
31
+ send("#{key}=", value) if respond_to?("#{key}=")
32
+ end
33
+ end
34
+
35
+ # Whether the pull request review was approved
36
+ # @return [Boolean]
37
+ def approved?
38
+ state.downcase == 'approved'
39
+ end
40
+
41
+ # Convert the PullRequestReviewEvent to a LogEntry
42
+ # @return [LogEntry]
43
+ def to_log_entry
44
+ message = String.new 'Reviewed '
45
+ message << 'and approved ' if approved?
46
+ message << "PR ##{number} #{creator}: #{title}"
47
+ LogEntry.new(
48
+ key: Hasher.sha256("#{repository}-#{number}-#{state}"),
49
+ source: 'github',
50
+ time: created_at,
51
+ message: message,
52
+ url: url
53
+ )
54
+ end
55
+
56
+ # String representation of the PullRequestReviewEvent
57
+ # @return [String]
58
+ def to_s
59
+ "#<PullRequestReviewEvent repository=#{repository} number=#{number} state=#{state} creator=#{creator} created_at=#{created_at}>" # rubocop:disable Layout/LineLength
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'log_entry'
4
+
5
+ module Worklog
6
+ module Github
7
+ # Event representing a push event
8
+ class PushEvent
9
+ def to_log_entry
10
+ WorkLogger.debug('Converting PushEvent to LogEntry')
11
+ LogEntry.new(
12
+ key: 'github-push-event',
13
+ source: 'github',
14
+ time: Time.now, # TODO: Fix this
15
+ message: 'Push event occurred',
16
+ url: nil
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
data/lib/log_entry.rb CHANGED
@@ -10,6 +10,8 @@ module Worklog
10
10
  # @see DailyLog
11
11
  # @!attribute [rw] key
12
12
  # @return [String] the unique key of the log entry. The key is generated based on the time and message.
13
+ # @!attribute [rw] source
14
+ # @return [String] the source of the log entry, e.g., 'github', 'manual', etc.
13
15
  # @!attribute [rw] time
14
16
  # @return [Time] the date and time of the log entry.
15
17
  # @!attribute [rw] tags
@@ -29,14 +31,15 @@ module Worklog
29
31
 
30
32
  include Hashify
31
33
 
32
- attr_accessor :key, :time, :tags, :ticket, :url, :epic, :message, :project
34
+ attr_accessor :key, :source, :time, :tags, :ticket, :url, :epic, :message, :project
33
35
 
34
36
  attr_reader :day
35
37
 
36
38
  def initialize(params = {})
37
39
  # key can be nil. This is needed for backwards compatibility with older log entries.
38
40
  @key = params[:key]
39
- @time = params[:time]
41
+ @source = params[:source] || 'manual'
42
+ @time = params[:time].is_a?(String) ? Time.parse(params[:time]) : params[:time]
40
43
  # If tags are nil, set to empty array.
41
44
  # This is similar to the CLI default value.
42
45
  @tags = params[:tags] || []
@@ -72,24 +75,21 @@ module Worklog
72
75
  end
73
76
  end
74
77
 
75
- s = ''
78
+ s = String.new
76
79
 
77
- s += if epic
78
- Rainbow("[EPIC] #{msg}").bg(:white).fg(:black)
80
+ # Prefix with [EPIC] if epic
81
+ s << epic_prefix if epic?
82
+
83
+ # Print the message
84
+ s << if source == 'github'
85
+ Rainbow(msg).fg(:green)
79
86
  else
80
87
  msg
81
88
  end
82
89
 
83
- s += " [#{Rainbow(@ticket).fg(:blue)}]" if @ticket
84
-
85
- # Add tags in brackets if defined.
86
- s += ' [' + @tags.map { |tag| "#{tag}" }.join(', ') + ']' if @tags && @tags.size > 0
87
-
88
- # Add URL in brackets if defined.
89
- s += " [#{@url}]" if @url && @url != ''
90
-
91
- s += " [#{@project}]" if @project && @project != ''
90
+ s << " [#{Rainbow(@ticket).fg(:blue)}]" if @ticket
92
91
 
92
+ s << format_metadata
93
93
  s
94
94
  end
95
95
 
@@ -130,5 +130,25 @@ module Worklog
130
130
  time == other.time && tags == other.tags && ticket == other.ticket && url == other.url &&
131
131
  epic == other.epic && message == other.message
132
132
  end
133
+
134
+ private
135
+
136
+ # Prefix for epic entries with formatting.
137
+ # @return [String]
138
+ def epic_prefix
139
+ "#{Rainbow('[EPIC]').bg(:white).fg(:black)} "
140
+ end
141
+
142
+ # Format metadata for display.
143
+ # @return [String]
144
+ def format_metadata
145
+ metadata_parts = []
146
+ metadata_parts << Rainbow(@ticket).fg(:blue) if @ticket
147
+ metadata_parts << @tags.join(', ') if @tags&.any?
148
+ metadata_parts << @url if @url && @url != ''
149
+ metadata_parts << @project if @project && @project != ''
150
+
151
+ metadata_parts.empty? ? '' : " [#{metadata_parts.join('] [')}]"
152
+ end
133
153
  end
134
154
  end
data/lib/person.rb CHANGED
@@ -15,7 +15,7 @@
15
15
  class Person
16
16
  attr_reader :handle, :name, :email, :team, :notes
17
17
 
18
- def initialize(handle, name, email, team, notes = [])
18
+ def initialize(handle:, name:, email:, team:, notes: [])
19
19
  @handle = handle
20
20
  @name = name
21
21
  @email = email
@@ -40,7 +40,7 @@ class Person
40
40
  email = hash[:email] || hash['email']
41
41
  team = hash[:team] || hash['team']
42
42
  notes = hash[:notes] || hash['notes'] || []
43
- Person.new(handle, name, email, team, notes)
43
+ Person.new(handle: handle, name: name, email: email, team: team, notes: notes)
44
44
  end
45
45
 
46
46
  def to_s
data/lib/printer.rb CHANGED
@@ -7,8 +7,10 @@ class Printer
7
7
  attr_reader :people
8
8
 
9
9
  # Initializes the printer with a list of people.
10
+ # @param configuration [Configuration] The configuration.
10
11
  # @param people [Array<Person>] An array of Person objects.
11
- def initialize(people = nil)
12
+ def initialize(configuration, people = nil)
13
+ @configuration = configuration
12
14
  @people = people || {}
13
15
  end
14
16
 
@@ -58,14 +60,18 @@ class Printer
58
60
  # @param entry [LogEntry]
59
61
  # @param date_inline [Boolean] If true, the date is printed inline with the time.
60
62
  def print_entry(daily_log, entry, date_inline = false)
61
- entry.time = DateTime.strptime(entry.time, '%H:%M:%S') unless entry.time.respond_to?(:strftime)
63
+ # Backwards compatibility: convert strings to Date/Time objects if necessary
64
+ entry.time = Time.strptime(entry.time, '%H:%M:%S') unless entry.time.respond_to?(:strftime)
65
+
66
+ # Convert to local time zone
67
+ entry.time = entry.time.getlocal(@configuration.timezone) if @configuration.timezone
62
68
 
63
69
  time_string = if date_inline
64
70
  "#{daily_log.date.strftime('%a, %Y-%m-%d')} #{entry.time.strftime('%H:%M')}"
65
71
  else
66
72
  entry.time.strftime('%H:%M')
67
73
  end
68
-
74
+ print ' ' unless date_inline
69
75
  puts "#{Rainbow(time_string).gold} #{entry.message_string(@people)}"
70
76
  end
71
77
  end
data/lib/storage.rb CHANGED
@@ -124,6 +124,9 @@ module Worklog
124
124
  WorkLogger.debug "Writing to file #{file}"
125
125
 
126
126
  File.open(file, 'w') do |f|
127
+ # Sort entries by time before saving
128
+ daily_log.entries.sort_by!(&:time)
129
+
127
130
  f.puts daily_log.to_yaml
128
131
  end
129
132
  end
data/lib/worklog.rb CHANGED
@@ -8,6 +8,7 @@ require 'yaml'
8
8
  require 'configuration'
9
9
  require 'daily_log'
10
10
  require 'date_parser'
11
+ require 'github/client'
11
12
  require 'hash'
12
13
  require 'hasher'
13
14
  require 'log_entry'
@@ -98,9 +99,6 @@ module Worklog
98
99
  url: options[:url], epic: options[:epic], message:, project: options[:project])
99
100
  daily_log << new_entry
100
101
 
101
- # Sort by time in case an entry was added later out of order.
102
- daily_log.entries.sort_by!(&:time)
103
-
104
102
  @storage.write_log(@storage.filepath(options[:date]), daily_log)
105
103
 
106
104
  (new_entry.people - @people.keys).each do |handle|
@@ -144,7 +142,7 @@ module Worklog
144
142
  # worklog.show(from: '2023-10-01', to: '2023-10-31')
145
143
  # worklog.show(date: '2023-10-01')
146
144
  def show(options = {})
147
- printer = Printer.new(@people)
145
+ printer = Printer.new(@config, @people)
148
146
 
149
147
  start_date, end_date = start_end_date(options)
150
148
 
@@ -194,7 +192,7 @@ module Worklog
194
192
  end
195
193
 
196
194
  def person_detail(all_logs, all_people, person)
197
- printer = Printer.new(all_people)
195
+ printer = Printer.new(@config, all_people)
198
196
  puts "All interactions with #{Rainbow(person.name).gold}"
199
197
 
200
198
  if person.notes
@@ -341,7 +339,7 @@ module Worklog
341
339
  # @example
342
340
  # worklog.tag_detail('example_tag', from: '2023-10-01', to: '2023-10-31')
343
341
  def tag_detail(tag, options)
344
- printer = Printer.new(@people)
342
+ printer = Printer.new(@config, @people)
345
343
  start_date, end_date = start_end_date(options)
346
344
 
347
345
  @storage.days_between(start_date, end_date).each do |daily_log|
@@ -371,7 +369,7 @@ module Worklog
371
369
 
372
370
  # Do nothing if no entries are found.
373
371
  if entries.empty?
374
- Printer.new.no_entries(start_date, end_date)
372
+ Printer.new(@config).no_entries(start_date, end_date)
375
373
  return
376
374
  end
377
375
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fewald-worklog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.37
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Friedrich Ewald
@@ -107,6 +107,20 @@ dependencies:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
109
  version: '1.3'
110
+ - !ruby/object:Gem::Dependency
111
+ name: tzinfo
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '2.0'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '2.0'
110
124
  description: |
111
125
  Command line tool for tracking achievments, tasks and interactions.
112
126
 
@@ -127,6 +141,11 @@ files:
127
141
  - lib/daily_log.rb
128
142
  - lib/date_parser.rb
129
143
  - lib/editor.rb
144
+ - lib/github/client.rb
145
+ - lib/github/pull_request_details.rb
146
+ - lib/github/pull_request_event.rb
147
+ - lib/github/pull_request_review_event.rb
148
+ - lib/github/push_event.rb
130
149
  - lib/hash.rb
131
150
  - lib/hasher.rb
132
151
  - lib/log_entry.rb