newsman 0.2.2 → 1.2.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: 4b217b1d0fee448151ff8a04b2099a6e311275e781112144a87f62a4e2a49e03
4
- data.tar.gz: 78de209a3631b3e1589caab3b1bb336925009bcd52861e570e53b589d3ed8a65
3
+ metadata.gz: 28993b9f11754759d159a42a76b5979bec9c12980098de771abd5442fc124ccd
4
+ data.tar.gz: 1e98570b8d3b0cb9b0694472e5fefa7fe780ce6434efa12cd231dfa86a31c885
5
5
  SHA512:
6
- metadata.gz: 317b520b605b309c9ce5aeed0ac3ba385afb6865a8469a3547189232f248011920a67c3a3cc9bb40470e1f0f0ec0b7014f667f106275e56e9df48469d810f757
7
- data.tar.gz: 65eacb6e1debdcb1581af1d40c0b043d641825fc70780b01471f496c0bf5a336fbc8d13a43eab89ea0dacd39890b7ec38f684c842472d9be970508de20a46f70
6
+ metadata.gz: a807c540f352124e1a89d485cccfb2f0cf6ff36300b42d57ebb591b5e3e4b03d12413325f69a75d7f99e309c0813d336b1c3e32bb71012e22dd1093129cee5a6
7
+ data.tar.gz: 81a635a5caba68596e9c3acd7a242fbf597da0cb06ce87c2103cdab84c1e2e89ef59ec9812e997ff970a0484a16ace406415a83b8c642f6db8ff0f82e571c111
data/README.md CHANGED
@@ -6,6 +6,32 @@
6
6
 
7
7
  Newsman is a simple script that collects information about a developer's weekly activity on GitHub and creates a human-readable summary of the work done. To create the summary, Newsman asks ChatGPT to handle all the information about a developer's activity, including their pull requests and created issues.
8
8
 
9
+ ## How it Works
10
+
11
+ `newsman` sends multiple requests to GitHub to retrieve the following data:
12
+
13
+ 1. **Recently Closed Pull Requests by the Author**
14
+ The tool fetches pull requests that were closed recently and were created by the specified author.
15
+ Example query:
16
+
17
+ ```
18
+ is:pr author:volodya-lombrozo created:>=2024-11-29 repo:objectionary/jeo-maven-plugin repo:objectionary/opeo-maven-plugin
19
+ ```
20
+
21
+ 2. **Open Issues Assigned to the Author with the `soon` Label**
22
+ The tool retrieves issues that:
23
+ - Were opened in the last year
24
+ - Are currently open
25
+ - Are assigned to the specified author
26
+ - Have the label `soon`
27
+
28
+ Example query:
29
+ ```
30
+ is:issue is:open assignee:volodya-lombrozo created:>=2023-12-06 repo:objectionary/jeo-maven-plugin repo:objectionary/opeo-maven-plugin label:soon
31
+ ```
32
+
33
+ Once the data is retrieved, the script analyzes it using the model specified with the `--model` option.
34
+
9
35
  ## Install
10
36
 
11
37
  To install [newsman](https://rubygems.org/gems/newsman) from RubyGems.org use the following command:
@@ -36,7 +62,7 @@ Usage: newsman [options]
36
62
  -u, --username USERNAME GitHub username. For example, 'volodya-lombrozo'
37
63
  -r, --repository REPOSITORIES Specify which repositories to include in a report. You can specify several repositories using a comma separator, for example: '-r objectionary/jeo-maven-plugin,objectionary/opeo-maven-plugin'
38
64
  -p, --position POSITION Reporter position in a company. Default value is a 'Software Developer'.
39
- -o, --output OUTPUT Output type. Newsman prints a report to a stdout by default. You can choose another options like '-o html', '-o txt' or even '-o html'
65
+ -o, --output OUTPUT Output type. Newsman prints a report to a stdout by default. You can choose another options like '-o html', '-o txt' or '-o docx'. The 'html' output also generates a downloadable '.docx' copy (Times New Roman, 12pt) and links it from the page.
40
66
  -t, --title TITLE Project Title. Empty by default
41
67
  -m, --model MODEL AI model to use. gpt-3.5-turbo by default
42
68
  ```
@@ -53,6 +79,8 @@ Also you can download this repository and run the newsman script directly using
53
79
  ./bin/newsman --name "Vladimir Lombrozo" --username "volodya-lombrozo" --repository objectionary/jeo-maven-plugin,objectionary/opeo-maven-plugin
54
80
  ```
55
81
 
82
+ Actually, this command is also useful during development process.
83
+
56
84
  ## How to build a gem from sources
57
85
 
58
86
  To create a newsman gem from sources first of all you need to build it:
@@ -25,11 +25,6 @@ require 'openai'
25
25
  # This class mimics a robot that can analyse a developer activity.
26
26
  # The assistant uses OpenAI API to analyze.
27
27
  class Assistant
28
- CONTEXT = 'You are a developer tasked'\
29
- ' with composing a concise report detailing your activities'\
30
- ' and progress for the previous week,'\
31
- ' intended for submission to your supervisor.'
32
-
33
28
  def initialize(token, model: 'gpt-3.5-turbo', temperature: 0.3)
34
29
  @token = token
35
30
  @model = model
@@ -117,18 +112,32 @@ class Assistant
117
112
  send(prompt)
118
113
  end
119
114
 
115
+ # rubocop:disable Metrics/MethodLength
120
116
  def send(request)
121
- @client.chat(
122
- parameters: {
123
- model: @model,
124
- messages: [
125
- { role: 'system', content: CONTEXT },
126
- { role: 'user', content: request.to_s }
127
- ],
128
- temperature: @temperature
129
- }
130
- ).dig('choices', 0, 'message', 'content')
117
+ if @model == 'o1-preview'
118
+ @client.chat(
119
+ parameters: {
120
+ model: @model,
121
+ messages: [{ role: 'user', content: request.to_s }]
122
+ }
123
+ ).dig('choices', 0, 'message', 'content')
124
+ else
125
+ @client.chat(
126
+ parameters: {
127
+ model: @model,
128
+ messages: [
129
+ { role: 'system', content: 'You are a developer tasked'\
130
+ ' with composing a concise report detailing your activities'\
131
+ ' and progress for the previous week,'\
132
+ ' intended for submission to your supervisor.' },
133
+ { role: 'user', content: request.to_s }
134
+ ],
135
+ temperature: @temperature
136
+ }
137
+ ).dig('choices', 0, 'message', 'content')
138
+ end
131
139
  end
140
+ # rubocop:enable Metrics/MethodLength
132
141
 
133
142
  def deprecated(method)
134
143
  warn "Warning! '#{method}' is deprecated and will be removed in future versions."
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Volodya Lombrozo
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 all
13
+ # 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 NONINFINGEMENT. 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 THE
21
+ # SOFTWARE.
22
+
23
+ require 'zip'
24
+ require 'cgi'
25
+
26
+ # This class represents a report output in DOCX (MS Word) format.
27
+ # It builds a minimal, valid OOXML package by hand (no external docx-builder
28
+ # gem), so every run of text is explicitly styled with Times New Roman, 12pt.
29
+ class Docxout
30
+ # Content types part, required by every OOXML package.
31
+ CONTENT_TYPES = <<~XML
32
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
33
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/></Types>
34
+ XML
35
+
36
+ # Package-level relationships, pointing at the main document part.
37
+ RELS = <<~XML
38
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
39
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>
40
+ XML
41
+
42
+ FONT = 'Times New Roman'
43
+ # Word expresses font size in half-points, so 12pt is 24.
44
+ SIZE = '24'
45
+
46
+ def initialize(root = '.')
47
+ @root = root
48
+ end
49
+
50
+ def print(report, reporter, model)
51
+ puts "Create a docx file in a directory #{@root}"
52
+ path = File.join(@root, filename(reporter, model))
53
+ Zip::File.open(path, create: true) do |zip|
54
+ zip.get_output_stream('[Content_Types].xml') { |f| f.write(CONTENT_TYPES) }
55
+ zip.get_output_stream('_rels/.rels') { |f| f.write(RELS) }
56
+ zip.get_output_stream('word/document.xml') { |f| f.write(document_xml(report)) }
57
+ end
58
+ puts "Report was successfully printed to a #{path}"
59
+ File.basename(path)
60
+ end
61
+
62
+ def filename(reporter, model)
63
+ date = Time.new.strftime('%d.%m.%Y')
64
+ model = model.gsub('.', '-')
65
+ "#{date}.#{reporter}.#{model}.docx"
66
+ end
67
+
68
+ private
69
+
70
+ def document_xml(report)
71
+ body = paragraphs(report).map { |lines| paragraph_xml(lines) }.join
72
+ <<~XML
73
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
74
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>#{body}<w:sectPr/></w:body></w:document>
75
+ XML
76
+ end
77
+
78
+ def paragraphs(report)
79
+ report.to_s.split(/\n{2,}/).map { |paragraph| paragraph.split("\n") }
80
+ end
81
+
82
+ def paragraph_xml(lines)
83
+ runs = lines.each_with_index.map do |line, index|
84
+ "#{run_xml(line)}#{'<w:r><w:br/></w:r>' if index < lines.size - 1}"
85
+ end.join
86
+ "<w:p>#{runs}</w:p>"
87
+ end
88
+
89
+ def run_xml(line)
90
+ "<w:r><w:rPr><w:rFonts w:ascii=\"#{FONT}\" w:hAnsi=\"#{FONT}\" w:cs=\"#{FONT}\"/>" \
91
+ "<w:sz w:val=\"#{SIZE}\"/><w:szCs w:val=\"#{SIZE}\"/></w:rPr>" \
92
+ "<w:t xml:space=\"preserve\">#{CGI.escapeHTML(line)}</w:t></w:r>"
93
+ end
94
+ end
@@ -40,8 +40,8 @@ class Github
40
40
 
41
41
  def issues(username, repositories)
42
42
  one_year_ago = Date.today.prev_year.strftime('%Y-%m-%d')
43
- query = "is:issue is:open author:#{username}"\
44
- " author:0pdd created:>=#{one_year_ago} #{repositories}"
43
+ query = "is:issue is:open assignee:#{username}"\
44
+ " created:>=#{one_year_ago} #{repositories} label:#{IMPORTANT_ISSUE}"
45
45
  puts "Searching issues using the following query: '#{query}'"
46
46
  @client.search_issues(query).items.map do |issue|
47
47
  parse_issue(issue)
@@ -23,6 +23,7 @@
23
23
  require 'erb'
24
24
  require 'redcarpet'
25
25
  require 'nokogiri'
26
+ require_relative 'docx_output'
26
27
 
27
28
  # This class represents a report output in HTML format.
28
29
  class Htmlout
@@ -32,6 +33,7 @@ class Htmlout
32
33
  </head>
33
34
  <body>
34
35
  <h1><%= title %></h1>
36
+ <p><a href="<%= docx %>">Download as Word (.docx)</a></p>
35
37
  <%= body %>
36
38
  </body>
37
39
  HTML
@@ -44,6 +46,7 @@ class Htmlout
44
46
  def print(report, reporter, model)
45
47
  title = title(reporter)
46
48
  body = to_html(report)
49
+ docx = Docxout.new(@root).print(report, reporter, model)
47
50
  puts "Create a html file in a directory #{@root}"
48
51
  file = File.new(File.join(@root, filename(reporter, model)), 'w')
49
52
  puts "File #{file.path} was successfully created"
@@ -23,6 +23,8 @@
23
23
  require 'net/http'
24
24
  require 'json'
25
25
 
26
+ IMPORTANT_ISSUE = 'soon'
27
+
26
28
  # This class represents a GitHub Issue abstraction created by a user.
27
29
  class Issue
28
30
  attr_accessor :title, :body, :repo, :number
@@ -69,7 +71,7 @@ class Issue
69
71
  end
70
72
 
71
73
  def important?
72
- labels.include? 'soon'
74
+ labels.include? IMPORTANT_ISSUE
73
75
  end
74
76
  end
75
77
 
@@ -124,7 +126,7 @@ class PddIssue
124
126
  end
125
127
 
126
128
  def important?
127
- labels.include? 'soon'
129
+ labels.include? IMPORTANT_ISSUE
128
130
  end
129
131
 
130
132
  def url
data/lib/newsman.rb CHANGED
@@ -31,6 +31,7 @@ require_relative 'newsman/issues'
31
31
  require_relative 'newsman/stdout_output'
32
32
  require_relative 'newsman/txt_output'
33
33
  require_relative 'newsman/html_output'
34
+ require_relative 'newsman/docx_output'
34
35
  require_relative 'newsman/report'
35
36
  require_relative 'newsman/assistant'
36
37
  require_relative 'newsman/github'
@@ -60,7 +61,7 @@ def generate
60
61
  end
61
62
  opts.on('-o', '--output OUTPUT',
62
63
  'Output type. Newsman prints a report to a stdout by default.'\
63
- "You can choose another options like '-o html', '-o txt' or even '-o html'") do |o|
64
+ "You can choose another options like '-o html', '-o txt' or '-o docx'") do |o|
64
65
  options[:output] = o
65
66
  end
66
67
  opts.on('-t', '--title TITLE', 'Project Title. Empty by default') { |t| options[:title] = t }
@@ -128,16 +129,21 @@ def generate
128
129
  full_answer = report.append_additional(full_answer)
129
130
  output_mode = options[:output]
130
131
  puts "Output mode is '#{output_mode}'"
131
- if output_mode.eql? 'txt'
132
- puts 'Print result to txy file'
132
+ case output_mode
133
+ when 'txt'
134
+ puts 'Print result to a txt file'
133
135
  output = Txtout.new('.')
134
136
  output.print(full_answer, github_username)
135
- elsif output_mode.eql? 'html'
136
- puts 'Print result to html file'
137
+ when 'html'
138
+ puts 'Print result to a html file'
137
139
  output = Htmlout.new('.')
138
140
  output.print(full_answer, github_username, options[:model])
141
+ when 'docx'
142
+ puts 'Print result to a docx file'
143
+ output = Docxout.new('.')
144
+ output.print(full_answer, github_username, options[:model])
139
145
  else
140
- puts 'Print result to stdout'
146
+ puts 'Print result to a stdout'
141
147
  output = Stdout.new
142
148
  output.print(full_answer)
143
149
  end
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright (c) 2024 Volodya Lombrozo
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the 'Software'), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+
24
+ require 'minitest/autorun'
25
+ require 'date'
26
+ require 'zip'
27
+ require_relative '../lib/newsman/docx_output'
28
+
29
+ class TestDocxout < Minitest::Test
30
+ def test_writes_to_a_docx_file
31
+ Dir.mktmpdir do |temp_dir|
32
+ output = Docxout.new(temp_dir)
33
+ today = Date.today.strftime('%d.%m.%Y')
34
+ expected = "#{today}.volodya-lombrozo.gpt-3-5-turbo.docx"
35
+ returned = output.print("Issue description\n\nHere is a new paragraph\nList is here:\n - one\n - two\n - three",
36
+ 'volodya-lombrozo',
37
+ 'gpt-3.5-turbo')
38
+ assert_equal(expected, returned)
39
+ assert(File.exist?(File.join(temp_dir, expected)))
40
+ end
41
+ end
42
+
43
+ def test_docx_uses_times_new_roman_12pt
44
+ Dir.mktmpdir do |temp_dir|
45
+ output = Docxout.new(temp_dir)
46
+ path = File.join(temp_dir, output.print('Issue description', 'volodya-lombrozo', 'gpt-3.5-turbo'))
47
+ document_xml = Zip::File.open(path) { |zip| zip.read('word/document.xml') }
48
+ assert_includes(document_xml, 'w:ascii="Times New Roman"')
49
+ assert_includes(document_xml, '<w:sz w:val="24"/>')
50
+ assert_includes(document_xml, 'Issue description')
51
+ end
52
+ end
53
+ end
data/test/test_htmlout.rb CHANGED
@@ -22,6 +22,7 @@
22
22
  # SOFTWARE.
23
23
 
24
24
  require 'minitest/autorun'
25
+ require 'date'
25
26
  require_relative '../lib/newsman/html_output'
26
27
 
27
28
  class TestHtmlout < Minitest::Test
@@ -32,6 +33,7 @@ class TestHtmlout < Minitest::Test
32
33
  </head>
33
34
  <body>
34
35
  <h1>volodya-lombrozo #{Time.new.strftime('%d.%m.%Y')}</h1>
36
+ <p><a href=\"#{Time.new.strftime('%d.%m.%Y')}.volodya-lombrozo.gpt-3-5-turbo.docx\">Download as Word (.docx)</a></p>
35
37
  <p>Issue description</p>
36
38
 
37
39
  <p>Here is a new paragraph<br/>
@@ -56,4 +58,14 @@ List is here:<br/>
56
58
  assert_equal(EXPECTED, File.read(File.join(temp_dir, expected)))
57
59
  end
58
60
  end
61
+
62
+ def test_also_writes_a_docx_file
63
+ Dir.mktmpdir do |temp_dir|
64
+ output = Htmlout.new(temp_dir)
65
+ today = Date.today.strftime('%d.%m.%Y')
66
+ expected = "#{today}.volodya-lombrozo.gpt-3-5-turbo.docx"
67
+ output.print('Issue description', 'volodya-lombrozo', 'gpt-3.5-turbo')
68
+ assert(File.exist?(File.join(temp_dir, expected)))
69
+ end
70
+ end
59
71
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: newsman
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Volodya Lombrozo
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-09-02 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: minitest
@@ -164,6 +163,20 @@ dependencies:
164
163
  - - "~>"
165
164
  - !ruby/object:Gem::Version
166
165
  version: '6.3'
166
+ - !ruby/object:Gem::Dependency
167
+ name: rubyzip
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '2.3'
173
+ type: :runtime
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '2.3'
167
180
  description: A simple gem that gathers GitHub statistics and creates human-readable
168
181
  report
169
182
  email:
@@ -178,6 +191,7 @@ files:
178
191
  - bin/newsman
179
192
  - lib/newsman.rb
180
193
  - lib/newsman/assistant.rb
194
+ - lib/newsman/docx_output.rb
181
195
  - lib/newsman/github.rb
182
196
  - lib/newsman/html_output.rb
183
197
  - lib/newsman/issues.rb
@@ -186,6 +200,7 @@ files:
186
200
  - lib/newsman/stdout_output.rb
187
201
  - lib/newsman/txt_output.rb
188
202
  - test/test_assistant.rb
203
+ - test/test_docxout.rb
189
204
  - test/test_htmlout.rb
190
205
  - test/test_issue.rb
191
206
  - test/test_pdd_issue.rb
@@ -198,7 +213,6 @@ homepage: https://github.com/volodya-lombrozo/newsman
198
213
  licenses:
199
214
  - MIT
200
215
  metadata: {}
201
- post_install_message:
202
216
  rdoc_options: []
203
217
  require_paths:
204
218
  - lib
@@ -214,8 +228,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
214
228
  - !ruby/object:Gem::Version
215
229
  version: '0'
216
230
  requirements: []
217
- rubygems_version: 3.5.18
218
- signing_key:
231
+ rubygems_version: 4.0.16
219
232
  specification_version: 4
220
233
  summary: GitHub user weekly news
221
234
  test_files: []