jekyll-chatgpt-translate 0.0.20 → 0.0.22

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76da34820682d38fd53ca0c5c02bf8d9e23f5665af2a6f0553f11a62d6fce452
4
- data.tar.gz: 413def3d7da352c1a6c2d7dbd67722329573732d53bfbae96a1bf86c39c9c16e
3
+ metadata.gz: daa42e099235943fc1a5473a83796abe90b7949d1365c9d9b7349c7c86be7406
4
+ data.tar.gz: 6c7559aea364339a8b2ac5eac397e314491166b77cfff92565218f6cdd04499b
5
5
  SHA512:
6
- metadata.gz: 715bdfed0bf33249c0091a66a0f338d89469aaa43db6fb6f80bf79a2e633d68ed0087997ce7b08b259bc24a47196061d3114d2c3f66ab8f1c7f76cc9758e0a2c
7
- data.tar.gz: 6a0eeb27d20053284535b0a053ab3e0832eb6e20477bb3451ac095ce5ab48e967b7e5e607aa777269475ce846b784414382e1367ceb88daf5e24ad458440c745
6
+ metadata.gz: e651bfae3d35ddf473555eb168cf3301ba6dacda8352fd00a610613c9a2dc8821a1188064089f701ae94c4e4b2277f41b5d635842ae4132f92e9242828ecc0ac
7
+ data.tar.gz: a18e7007a9f40cfdba340285c8ef665c8c47d8e829ac6f76d304b77cec89b14ce9e9c1d56ddfd72f010429d4620f712ee773ed7011942d771c49993799bcef23
data/.rubocop.yml CHANGED
@@ -18,7 +18,7 @@ Layout/MultilineMethodCallIndentation:
18
18
  Metrics/AbcSize:
19
19
  Enabled: false
20
20
  Metrics/BlockLength:
21
- Max: 50
21
+ Max: 100
22
22
  Metrics/CyclomaticComplexity:
23
23
  Max: 20
24
24
  Metrics/PerceivedComplexity:
data/README.md CHANGED
@@ -1,4 +1,3 @@
1
- [![Gem Version](https://badge.fury.io/rb/glogin.svg)](http://badge.fury.io/rb/glogin)
2
1
  <img src="logo.png" style="width:256px;"/>
3
2
 
4
3
  [![rake](https://github.com/yegor256/jekyll-chatgpt-translate/actions/workflows/rake.yml/badge.svg)](https://github.com/yegor256/jekyll-chatgpt-translate/actions/workflows/rake.yml)
data/features/cli.feature CHANGED
@@ -31,6 +31,11 @@ Feature: Simple site building
31
31
  Chinese: {{ content }}
32
32
  The original: {{ page.translated-original-url }}
33
33
  """
34
+ And I have a "_layouts/translated.html" file with content:
35
+ """
36
+ French: {{ content }}
37
+ The original: {{ page.translated-original-url }}
38
+ """
34
39
  And I have a "_posts/2023-01-01-hello.md" file with content:
35
40
  """
36
41
  ---
@@ -40,6 +45,7 @@ Feature: Simple site building
40
45
  Hello, world!
41
46
  """
42
47
  Then I build Jekyll site
48
+ And Exit code is zero
43
49
  And File "_chatgpt-translated/zh/2023-01-01-hello-zh.md" exists
44
50
  And File "_chatgpt-translated/zh/2023-01-01-hello-zh.md" contains "/2023-01-01-hello-chinese.html"
45
51
  And File "_chatgpt-translated/zh/2023-01-01-hello-zh.md" contains "translated-language: \"zh\""
@@ -48,5 +54,34 @@ Feature: Simple site building
48
54
  And File "_site/2023-01-01-hello-chinese.html" exists
49
55
  And File "_site/2023-01-01-hello-chinese.html" contains "The original: /2023/01/01/hello.html"
50
56
  And File "_site/2023/hello-french.html" exists
51
- And Exit code is zero
52
57
 
58
+ Scenario: Simple download of existing page
59
+ Given I have a "_config.yml" file with content:
60
+ """
61
+ url: https://www.yegor256.com
62
+ markdown: kramdown
63
+ plugins:
64
+ - jekyll-chatgpt-translate
65
+ chatgpt-translate:
66
+ source: en
67
+ version: ""
68
+ api_key: "it-is-not-used, because EN to EN translation"
69
+ layout: should-not-be-used
70
+ targets:
71
+ -
72
+ language: en
73
+ permalink: about-me.html
74
+ """
75
+ And I have a "_posts/2023-01-01-hello.md" file with content:
76
+ """
77
+ ---
78
+ title: foo
79
+ ---
80
+ foo
81
+ """
82
+ Then I build Jekyll site
83
+ And Exit code is zero
84
+ And Stdout contains "No need to translate, the page exists"
85
+ And File "_site/2023/01/01/hello.html" exists
86
+ And File "_site/about-me.html" exists
87
+ And File "_site/about-me.html" contains "Yegor Bugayenko"
@@ -28,7 +28,7 @@ Gem::Specification.new do |s|
28
28
  s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
29
29
  s.required_ruby_version = '>= 2.6'
30
30
  s.name = 'jekyll-chatgpt-translate'
31
- s.version = '0.0.20'
31
+ s.version = '0.0.22'
32
32
  s.license = 'MIT'
33
33
  s.summary = 'Translate Jekyll Pages Through ChatGPT'
34
34
  s.description = [
@@ -50,7 +50,10 @@ class GptTranslate::ChatGPT
50
50
  def translate(markdown, min: 32)
51
51
  markdown.split(/\n{2,}/).compact.map do |par|
52
52
  par.strip!
53
- if par.length < min
53
+ if @source == @target
54
+ Jekyll.logger.debug("No need to translate from #{@source.inspect} to #{@target.inspect}: #{par.inspect}")
55
+ par
56
+ elsif par.length < min
54
57
  Jekyll.logger.debug("Not translating this, b/c too short: #{par.inspect}")
55
58
  par
56
59
  elsif par.start_with?('```') || par.end_with?('```')
@@ -58,7 +58,7 @@ class GptTranslate::Generator < Jekyll::Generator
58
58
  translated = 0
59
59
  copied = 0
60
60
  model = config['model'] || 'gpt-3.5-turbo'
61
- marker = "Translated by ChatGPT #{model}/#{version}"
61
+ marker = "Translated by ChatGPT #{model}#{version.empty? ? '' : "/#{version}"}"
62
62
  site.posts.docs.shuffle.each do |doc|
63
63
  plain = GptTranslate::Plain.new(doc.content).to_s
64
64
  config['targets'].each do |target|
@@ -67,9 +67,22 @@ class GptTranslate::Generator < Jekyll::Generator
67
67
  raise 'Language must be defined for each target' if target.nil?
68
68
  path = File.join(home, lang, doc.basename.gsub(/\.md$/, "-#{lang}.md"))
69
69
  FileUtils.mkdir_p(File.dirname(path))
70
- File.write(path, '') # in order to surpress warnings in Page ctor
71
- dest = Jekyll::Page.new(site, site.source, File.dirname(path), File.basename(path)).destination(site.dest)
72
- if config['no_download'].nil? && GptTranslate::Ping.new(site, link).found?(dest, version)
70
+ File.write(
71
+ path,
72
+ [
73
+ '---',
74
+ "layout: #{target['layout'] || layout}",
75
+ "title: #{doc['title'].to_json}",
76
+ "description: #{doc['description'].to_json}",
77
+ "permalink: #{link.to_json}",
78
+ "translated-original-url: #{doc.url.to_json}",
79
+ "translated-language: #{lang.to_json}",
80
+ "chatgpt-model: #{model.to_json}",
81
+ '---'
82
+ ].join("\n")
83
+ )
84
+ ping = GptTranslate::Ping.new(site, link)
85
+ if config['no_download'].nil? && ping.found?(version.empty? ? '' : marker)
73
86
  copied += 1
74
87
  elsif translated >= threshold
75
88
  next
@@ -84,20 +97,12 @@ class GptTranslate::Generator < Jekyll::Generator
84
97
  File.write(
85
98
  path,
86
99
  [
87
- '---',
88
- "layout: #{target['layout'] || layout}",
89
- "title: #{doc['title'].to_json}",
90
- "description: #{doc['description'].to_json}",
91
- "permalink: #{link.to_json}",
92
- "translated-original-url: #{doc.url.to_json}",
93
- "translated-language: #{lang.to_json}",
94
- "chatgpt-model: #{model.to_json}",
95
- '---',
96
100
  '',
97
101
  foreign,
98
102
  '',
99
103
  "#{marker} on #{Time.now.strftime('%Y-%m-%d at %H:%M')}\n{: .jekyll-chatgpt-translate}"
100
- ].join("\n")
104
+ ].join("\n"),
105
+ mode: 'a+'
101
106
  )
102
107
  site.pages << Jekyll::Page.new(site, site.source, File.dirname(path), File.basename(path))
103
108
  translated += 1
@@ -107,7 +112,8 @@ class GptTranslate::Generator < Jekyll::Generator
107
112
  doc.data['chatgpt-model'] = model
108
113
  end
109
114
  end
110
- Jekyll.logger.info("#{translated} pages translated and #{copied} pages copied in #{(Time.now - start).round(2)}s")
115
+ Jekyll.logger.info("jekyll-chatgpt-translate #{GptTranslate::VERSION}: \
116
+ #{translated} pages translated and #{copied} pages copied in #{(Time.now - start).round(2)}s")
111
117
  end
112
118
 
113
119
  private
@@ -128,6 +134,10 @@ class GptTranslate::Generator < Jekyll::Generator
128
134
  Jekyll.logger.info("The file with the OpenAI API key is not found: #{file.inspect}")
129
135
  nil
130
136
  end
137
+ if key.nil? && config['api_key']
138
+ Jekyll.logger.info("The OpenAI API key is found in 'api_key' of _config.yml")
139
+ key = config['api_key']
140
+ end
131
141
  if key.nil? && Jekyll.env == 'development'
132
142
  Jekyll.logger.info("OPENAI_API_KEY environment variable is not set, \
133
143
  the `api_key_file` option is not specified in the _config.yml, and \
@@ -25,6 +25,7 @@
25
25
  require 'iri'
26
26
  require 'net/http'
27
27
  require 'uri'
28
+ require 'jekyll'
28
29
  require 'fileutils'
29
30
  require_relative 'version'
30
31
 
@@ -47,7 +48,7 @@ class GptTranslate::Ping
47
48
  @path = path
48
49
  end
49
50
 
50
- def found?(file, marker)
51
+ def found?(marker)
51
52
  home = @site.config['url']
52
53
  return false if home.nil?
53
54
  uri = Iri.new(home).path(@path).to_s
@@ -57,20 +58,34 @@ class GptTranslate::Ping
57
58
  html = before.body
58
59
  if html.include?(marker)
59
60
  Jekyll.logger.info("No need to translate, the page exists at \
60
- #{uri.inspect} (#{html.split.count} words), saved to #{file.inspect}")
61
- FileUtils.mkdir_p(File.dirname(file))
62
- File.write(file, html)
61
+ #{uri.inspect} (#{html.split.count} words)")
62
+ @site.static_files << DownloadedFile.new(@site, @path, html)
63
63
  return true
64
64
  end
65
65
  Jekyll.logger.info("Re-translation required for #{uri.inspect}")
66
66
  else
67
- Jekyll.logger.info("The page is absent, will translate #{uri.inspect}")
67
+ Jekyll.logger.info("The page is absent, will translate #{uri.inspect} (#{before.code})")
68
68
  end
69
69
  Jekyll.logger.debug("GET #{uri.inspect}: #{before.code}")
70
- rescue StandardError => e
70
+ rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL => e
71
71
  Jekyll.logger.debug("Failed to ping #{uri.inspect}: #{e.message}")
72
72
  Jekyll.logger.info("The page is absent (#{e.class.name}): #{uri.inspect}")
73
73
  end
74
74
  false
75
75
  end
76
+
77
+ # The file we just downloaded.
78
+ class DownloadedFile < Jekyll::StaticFile
79
+ def initialize(site, path, html)
80
+ super(site, site.dest, '', path)
81
+ @html = html
82
+ end
83
+
84
+ def write(_dest)
85
+ FileUtils.mkdir_p(File.dirname(path))
86
+ File.write(path, @html)
87
+ Jekyll.logger.info("Saved #{@html.split.count} words to #{path.inspect}")
88
+ true
89
+ end
90
+ end
76
91
  end
@@ -23,5 +23,5 @@
23
23
  # SOFTWARE.
24
24
 
25
25
  module GptTranslate
26
- VERSION = '0.0.20'
26
+ VERSION = '0.0.22'
27
27
  end
data/test/test__helper.rb CHANGED
@@ -27,3 +27,107 @@ SimpleCov.start
27
27
 
28
28
  require 'jekyll'
29
29
  Jekyll.logger.adjust_verbosity(verbose: true)
30
+
31
+ # The module we are in.
32
+ module GptTranslate; end
33
+
34
+ # Fake.
35
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
36
+ # Copyright:: Copyright (c) 2023 Yegor Bugayenko
37
+ # License:: MIT
38
+ class GptTranslate::FakeSite
39
+ attr_reader :config, :pages, :static_files
40
+
41
+ def initialize(config, docs = [])
42
+ @config = config
43
+ @docs = docs
44
+ @pages = []
45
+ @static_files = []
46
+ end
47
+
48
+ def posts
49
+ GptTranslate::FakePosts.new(@docs)
50
+ end
51
+
52
+ def permalink_style
53
+ ''
54
+ end
55
+
56
+ def frontmatter_defaults
57
+ Jekyll::FrontmatterDefaults.new(self)
58
+ end
59
+
60
+ def converters
61
+ [Jekyll::Converters::Markdown.new({ 'markdown_ext' => 'md' })]
62
+ end
63
+
64
+ def source
65
+ ''
66
+ end
67
+
68
+ def dest
69
+ return '' if @docs.empty?
70
+ File.dirname(@docs[0])
71
+ end
72
+
73
+ def in_theme_dir(base, _foo = nil, _bar = nil)
74
+ base
75
+ end
76
+
77
+ def in_dest_dir(*paths)
78
+ paths[0].dup
79
+ end
80
+ end
81
+
82
+ # Fake.
83
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
84
+ # Copyright:: Copyright (c) 2023 Yegor Bugayenko
85
+ # License:: MIT
86
+ class GptTranslate::FakeDocument
87
+ attr_reader :data
88
+
89
+ def initialize(path)
90
+ @path = path
91
+ @data = { 'date' => Time.now, 'title' => 'Hello!' }
92
+ end
93
+
94
+ def content
95
+ 'Hello, world!'
96
+ end
97
+
98
+ def []=(key, value)
99
+ @data[key] = value
100
+ end
101
+
102
+ def [](key)
103
+ @data[key] || ''
104
+ end
105
+
106
+ def relative_path
107
+ @path
108
+ end
109
+
110
+ def url
111
+ '2023-01-01-hello.html'
112
+ end
113
+
114
+ def basename
115
+ '2023-01-01-hello.md'
116
+ end
117
+ end
118
+
119
+ # Fake.
120
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
121
+ # Copyright:: Copyright (c) 2023 Yegor Bugayenko
122
+ # License:: MIT
123
+ class GptTranslate::FakePosts
124
+ attr_reader :config
125
+
126
+ def initialize(docs)
127
+ @docs = docs
128
+ end
129
+
130
+ def docs
131
+ @docs.map { |d| GptTranslate::FakeDocument.new(d) }
132
+ end
133
+ end
@@ -33,98 +33,11 @@ require_relative '../lib/jekyll-chatgpt-translate/generator'
33
33
  # Copyright:: Copyright (c) 2023 Yegor Bugayenko
34
34
  # License:: MIT
35
35
  class GptTranslate::GeneratorTest < Minitest::Test
36
- class FakeSite
37
- attr_reader :config, :pages
38
-
39
- def initialize(config, docs)
40
- @config = config
41
- @docs = docs
42
- @pages = []
43
- end
44
-
45
- def posts
46
- FakePosts.new(@docs)
47
- end
48
-
49
- def permalink_style
50
- ''
51
- end
52
-
53
- def frontmatter_defaults
54
- Jekyll::FrontmatterDefaults.new(self)
55
- end
56
-
57
- def converters
58
- [Jekyll::Converters::Markdown.new({ 'markdown_ext' => 'md' })]
59
- end
60
-
61
- def source
62
- ''
63
- end
64
-
65
- def dest
66
- File.dirname(@docs[0])
67
- end
68
-
69
- def in_theme_dir(base, _foo = nil, _bar = nil)
70
- base
71
- end
72
-
73
- def in_dest_dir(*paths)
74
- paths[0].dup
75
- end
76
- end
77
-
78
- class FakeDocument
79
- attr_reader :data
80
-
81
- def initialize(path)
82
- @path = path
83
- @data = { 'date' => Time.now, 'title' => 'Hello!' }
84
- end
85
-
86
- def content
87
- 'Hello, world!'
88
- end
89
-
90
- def []=(key, value)
91
- @data[key] = value
92
- end
93
-
94
- def [](key)
95
- @data[key] || ''
96
- end
97
-
98
- def relative_path
99
- @path
100
- end
101
-
102
- def url
103
- '2023-01-01-hello.html'
104
- end
105
-
106
- def basename
107
- '2023-01-01-hello.md'
108
- end
109
- end
110
-
111
- class FakePosts
112
- attr_reader :config
113
-
114
- def initialize(docs)
115
- @docs = docs
116
- end
117
-
118
- def docs
119
- @docs.map { |d| FakeDocument.new(d) }
120
- end
121
- end
122
-
123
36
  def test_simple_scenario
124
37
  Dir.mktmpdir do |home|
125
38
  post = File.join(home, '2023-01-01-hello.md')
126
39
  File.write(post, "---\ntitle: Hello\n---\n\nHello, world!")
127
- site = FakeSite.new(
40
+ site = GptTranslate::FakeSite.new(
128
41
  {
129
42
  'url' => 'https://www.yegor256.com/',
130
43
  'chatgpt-translate' => {
@@ -150,7 +63,7 @@ class GptTranslate::GeneratorTest < Minitest::Test
150
63
  Dir.mktmpdir do |home|
151
64
  post = File.join(home, '2023-01-01-hello.md')
152
65
  File.write(post, "---\ntitle: Hello\n---\n\nHello, world!")
153
- site = FakeSite.new(
66
+ site = GptTranslate::FakeSite.new(
154
67
  {
155
68
  'chatgpt-translate' => {
156
69
  'threshold' => 1,
data/test/test_ping.rb CHANGED
@@ -34,39 +34,28 @@ require_relative '../lib/jekyll-chatgpt-translate/ping'
34
34
  # Copyright:: Copyright (c) 2023 Yegor Bugayenko
35
35
  # License:: MIT
36
36
  class GptTranslate::PingTest < Minitest::Test
37
- class FakeSite
38
- attr_reader :config
39
-
40
- def initialize(config)
41
- @config = config
42
- end
43
- end
44
-
45
37
  def test_when_exists
46
- stub_request(:any, 'https://www.yegor256.com/about-me.html')
47
- site = FakeSite.new({ 'url' => 'https://www.yegor256.com/' })
38
+ stub_request(:any, 'https://www.yegor256.com/about-me.html').to_return(body: 'Hello!')
39
+ site = GptTranslate::FakeSite.new({ 'url' => 'https://www.yegor256.com/' })
48
40
  ping = GptTranslate::Ping.new(site, '/about-me.html')
49
- Tempfile.open do |f|
50
- assert(ping.found?(f, ''))
51
- end
41
+ assert(ping.found?(''))
42
+ assert_equal(1, site.static_files.size)
52
43
  end
53
44
 
54
45
  def test_when_not_exists
55
46
  stub_request(:any, 'https://www.yegor256.com/absent.html').to_return(status: 404)
56
- site = FakeSite.new({ 'url' => 'https://www.yegor256.com/' })
47
+ site = GptTranslate::FakeSite.new({ 'url' => 'https://www.yegor256.com/' })
57
48
  ping = GptTranslate::Ping.new(site, '/absent.html')
58
- Tempfile.open do |f|
59
- assert(!ping.found?(f, ''))
60
- end
49
+ assert(!ping.found?(''))
50
+ assert_equal(0, site.static_files.size)
61
51
  end
62
52
 
63
53
  def test_wrong_address
64
54
  WebMock.allow_net_connect!
65
- site = FakeSite.new({ 'url' => 'https://localhost:1/' })
55
+ site = GptTranslate::FakeSite.new({ 'url' => 'https://localhost:1/' })
66
56
  ping = GptTranslate::Ping.new(site, '/boom.html')
67
- Tempfile.open do |f|
68
- assert(!ping.found?(f, ''))
69
- end
57
+ assert(!ping.found?(''))
58
+ assert_equal(0, site.static_files.size)
70
59
  end
71
60
 
72
61
  def test_relative_path
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-chatgpt-translate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.20
4
+ version: 0.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko