jekyll-chatgpt-translate 0.0.20 → 0.0.22
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 +4 -4
- data/.rubocop.yml +1 -1
- data/README.md +0 -1
- data/features/cli.feature +36 -1
- data/jekyll-chatgpt-translate.gemspec +1 -1
- data/lib/jekyll-chatgpt-translate/chatgpt.rb +4 -1
- data/lib/jekyll-chatgpt-translate/generator.rb +25 -15
- data/lib/jekyll-chatgpt-translate/ping.rb +21 -6
- data/lib/jekyll-chatgpt-translate/version.rb +1 -1
- data/test/test__helper.rb +104 -0
- data/test/test_generator.rb +2 -89
- data/test/test_ping.rb +10 -21
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: daa42e099235943fc1a5473a83796abe90b7949d1365c9d9b7349c7c86be7406
|
4
|
+
data.tar.gz: 6c7559aea364339a8b2ac5eac397e314491166b77cfff92565218f6cdd04499b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e651bfae3d35ddf473555eb168cf3301ba6dacda8352fd00a610613c9a2dc8821a1188064089f701ae94c4e4b2277f41b5d635842ae4132f92e9242828ecc0ac
|
7
|
+
data.tar.gz: a18e7007a9f40cfdba340285c8ef665c8c47d8e829ac6f76d304b77cec89b14ce9e9c1d56ddfd72f010429d4620f712ee773ed7011942d771c49993799bcef23
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
[](http://badge.fury.io/rb/glogin)
|
2
1
|
<img src="logo.png" style="width:256px;"/>
|
3
2
|
|
4
3
|
[](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.
|
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
|
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(
|
71
|
-
|
72
|
-
|
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("
|
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?(
|
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)
|
61
|
-
|
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
|
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
|
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
|
data/test/test_generator.rb
CHANGED
@@ -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
|
-
|
50
|
-
|
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
|
-
|
59
|
-
|
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
|
-
|
68
|
-
|
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
|