ruby-net-text 0.1.0 → 0.1.2
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/LICENSE +1 -1
- data/README.md +30 -3
- data/lib/net/gemini/client/ssl.rb +4 -5
- data/lib/net/gemini/client.rb +28 -14
- data/lib/net/gemini/response/parser.rb +11 -16
- data/lib/net/gemini/response.rb +56 -18
- data/lib/net/gemini.rb +41 -7
- data/lib/net/text/reflow.rb +22 -15
- data/lib/uri/finger.rb +1 -1
- data/lib/uri/gemini.rb +1 -1
- data/lib/uri/gopher.rb +1 -1
- data/lib/uri/nex.rb +1 -1
- metadata +6 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d42363c44449f94239e0f6ab431c925bf663ed11ee823d1fb797711011401795
|
|
4
|
+
data.tar.gz: ac7bcf1f19392f5a57efab70f71b340680cb51b21d946af00a66dbf20b49ca7e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 934907f91ea8c4d00415f7a58d238dfadda9dd5bfd017b74fdbaab530eebbc20eb1e0791ead39db5b4637eaa89bdec13fb90e6cf64dd08332ed13b7deddf61df
|
|
7
|
+
data.tar.gz: b0aa7b75364c0ba4d56be615b1d1db77b32f515d5d2a73096c1900d82509a8f7f658f264c81294e833a24cbb421e025ef9ad69c41f8d3becc28b58bd25ac1b5c
|
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# Finger, Gemini, Gopher and Nex support for Net::* and URI::*
|
|
2
2
|
|
|
3
|
-
[](https://liberapay.com/milouse/donate)
|
|
4
|
-
[](https://flattr.com/@milouse)
|
|
5
3
|
[](https://paypal.me/milouse)
|
|
6
|
-
|
|
7
4
|
[](https://rubygems.org/gems/ruby-net-text)
|
|
8
5
|
[](https://www.rubydoc.info/gems/ruby-net-text/)
|
|
9
6
|
|
|
@@ -65,3 +62,33 @@ This repository also includes 2 little helpers:
|
|
|
65
62
|
and it will output the remote file.
|
|
66
63
|
- `bin/test_thread.rb`: a toy performance test script to run against a Gemini
|
|
67
64
|
server
|
|
65
|
+
|
|
66
|
+
## Contributing
|
|
67
|
+
|
|
68
|
+
### Code of conduct
|
|
69
|
+
|
|
70
|
+

|
|
71
|
+
|
|
72
|
+
`ruby-net-text` is proudly following the
|
|
73
|
+
[Contributor Covenant Code of Conduct](https://www.contributor-covenant.org/).
|
|
74
|
+
|
|
75
|
+
[You can read it here](./CODE_OF_CONDUCT.md).
|
|
76
|
+
|
|
77
|
+
### Code
|
|
78
|
+
|
|
79
|
+
The canonical repository is located at https://git.umaneti.net/ruby-net-text.
|
|
80
|
+
A mirror also exists on [Github](https://github.com/milouse/ruby-net-text).
|
|
81
|
+
|
|
82
|
+
Merge requests and issues are accepted on github, or by mail to the main author.
|
|
83
|
+
|
|
84
|
+
Contributions must not include content generated by large language models. This
|
|
85
|
+
policy covers code, documentation, pull requests, issues, comments, and any
|
|
86
|
+
other contributions to ruby-net-text. The main reason for this, sufficient by
|
|
87
|
+
itself, is ethical issues. AI tools require an unreasonable amount of energy and
|
|
88
|
+
water to build and operate; their models are built with heavily exploited
|
|
89
|
+
workers in unacceptable working conditions. These are harms that we do not want
|
|
90
|
+
to perpetuate, even if only indirectly.¹
|
|
91
|
+
|
|
92
|
+
¹ This paragraph only is adapted from
|
|
93
|
+
[the Servo project note on AI contribution](https://book.servo.org/contributing/getting-started.html#ai-contributions),
|
|
94
|
+
available under the [Mozilla Public License 2.0](https://github.com/servo/servo/blob/main/LICENSE).
|
|
@@ -7,7 +7,7 @@ module Net
|
|
|
7
7
|
class Client
|
|
8
8
|
private
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def ssl_known_cert_valid?(new_cert, cert_file)
|
|
11
11
|
raw = File.read cert_file
|
|
12
12
|
saved_one = OpenSSL::X509::Certificate.new raw
|
|
13
13
|
return true if saved_one == new_cert
|
|
@@ -17,12 +17,12 @@ module Net
|
|
|
17
17
|
false
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def
|
|
20
|
+
def ssl_cert_valid?(cert)
|
|
21
21
|
identity_check = OpenSSL::SSL.verify_certificate_identity(cert, @host)
|
|
22
22
|
return false unless identity_check
|
|
23
23
|
|
|
24
24
|
cert_file = File.expand_path("#{@certs_path}/#{@host}.pem")
|
|
25
|
-
return
|
|
25
|
+
return ssl_known_cert_valid?(cert, cert_file) if File.exist?(cert_file)
|
|
26
26
|
|
|
27
27
|
FileUtils.mkdir_p(File.expand_path(@certs_path))
|
|
28
28
|
File.open(cert_file, 'wb') { |f| f.print cert.to_pem }
|
|
@@ -34,13 +34,12 @@ module Net
|
|
|
34
34
|
ssl_context.set_params(
|
|
35
35
|
verify_mode: OpenSSL::SSL::VERIFY_PEER,
|
|
36
36
|
min_version: OpenSSL::SSL::TLS1_2_VERSION,
|
|
37
|
-
ca_file: '/etc/ssl/certs/ca-certificates.crt',
|
|
38
37
|
verify_hostname: true
|
|
39
38
|
)
|
|
40
39
|
ssl_context.verify_callback = lambda do |preverify_ok, store_context|
|
|
41
40
|
return true if preverify_ok
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
ssl_cert_valid? store_context.current_cert
|
|
44
43
|
end
|
|
45
44
|
ssl_context
|
|
46
45
|
end
|
data/lib/net/gemini/client.rb
CHANGED
|
@@ -6,7 +6,7 @@ require_relative 'response'
|
|
|
6
6
|
require_relative '../text/generic'
|
|
7
7
|
|
|
8
8
|
module Net
|
|
9
|
-
module Gemini
|
|
9
|
+
module Gemini
|
|
10
10
|
# An example client to fetch resources hosted on Gemini network.
|
|
11
11
|
class Client
|
|
12
12
|
attr_writer :certs_path
|
|
@@ -27,8 +27,12 @@ module Net
|
|
|
27
27
|
res.reading_body(@ssl_socket)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def request(uri)
|
|
31
|
-
request! uri
|
|
30
|
+
def request(uri, &block)
|
|
31
|
+
response = request! uri
|
|
32
|
+
yield response if block
|
|
33
|
+
# In any case, read it once
|
|
34
|
+
response.read_body
|
|
35
|
+
response
|
|
32
36
|
rescue OpenSSL::SSL::SSLError => e
|
|
33
37
|
msg = format(
|
|
34
38
|
'SSLError: %<cause>s',
|
|
@@ -41,19 +45,19 @@ module Net
|
|
|
41
45
|
finish
|
|
42
46
|
end
|
|
43
47
|
|
|
44
|
-
def fetch(uri, limit = 5)
|
|
48
|
+
def fetch(uri, limit = 5, &)
|
|
45
49
|
raise Error, 'Too many Gemini redirects' if limit.zero?
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
return
|
|
51
|
+
response = request(uri, &)
|
|
52
|
+
return response unless response.status[0] == '3'
|
|
49
53
|
|
|
50
54
|
begin
|
|
51
|
-
uri = handle_redirect
|
|
55
|
+
uri = handle_redirect response
|
|
52
56
|
rescue ArgumentError, URI::InvalidURIError
|
|
53
|
-
return
|
|
57
|
+
return response
|
|
54
58
|
end
|
|
55
59
|
warn "Redirect to #{uri}" if $VERBOSE
|
|
56
|
-
fetch(uri, limit - 1)
|
|
60
|
+
fetch(uri, limit - 1, &)
|
|
57
61
|
end
|
|
58
62
|
|
|
59
63
|
private
|
|
@@ -71,6 +75,11 @@ module Net
|
|
|
71
75
|
end
|
|
72
76
|
end
|
|
73
77
|
|
|
78
|
+
# @param host_or_uri [String, ::URI]
|
|
79
|
+
# @param port [Integer, nil]
|
|
80
|
+
# @yield [self]
|
|
81
|
+
# @return [self] Returns self with no block given
|
|
82
|
+
# @return [Object] Returns the result of the block, if given
|
|
74
83
|
def self.start(host_or_uri, port = nil, &block)
|
|
75
84
|
if host_or_uri.is_a? URI::Gemini
|
|
76
85
|
host = host_or_uri.host
|
|
@@ -78,16 +87,21 @@ module Net
|
|
|
78
87
|
else
|
|
79
88
|
host = host_or_uri
|
|
80
89
|
end
|
|
81
|
-
|
|
82
|
-
return
|
|
90
|
+
client = Client.new(host, port)
|
|
91
|
+
return client unless block
|
|
83
92
|
|
|
84
|
-
yield
|
|
93
|
+
yield client
|
|
85
94
|
end
|
|
86
95
|
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
# @param uri [::URI]
|
|
97
|
+
# @yield [Response]
|
|
98
|
+
# @return [Response]
|
|
99
|
+
def self.get_response(uri, &)
|
|
100
|
+
start(uri.host, uri.port) { |client| client.fetch(uri, &) }
|
|
89
101
|
end
|
|
90
102
|
|
|
103
|
+
# @param string_or_uri [String, ::URI]
|
|
104
|
+
# @return [String]
|
|
91
105
|
def self.get(string_or_uri)
|
|
92
106
|
uri = Net::Text::Generic.build_uri string_or_uri, URI::Gemini
|
|
93
107
|
get_response(uri).body
|
|
@@ -11,6 +11,8 @@ module Net
|
|
|
11
11
|
meta_data = raw_meta.map { |m| m.split('=') }
|
|
12
12
|
mime = { lang: nil, charset: 'utf-8', format: nil }
|
|
13
13
|
new_mime = meta_data.filter_map do |opt|
|
|
14
|
+
next if opt.empty?
|
|
15
|
+
|
|
14
16
|
key = opt[0].downcase.to_sym
|
|
15
17
|
next unless mime.has_key? key
|
|
16
18
|
|
|
@@ -30,17 +32,6 @@ module Net
|
|
|
30
32
|
header.merge received_mime(raw_meta)
|
|
31
33
|
end
|
|
32
34
|
|
|
33
|
-
def parse_preformatted_block(line, buf)
|
|
34
|
-
cur_block = { meta: line[3..].chomp, content: '' }
|
|
35
|
-
while (line = buf.gets)
|
|
36
|
-
if line.start_with?('```')
|
|
37
|
-
@preformatted_blocks << cur_block
|
|
38
|
-
break
|
|
39
|
-
end
|
|
40
|
-
cur_block[:content] += line
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
35
|
def parse_link(line)
|
|
45
36
|
m = line.strip.match(/\A=>\s*([^\s]+)(?:\s*(.+))?\z/)
|
|
46
37
|
return if m.nil?
|
|
@@ -56,12 +47,16 @@ module Net
|
|
|
56
47
|
|
|
57
48
|
def parse_body
|
|
58
49
|
buf = StringIO.new(@body)
|
|
50
|
+
mono_block_open = false
|
|
59
51
|
while (line = buf.gets)
|
|
60
|
-
if line.start_with?('```')
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
mono_block_open = !mono_block_open if line.start_with?('```')
|
|
53
|
+
|
|
54
|
+
# Do not parse links inside preformatted block
|
|
55
|
+
next if mono_block_open
|
|
56
|
+
|
|
57
|
+
next unless line.start_with?('=>')
|
|
58
|
+
|
|
59
|
+
parse_link(line)
|
|
65
60
|
end
|
|
66
61
|
end
|
|
67
62
|
end
|
data/lib/net/gemini/response.rb
CHANGED
|
@@ -19,24 +19,36 @@ module Net
|
|
|
19
19
|
#
|
|
20
20
|
class Response
|
|
21
21
|
# @return [String] The Gemini response <STATUS> string.
|
|
22
|
-
# @example
|
|
22
|
+
# @example
|
|
23
|
+
# "20"
|
|
23
24
|
attr_reader :status
|
|
24
25
|
|
|
25
26
|
# @return [String] The Gemini response <META> message sent by the server.
|
|
26
|
-
# @example
|
|
27
|
+
# @example
|
|
28
|
+
# "text/gemini"
|
|
27
29
|
attr_reader :meta
|
|
28
30
|
|
|
29
|
-
# @return [Hash] The Gemini response <META>.
|
|
31
|
+
# @return [Hash{Symbol => String, nil}] The Gemini response <META>.
|
|
32
|
+
# @example
|
|
33
|
+
# { status: '20', meta: 'text/gemini; charset=UTF-8',
|
|
34
|
+
# mimetype: 'text/gemini', lang: 'en',
|
|
35
|
+
# charset: 'utf-8', format: nil }
|
|
30
36
|
attr_reader :header
|
|
31
37
|
|
|
32
38
|
# The Gemini response main content as a string.
|
|
33
39
|
attr_writer :body
|
|
34
40
|
|
|
35
41
|
# The URI related to this response as an URI object.
|
|
42
|
+
# @return [::URI]
|
|
36
43
|
attr_accessor :uri
|
|
37
44
|
|
|
38
|
-
#
|
|
39
|
-
#
|
|
45
|
+
# All links found on a Gemini response of MIME text/gemini
|
|
46
|
+
#
|
|
47
|
+
# Each link is a Hash with the keys `:uri` containing the link {::URI},
|
|
48
|
+
# and `:label` containing the link label as a {::String}, or nil if none
|
|
49
|
+
# was provided.
|
|
50
|
+
#
|
|
51
|
+
# @return [Array<Hash{:uri => ::URI; :label => String, nil}>]
|
|
40
52
|
attr_reader :links
|
|
41
53
|
|
|
42
54
|
def initialize(status = nil, meta = nil)
|
|
@@ -46,23 +58,40 @@ module Net
|
|
|
46
58
|
@uri = nil
|
|
47
59
|
@body = nil
|
|
48
60
|
@links = []
|
|
49
|
-
@
|
|
61
|
+
@socket = nil
|
|
50
62
|
end
|
|
51
63
|
|
|
64
|
+
# Whether the current {Response} has a body of interest
|
|
65
|
+
# (i.e. is not an error or a redirection).
|
|
66
|
+
# @return [Boolean]
|
|
52
67
|
def body_permitted?
|
|
53
68
|
@status && @status[0] == '2'
|
|
54
69
|
end
|
|
55
70
|
|
|
71
|
+
# Set the socket to read data through {#read_body}.
|
|
72
|
+
# @param sock [OpenSSL::SSL::SSLSocket]
|
|
73
|
+
# @return [self]
|
|
56
74
|
def reading_body(sock)
|
|
57
75
|
return self unless body_permitted?
|
|
58
76
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
@socket = sock
|
|
78
|
+
self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Read data from the SSL socket.
|
|
82
|
+
# @yield [self]
|
|
83
|
+
# @return [String, nil] The data read from the socket
|
|
84
|
+
# (aka. the Response body)
|
|
85
|
+
def read_body(&)
|
|
86
|
+
return @body unless @socket
|
|
87
|
+
|
|
88
|
+
@body = read_chunked(&)
|
|
89
|
+
return @body unless @header[:mimetype] == 'text/gemini'
|
|
63
90
|
|
|
64
91
|
parse_body
|
|
65
|
-
|
|
92
|
+
@body
|
|
93
|
+
ensure
|
|
94
|
+
@socket = nil
|
|
66
95
|
end
|
|
67
96
|
|
|
68
97
|
# Return the response body (i.e. the requested document content).
|
|
@@ -100,18 +129,27 @@ module Net
|
|
|
100
129
|
|
|
101
130
|
private
|
|
102
131
|
|
|
103
|
-
def
|
|
104
|
-
|
|
132
|
+
def read_chunked(&block)
|
|
133
|
+
raw_body = ''
|
|
134
|
+
is_text = @header[:mimetype].start_with?('text/')
|
|
135
|
+
while (chunk = @socket.read(4096))
|
|
136
|
+
chunk = fix_encoding chunk if is_text
|
|
137
|
+
yield chunk if block
|
|
138
|
+
raw_body += chunk
|
|
139
|
+
end
|
|
140
|
+
raw_body
|
|
141
|
+
end
|
|
105
142
|
|
|
143
|
+
def fix_encoding(data)
|
|
106
144
|
if @header[:charset] && @header[:charset] != 'utf-8'
|
|
107
|
-
# If
|
|
145
|
+
# If data use another charset than utf-8, we need first to
|
|
108
146
|
# declare the raw byte string as using this chasret
|
|
109
|
-
|
|
147
|
+
data.force_encoding(@header[:charset])
|
|
110
148
|
# Then we can safely try to convert it to utf-8
|
|
111
|
-
return
|
|
149
|
+
return data.encode('utf-8')
|
|
112
150
|
end
|
|
113
|
-
# Just declare that the
|
|
114
|
-
|
|
151
|
+
# Just declare that the data uses utf-8
|
|
152
|
+
data.force_encoding('utf-8')
|
|
115
153
|
end
|
|
116
154
|
end
|
|
117
155
|
end
|
data/lib/net/gemini.rb
CHANGED
|
@@ -21,8 +21,7 @@ module Net
|
|
|
21
21
|
#
|
|
22
22
|
# require 'net/gemini'
|
|
23
23
|
#
|
|
24
|
-
# This will also require 'uri' so you don't need to require it
|
|
25
|
-
# separately.
|
|
24
|
+
# This will also require 'uri' so you don't need to require it separately.
|
|
26
25
|
#
|
|
27
26
|
# The Net::Gemini methods in the following section do not persist
|
|
28
27
|
# connections.
|
|
@@ -53,18 +52,47 @@ module Net
|
|
|
53
52
|
# mimetype: 'text/gemini', lang: 'en',
|
|
54
53
|
# charset: 'utf-8', format: nil }
|
|
55
54
|
#
|
|
56
|
-
# The lang
|
|
57
|
-
# of `text/*` mimetype, and
|
|
55
|
+
# The `:lang`, `:charset` and `:format` headers will only be provided in case
|
|
56
|
+
# of `text/*` mimetype, and {Response#body body} only for 2* status codes.
|
|
58
57
|
#
|
|
59
58
|
# # Body
|
|
60
59
|
# puts res.body if res.body_permitted?
|
|
61
60
|
# puts res.body(reflow_at: 85)
|
|
62
61
|
#
|
|
62
|
+
# When the response is `text/gemini` mimetype, the body is parsed
|
|
63
|
+
# automatically to extract all its links.
|
|
64
|
+
#
|
|
65
|
+
# res.links # => [{ :uri => #<URI::Gemini gemini://…>, :label => "…" }, …]
|
|
66
|
+
#
|
|
67
|
+
# ==== Large Response
|
|
68
|
+
#
|
|
69
|
+
# You may not want to load the whole body of a large response such as images,
|
|
70
|
+
# videos, etc. into memory. To avoid that, you can read response body as
|
|
71
|
+
# chunks.
|
|
72
|
+
#
|
|
73
|
+
# f = File.new 'image.png', 'wb'
|
|
74
|
+
# Net::Gemini.get_response(URI('gemini://example.org/image.png')) do |res|
|
|
75
|
+
# res.read_body { |chunk| f.write chunk }
|
|
76
|
+
# end
|
|
77
|
+
# f.close
|
|
78
|
+
#
|
|
79
|
+
# It has to be noted that the block given to the {::get_response} method is
|
|
80
|
+
# executed while the socket is being read. This means the {Response} instance
|
|
81
|
+
# you get as block argument does not have parsed its body in case of
|
|
82
|
+
# a `text/gemini` mimetype, and thus its {Response#links} array will be
|
|
83
|
+
# empty. In any case, {::get_response} returns the {Response} instance, thus
|
|
84
|
+
# if you need access to these links, you must do this:
|
|
85
|
+
#
|
|
86
|
+
# uri = URI('gemini://example.org/very_long_text.gmi')
|
|
87
|
+
# res = Net::Gemini.get_response(uri) do |res|
|
|
88
|
+
# # … do something
|
|
89
|
+
# end
|
|
90
|
+
# res.links # => [{ :uri => #<URI::Gemini gemini://…>, :label => "…" }, …]
|
|
91
|
+
#
|
|
63
92
|
# === Following Redirection
|
|
64
93
|
#
|
|
65
|
-
# The {Client#fetch} method, contrary to the {Client#request} one will try
|
|
66
|
-
#
|
|
67
|
-
# destination.
|
|
94
|
+
# The {Client#fetch} method, contrary to the {Client#request} one will try to
|
|
95
|
+
# automatically resolves redirection, leading you to the final destination.
|
|
68
96
|
#
|
|
69
97
|
# u = URI('gemini://exemple.com/redirect')
|
|
70
98
|
# res = Net::Gemini.start(u.host, u.port) do |g|
|
|
@@ -80,6 +108,12 @@ module Net
|
|
|
80
108
|
# puts "#{res.status} - #{res.meta}" # => '20 - text/gemini;'
|
|
81
109
|
# puts res.uri.to_s # => 'gemini://exemple.com/final/dest'
|
|
82
110
|
#
|
|
111
|
+
# This fetch method is automatically called by the class methods {::get} and
|
|
112
|
+
# {::get_response}.
|
|
113
|
+
#
|
|
114
|
+
# res = Net::Gemini.get_response(URI('gemini://exemple.com/redirect'))
|
|
115
|
+
# puts "#{res.status} - #{res.meta}" # => '20 - text/gemini;'
|
|
116
|
+
# puts res.uri.to_s # => 'gemini://exemple.com/final/dest'
|
|
83
117
|
module Gemini; end
|
|
84
118
|
end
|
|
85
119
|
|
data/lib/net/text/reflow.rb
CHANGED
|
@@ -16,36 +16,43 @@ module Net
|
|
|
16
16
|
' ' * m[1].length
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def self.
|
|
20
|
-
line.strip!
|
|
21
|
-
if mono_block_open || line.start_with?('=>') || line.length < length
|
|
22
|
-
return [line]
|
|
23
|
-
end
|
|
24
|
-
|
|
19
|
+
def self.reflow_line(line, length)
|
|
25
20
|
output = []
|
|
26
21
|
prefix = reflow_line_prefix(line)
|
|
27
22
|
limit_chars = ['-', '', ' '].freeze
|
|
28
23
|
while line.length > length
|
|
29
|
-
|
|
30
|
-
cut_index = limit_chars.map {
|
|
24
|
+
# Detect first possible cut
|
|
25
|
+
cut_index = limit_chars.map { line[0...length].rindex(_1) || -1 }.max
|
|
31
26
|
break if cut_index.zero? # Better do nothing for now
|
|
32
27
|
|
|
33
28
|
output << line[0...cut_index]
|
|
34
|
-
line = prefix + line[cut_index + 1..]
|
|
29
|
+
line = prefix + line[(cut_index + 1)..]
|
|
35
30
|
end
|
|
36
31
|
output << line
|
|
37
32
|
end
|
|
38
33
|
|
|
34
|
+
def self.parse_line(line, mono_block_open, length)
|
|
35
|
+
if line.start_with?('```')
|
|
36
|
+
mono_block_open = !mono_block_open
|
|
37
|
+
return [mono_block_open, [line.chomp]]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
return [mono_block_open, [line.chomp]] if mono_block_open
|
|
41
|
+
|
|
42
|
+
line.strip!
|
|
43
|
+
if line.start_with?('=>') || line.length < length
|
|
44
|
+
return [mono_block_open, [line]]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
[mono_block_open, reflow_line(line, length)]
|
|
48
|
+
end
|
|
49
|
+
|
|
39
50
|
def self.format_body(body, length)
|
|
40
51
|
new_body = []
|
|
41
52
|
mono_block_open = false
|
|
42
53
|
body.each_line do |line|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
# Don't include code block toggle lines
|
|
46
|
-
next
|
|
47
|
-
end
|
|
48
|
-
new_body += reflow_text_line(line, mono_block_open, length)
|
|
54
|
+
mono_block_open, content = parse_line line, mono_block_open, length
|
|
55
|
+
new_body += content
|
|
49
56
|
end
|
|
50
57
|
new_body.join("\n")
|
|
51
58
|
end
|
data/lib/uri/finger.rb
CHANGED
data/lib/uri/gemini.rb
CHANGED
data/lib/uri/gopher.rb
CHANGED
data/lib/uri/nex.rb
CHANGED
metadata
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby-net-text
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
- Étienne
|
|
8
|
-
autorequire:
|
|
7
|
+
- Étienne Pflieger
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
|
-
|
|
14
|
-
email: etienne@depar.is
|
|
12
|
+
email: etienne@pflieger.bzh
|
|
15
13
|
executables: []
|
|
16
14
|
extensions: []
|
|
17
15
|
extra_rdoc_files: []
|
|
@@ -56,15 +54,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
56
54
|
requirements:
|
|
57
55
|
- - ">="
|
|
58
56
|
- !ruby/object:Gem::Version
|
|
59
|
-
version: '
|
|
57
|
+
version: '3.3'
|
|
60
58
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
59
|
requirements:
|
|
62
60
|
- - ">="
|
|
63
61
|
- !ruby/object:Gem::Version
|
|
64
62
|
version: '0'
|
|
65
63
|
requirements: []
|
|
66
|
-
rubygems_version: 3.
|
|
67
|
-
signing_key:
|
|
64
|
+
rubygems_version: 3.6.9
|
|
68
65
|
specification_version: 4
|
|
69
66
|
summary: Finger, Gemini, Gopher and Nex support for Net::* and URI::*
|
|
70
67
|
test_files: []
|