flnews_post_proc 1.40
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 +7 -0
- data/README.md +374 -0
- data/bin/flnews_post_proc +84 -0
- data/doc/html/flnews_post_proc.html +654 -0
- data/doc/html/flnews_post_proc_fr.html +701 -0
- data/doc/license.txt +13 -0
- data/doc/man/flnews_post_proc.1.gz +0 -0
- data/doc/man/flnews_post_proc_fr.1.gz +0 -0
- data/doc/pdf/flnews_post_proc.pdf +0 -0
- data/doc/pdf/flnews_post_proc_fr.pdf +0 -0
- data/doc/rst/flnews_post_proc.rst +334 -0
- data/doc/rst/flnews_post_proc_fr.rst +377 -0
- data/lib/basic_logging.rb +170 -0
- data/lib/body.rb +270 -0
- data/lib/color_output.rb +65 -0
- data/lib/configuration.rb +136 -0
- data/lib/flnews_post_proc.conf +172 -0
- data/lib/flnews_post_proc.rb +71 -0
- data/lib/headers.rb +148 -0
- data/lib/newsgroups.rb +134 -0
- data/lib/override.rb +199 -0
- data/lib/ruby_dlg +136 -0
- data/lib/version.rb +21 -0
- data/lib/whiptail_dlg +79 -0
- data/lib/yad_dlg +52 -0
- data/lib/zenity_dlg +59 -0
- metadata +88 -0
data/lib/body.rb
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
=begin
|
|
4
|
+
/***************************************************************************
|
|
5
|
+
* 2023-2024, Michael Uplawski <michael.uplawski@uplawski.eu> *
|
|
6
|
+
* This program is free software; you can redistribute it and/or modify *
|
|
7
|
+
* it under the terms of the WTFPL 2.0 or later, see *
|
|
8
|
+
* http://www.wtfpl.net/about/ *
|
|
9
|
+
* *
|
|
10
|
+
* This program is distributed in the hope that it will be useful, *
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
13
|
+
* *
|
|
14
|
+
***************************************************************************/
|
|
15
|
+
=end
|
|
16
|
+
|
|
17
|
+
require_relative 'basic_logging'
|
|
18
|
+
require_relative 'configuration'
|
|
19
|
+
|
|
20
|
+
# an object of this class represents the body of a news-article.
|
|
21
|
+
class Body
|
|
22
|
+
# a class-level configuration instance.
|
|
23
|
+
@@config = Configuration.instance
|
|
24
|
+
include BasicLogging
|
|
25
|
+
|
|
26
|
+
# reads the body text of the article
|
|
27
|
+
def initialize(article_text)
|
|
28
|
+
debug 'body intialize'
|
|
29
|
+
# for simplicity.
|
|
30
|
+
line = nil
|
|
31
|
+
# transform the article into an array.
|
|
32
|
+
line_array = article_text.split($LN)
|
|
33
|
+
# keep only from the first after an empty line ''
|
|
34
|
+
start_index = line_array.index('')
|
|
35
|
+
|
|
36
|
+
# ... to the end of the current array (all that follows '').
|
|
37
|
+
@lines = line_array.slice(start_index + 1, line_array.size)
|
|
38
|
+
debug('initialize(): body lines are ' << @lines.inspect)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# If so configured, replace an eventual followup-intro by the one
|
|
42
|
+
# configured for a group. This may depend on other conditions and
|
|
43
|
+
# must be triggered explicitly.
|
|
44
|
+
def set_intro(intro)
|
|
45
|
+
return if !intro || intro.empty? || @@config.no_intro
|
|
46
|
+
|
|
47
|
+
# name of the previous poster
|
|
48
|
+
fup_name = nil
|
|
49
|
+
# the current newsgroup
|
|
50
|
+
fup_group = nil
|
|
51
|
+
|
|
52
|
+
debug('FUP_NAME is ' << @@config.FUP_NAME)
|
|
53
|
+
debug('FUP_GROUP is ' << @@config.FUP_GROUP)
|
|
54
|
+
# The expressions which allow the identification of both
|
|
55
|
+
# in the current article.
|
|
56
|
+
fn = @@config.FUP_NAME
|
|
57
|
+
fg = @@config.FUP_GROUP
|
|
58
|
+
|
|
59
|
+
# Okay, this is called parsing, when it is well done.
|
|
60
|
+
# I just try and am happy when it works.
|
|
61
|
+
@lines.each_with_index do |line, i|
|
|
62
|
+
# find the name in the intro-line
|
|
63
|
+
if !fn.strip.empty? && !line.strip.empty? && !fup_name
|
|
64
|
+
# match a name
|
|
65
|
+
fup_name = line.match(Regexp.new(fn) ) do |md|
|
|
66
|
+
# debug("\tmatch: " << md.to_s)
|
|
67
|
+
md.length == 2 ? md[1] : md[0]
|
|
68
|
+
end
|
|
69
|
+
debug("\tfup_name: " << fup_name.to_s)
|
|
70
|
+
|
|
71
|
+
if !fg.strip.empty? && !fup_group
|
|
72
|
+
# match a group
|
|
73
|
+
fup_group = line.match(Regexp.new(fg) ) do |md|
|
|
74
|
+
debug("\tmatch: " << md.to_s)
|
|
75
|
+
md.length == 2 ? md[1] : nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
debug "group is " << fup_group.to_s
|
|
79
|
+
|
|
80
|
+
# All that follows depends on the presence of a name
|
|
81
|
+
# in the intro-string.
|
|
82
|
+
if fup_name && !fup_name.strip.empty?
|
|
83
|
+
# keep the current intro for later
|
|
84
|
+
ointro = line
|
|
85
|
+
line = ''
|
|
86
|
+
while line.strip.empty?
|
|
87
|
+
i = i.next
|
|
88
|
+
line = @lines[i]
|
|
89
|
+
end
|
|
90
|
+
# check if there is a citation, at all
|
|
91
|
+
if(line.start_with?('>'))
|
|
92
|
+
debug("\tfound intro " << ointro)
|
|
93
|
+
# variables are part of the $intro.
|
|
94
|
+
# Do substitutions.
|
|
95
|
+
intro.sub!('%fup_name%', fup_name) if fup_name
|
|
96
|
+
intro.sub!('%fup_group%', fup_group) if fup_group
|
|
97
|
+
debug("\tsetting intro " << intro.to_s)
|
|
98
|
+
|
|
99
|
+
# exchange original intro-line against the new one
|
|
100
|
+
@lines[@lines.index(ointro)] = intro.strip
|
|
101
|
+
# looked complicated because it is.
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end # fn.strip.empty?
|
|
105
|
+
end # lines.each_with_index
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def set_signature(signature)
|
|
109
|
+
# unless no changes requested.
|
|
110
|
+
if signature && !signature.empty?
|
|
111
|
+
# remove any signature(s) from
|
|
112
|
+
# the current article
|
|
113
|
+
sigpos = @lines.index('-- ')
|
|
114
|
+
debug('found signature at position ' << sigpos) if sigpos
|
|
115
|
+
@lines = @lines.slice(0, sigpos ) if sigpos
|
|
116
|
+
debug('setting signature ' << signature) if signature
|
|
117
|
+
@lines << "-- " << signature if signature
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def join
|
|
122
|
+
return @lines.join("\r\n")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Verify and possibly correct links in the post.
|
|
126
|
+
# Simple.
|
|
127
|
+
def handle_urls()
|
|
128
|
+
# Determine here or elsewhere if URLs shall be verified.
|
|
129
|
+
# Default is != no. nil or '' do qualify as default.
|
|
130
|
+
if @@config.VFY_URLS
|
|
131
|
+
@lines.each_with_index do | l, i |
|
|
132
|
+
# leave cited lines as they are.
|
|
133
|
+
if !l.start_with?( '>')
|
|
134
|
+
# IMPORTANT --------------------------------->
|
|
135
|
+
# IT IS HENCEFORTH PROHIBITED TO WRITE AN EMAIL-ADDRESS
|
|
136
|
+
# IN THE BODY OF A NEWS-POST AND TO NOT PREPEND IT WITH
|
|
137
|
+
# mailto:
|
|
138
|
+
# ... Because I do not know what to do in these cases,
|
|
139
|
+
# and I am the boss!
|
|
140
|
+
# <----------------------------
|
|
141
|
+
|
|
142
|
+
# BUGFIX : Urls with @
|
|
143
|
+
new_line = handle_news(l)
|
|
144
|
+
# http(s)
|
|
145
|
+
if l.include?('http')
|
|
146
|
+
new_line = handle_http(l)
|
|
147
|
+
end #if http
|
|
148
|
+
@lines[i] = new_line if new_line
|
|
149
|
+
end # if '>'
|
|
150
|
+
end # @lines.each_with_index
|
|
151
|
+
end # if verify
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# extract URL or other stuff, if configured for footnotes,
|
|
155
|
+
def handle_references()
|
|
156
|
+
# a symbol or string to mark the beginning an ending of a future footnote.
|
|
157
|
+
ref_delim = @@config.REFERENCES_DELIMITER
|
|
158
|
+
debug('references delimiter is ' << ref_delim)
|
|
159
|
+
references = Array.new
|
|
160
|
+
body = @lines.join($LN)
|
|
161
|
+
if ref_delim && !ref_delim.strip.empty?
|
|
162
|
+
unless ref_delim == ref_delim.reverse
|
|
163
|
+
ref_delim.strip!
|
|
164
|
+
ref_rx = Regexp.new(ref_delim.dup << ".*?" << ref_delim.reverse, Regexp::MULTILINE)
|
|
165
|
+
debug('ref_rx is ' << ref_rx.to_s)
|
|
166
|
+
index = 0
|
|
167
|
+
# I cannot work with an array, here, and apply the pattern
|
|
168
|
+
# to the whole body, over multiple lines, if need be.
|
|
169
|
+
begin
|
|
170
|
+
ref = body.match(ref_rx )
|
|
171
|
+
debug("found reference " << ref.to_s << " (length: " << (ref ? ref.to_s.size.to_s : '0') << ")")
|
|
172
|
+
if ref
|
|
173
|
+
debug('ref is ' << ref.to_s)
|
|
174
|
+
# ... This is some presentation thing and I think
|
|
175
|
+
# it works, too.
|
|
176
|
+
r = ref[0].gsub(/[ \t]+/, ' ').strip
|
|
177
|
+
r.gsub!("\n", "\n ")
|
|
178
|
+
references << r
|
|
179
|
+
index += 1
|
|
180
|
+
body.gsub!(ref[0], format(@@config.REFERENCE_FORMAT, index.to_s ))
|
|
181
|
+
end
|
|
182
|
+
end until ref == nil
|
|
183
|
+
debug("all references found:\n" << references.join('\n'))
|
|
184
|
+
else
|
|
185
|
+
msg = 'The References Delimiter is the same in its reversed form.'
|
|
186
|
+
msg << "#{$LN}Cannot handle references or footnotes!"
|
|
187
|
+
error(msg)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
if(references && !references.empty?)
|
|
191
|
+
# a line, separating the footnotes from the body of the article
|
|
192
|
+
body << $LN << @@config.REFERENCES_SEPARATOR << $LN
|
|
193
|
+
references.each_with_index do |r, i|
|
|
194
|
+
r = r.gsub(ref_delim, '').gsub(ref_delim.reverse,'')
|
|
195
|
+
body << (i + 1 ).to_s << ") " << r.strip << $LN
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
@lines = body.split($LN)
|
|
200
|
+
end
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
# handle <news:..@...>
|
|
204
|
+
def handle_news(l)
|
|
205
|
+
# ... “Do not trust nobody!”
|
|
206
|
+
if l.include?('@') && ! l.include?('http:')
|
|
207
|
+
# And I forgot how this works. It does.
|
|
208
|
+
l.split.collect do |ele|
|
|
209
|
+
# angular brackets are there alright. Hurra.
|
|
210
|
+
url = ele.match(/\<(.*)\>/)
|
|
211
|
+
unless !url
|
|
212
|
+
debug(' with angular brackets: ' << url.inspect)
|
|
213
|
+
l = l.sub(url[1], 'news:' << url[1]) if !url[1].include?('mailto')
|
|
214
|
+
else
|
|
215
|
+
url = ele.match(/\S+@(\.|[^\p{P}]+)\S+/)
|
|
216
|
+
if url
|
|
217
|
+
debug(' withOUT angular brackets: ' << url.inspect)
|
|
218
|
+
l = l.sub(url[0], '<news:' << url[0] << '>') if !url[0].include?('mailto')
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end # url_strs = collect
|
|
222
|
+
debug(' news: line is now ' << l)
|
|
223
|
+
return l
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def handle_http(l)
|
|
228
|
+
debug('handle http')
|
|
229
|
+
l_array = l.split
|
|
230
|
+
url_strs = l_array.collect{|ele| ele.strip.include?('http') ? ele.strip : nil}.compact
|
|
231
|
+
debug('url_strs: ' << url_strs.to_s)
|
|
232
|
+
url_strs.each do |str|
|
|
233
|
+
# keep str for final replacement
|
|
234
|
+
nstr = str.dup
|
|
235
|
+
debug( 'try to match http-link')
|
|
236
|
+
begin
|
|
237
|
+
# missing slashes ?
|
|
238
|
+
match = str.match(/(https?):(\/{2})/)
|
|
239
|
+
if (!match || match.length != 3 || match[2] != '//')
|
|
240
|
+
debug 'cannot find slashes in the URL'
|
|
241
|
+
if str.match(/https:/)
|
|
242
|
+
nstr = str.sub(/https:/, "https://")
|
|
243
|
+
elsif str.match(/http:/)
|
|
244
|
+
nstr = str.sub(/http:/, "http://")
|
|
245
|
+
end
|
|
246
|
+
debug ' with slashes: ' << nstr
|
|
247
|
+
end
|
|
248
|
+
# missing angular brackets
|
|
249
|
+
nstr.sub!(/http/, "<http") if !nstr.include?( "<http")
|
|
250
|
+
nstr << '>' if !nstr.end_with? '>'
|
|
251
|
+
debug ' matching nstr ' << nstr
|
|
252
|
+
match = nstr.match(/\<?(https?:\/\/[^\>]*)\>?/)
|
|
253
|
+
# get the darned URL already!
|
|
254
|
+
url = match[1] if match
|
|
255
|
+
# nstr = with_angular_brackets(nstr, url)
|
|
256
|
+
return l.sub(str, nstr)
|
|
257
|
+
rescue Exception => ex
|
|
258
|
+
# this had looked intelligent, once.
|
|
259
|
+
line = __LINE__
|
|
260
|
+
msg = "Line #{line}: Cannot match the url " << str << ' against the Regexp (' << ex.message << ')'
|
|
261
|
+
msg << "\nAborting. Bye"
|
|
262
|
+
fatal msg
|
|
263
|
+
STDERR.puts msg
|
|
264
|
+
exit false
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
# Ω
|
|
270
|
+
|
data/lib/color_output.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#encoding: UTF-8
|
|
2
|
+
=begin
|
|
3
|
+
/***************************************************************************
|
|
4
|
+
* 2023-2024, Michael Uplawski <michael.uplawski@uplawski.eu> *
|
|
5
|
+
* This program is free software; you can redistribute it and/or modify *
|
|
6
|
+
* it under the terms of the WTFPL 2.0 or later, see *
|
|
7
|
+
* http://www.wtfpl.net/about/ *
|
|
8
|
+
* *
|
|
9
|
+
* This program is distributed in the hope that it will be useful, *
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
12
|
+
* *
|
|
13
|
+
***************************************************************************/
|
|
14
|
+
=end
|
|
15
|
+
|
|
16
|
+
# Functions to apply colors to terminal output.
|
|
17
|
+
# This is stolen from the Internet and I have lost track of my own additions
|
|
18
|
+
# and modifications.
|
|
19
|
+
|
|
20
|
+
COLORS = {:default => 9, :black => 0, :red => 1, :green => 2, :yellow => 3, :blue => 4, :purple => 5, :cyan => 6, :white => 7 }
|
|
21
|
+
|
|
22
|
+
BG = 4
|
|
23
|
+
FG = 3
|
|
24
|
+
REGULAR = 0
|
|
25
|
+
BOLD = 1
|
|
26
|
+
UNDERLINE = 4
|
|
27
|
+
BLINK = 5
|
|
28
|
+
SWAP = 7
|
|
29
|
+
NEUTRAL = 0
|
|
30
|
+
|
|
31
|
+
STYLES = {:regular => REGULAR, :bold => BOLD, :underline => UNDERLINE, :blink => BLINK, :swap => SWAP, :neutral => NEUTRAL}
|
|
32
|
+
|
|
33
|
+
# Colorizes the given text. Color-code is either an escape-sequence or one of
|
|
34
|
+
# the symbols representing color-names in the COLORS hash.
|
|
35
|
+
def colorize(text, color_code)
|
|
36
|
+
if (COLORS.keys.include?(color_code) )
|
|
37
|
+
"\033[3#{COLORS[color_code]}m#{text}\033[0m"
|
|
38
|
+
else
|
|
39
|
+
"#{color_code}#{text}\033[0m"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def style(text, style_code)
|
|
44
|
+
"#{style_code}#{text}\033[0m"
|
|
45
|
+
end
|
|
46
|
+
# a function which allows to manipulate every known aspect of the ansi-output.
|
|
47
|
+
def colored_output(output_text, fg_color = :default, bg_color = :default, style = :regular , mode = :neutral )
|
|
48
|
+
"\033[%i;%i;%i%i;%i%im%s\033[0m" %[STYLES[mode.to_sym], STYLES[style.to_sym], FG, COLORS[fg_color.to_sym], BG, COLORS[bg_color.to_sym], output_text]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# convenience functions
|
|
52
|
+
def red(text); colorize(text, "\033[31m"); end
|
|
53
|
+
def green(text); colorize(text, "\033[32m"); end
|
|
54
|
+
def yellow(text); colorize(text, "\033[33m"); end
|
|
55
|
+
def purple(text); colorize(text, "\033[35m"); end
|
|
56
|
+
def cyan(text); colorize(text, "\033[36m"); end
|
|
57
|
+
def blue(text); colorize(text, "\033[34m"); end
|
|
58
|
+
def white(text); colorize(text, "\033[37m"); end
|
|
59
|
+
|
|
60
|
+
def black_on_white(text); colorize(colorize(text, "\033[30m"), "\033[47m");end
|
|
61
|
+
def white_on_black(text); colorize(colorize(text, "\033[37m"), "\033[40m");end
|
|
62
|
+
|
|
63
|
+
def bold(text); style(text, "\033[01m");end
|
|
64
|
+
def underline(text); style(text, "\033[04m");end
|
|
65
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#encoding: UTF-8
|
|
2
|
+
=begin
|
|
3
|
+
/***************************************************************************
|
|
4
|
+
* 2023-2024, Michael Uplawski <michael.uplawski@uplawski.eu> *
|
|
5
|
+
* This program is free software; you can redistribute it and/or modify *
|
|
6
|
+
* it under the terms of the WTFPL 2.0 or later, see *
|
|
7
|
+
* http://www.wtfpl.net/about/ *
|
|
8
|
+
* *
|
|
9
|
+
* This program is distributed in the hope that it will be useful, *
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
12
|
+
* *
|
|
13
|
+
***************************************************************************/
|
|
14
|
+
=end
|
|
15
|
+
|
|
16
|
+
require_relative 'basic_logging'
|
|
17
|
+
require_relative 'version'
|
|
18
|
+
require 'yaml'
|
|
19
|
+
require 'singleton'
|
|
20
|
+
|
|
21
|
+
# An object of this class represents the configuration of the program.
|
|
22
|
+
# The parameters are set in the configuration-file
|
|
23
|
+
|
|
24
|
+
class Configuration
|
|
25
|
+
include Singleton
|
|
26
|
+
include BasicLogging
|
|
27
|
+
|
|
28
|
+
self.extend(BasicLogging)
|
|
29
|
+
|
|
30
|
+
def initialize()
|
|
31
|
+
debug 'Configuration::initialize()'
|
|
32
|
+
confname = PROGNAME << '.conf'
|
|
33
|
+
# try to open user-configuration
|
|
34
|
+
@config_file = ENV['HOME'].dup << File::Separator << '.' << confname
|
|
35
|
+
# if user-configuration does not exist, copy installed version.
|
|
36
|
+
installed_config = File::dirname(File::absolute_path(__FILE__)) << File::Separator << confname
|
|
37
|
+
debug 'installed configuration is in ' << installed_config
|
|
38
|
+
if !File.exist?(@config_file)
|
|
39
|
+
begin
|
|
40
|
+
File.open(@config_file, 'w') do |cf|
|
|
41
|
+
cf.write(File.read(installed_config ) )
|
|
42
|
+
end
|
|
43
|
+
rescue => ex
|
|
44
|
+
STDERR.puts('Cannot write user-configuration to ' << @config_file << '! (' << ex.message << ')' )
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
# read the config-file.
|
|
48
|
+
read_config
|
|
49
|
+
# updates the user-configuration *ONLY*
|
|
50
|
+
# if necessary
|
|
51
|
+
update_config(installed_config)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# What this object does not have, may still be in the configuration.
|
|
55
|
+
def method_missing(method, args = nil)
|
|
56
|
+
if @conf
|
|
57
|
+
v = @conf[method]
|
|
58
|
+
debug('method_missing returns value for ' << method.to_s << ': |' << v.to_s << '|')
|
|
59
|
+
return v
|
|
60
|
+
else
|
|
61
|
+
error("user-version of the configuration (#{@conf}) is not accessible")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# set a configuration option.
|
|
66
|
+
def set(key, value)
|
|
67
|
+
if @conf
|
|
68
|
+
@conf[key] = value
|
|
69
|
+
if key == 'DEBUG_LOG'
|
|
70
|
+
set_target value
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
attr_reader :log_target, :log_level
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# updates the configuration file, if need be.
|
|
79
|
+
def update_config(i_config)
|
|
80
|
+
if @conf
|
|
81
|
+
conf_version = @conf[:CONFIG_VERSION]
|
|
82
|
+
if !conf_version || conf_version < PROGVERSION.to_f
|
|
83
|
+
info "configuration has an older version number, looking for changes"
|
|
84
|
+
i_conf = YAML::load_file(i_config)
|
|
85
|
+
i_conf.transform_keys!{|k| k.to_sym}
|
|
86
|
+
i_keys = i_conf.keys
|
|
87
|
+
i_keys.each do |k|
|
|
88
|
+
if !@conf[k]
|
|
89
|
+
info('new configuration option: ' << k.to_s)
|
|
90
|
+
@conf[k] = ''
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
@conf[:CONFIG_VERSION] = PROGVERSION.to_f
|
|
94
|
+
bak_conf = @config_file.dup << '_' << conf_version.to_s
|
|
95
|
+
info('Old configuration is saved to ' << bak_conf )
|
|
96
|
+
info('New configuration is saved to ' << @config_file )
|
|
97
|
+
begin
|
|
98
|
+
File::write(bak_conf, File.read(@config_file))
|
|
99
|
+
File::write(@config_file, @conf.to_yaml)
|
|
100
|
+
rescue Exception => ex
|
|
101
|
+
msg = ex.message
|
|
102
|
+
error "Cannot write altered configuration to " << @config_file << "!"
|
|
103
|
+
error msg
|
|
104
|
+
exit false
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
else
|
|
108
|
+
error "User-version of the configuration (#{@conf}) inaccessibble."
|
|
109
|
+
exit false
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# reads the configuration and sets some properties.
|
|
114
|
+
def read_config
|
|
115
|
+
if File::exist?(@config_file) && File::readable?(@config_file)
|
|
116
|
+
begin
|
|
117
|
+
@conf = YAML::load_file(@config_file)
|
|
118
|
+
@conf.transform_keys!{|k| k.to_sym}
|
|
119
|
+
@keys = @conf.keys
|
|
120
|
+
clevel = @conf[:LOG_LEVEL]
|
|
121
|
+
set_level clevel
|
|
122
|
+
set_target @conf[:DEBUG_LOG]
|
|
123
|
+
debug('log target and -level set: ' << @@log_level.to_s << ', ' << @@target.path)
|
|
124
|
+
rescue Exception => ex
|
|
125
|
+
msg = ex.message
|
|
126
|
+
error msg
|
|
127
|
+
exit false
|
|
128
|
+
end
|
|
129
|
+
else
|
|
130
|
+
STDERR.puts("cannot read the configuration-file (" << @config_file << ")")
|
|
131
|
+
exit false
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
# EOF
|
|
136
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#/***************************************************************************
|
|
2
|
+
# * 2023-2024, Michael Uplawski <michael.uplawski@uplawski.eu> *
|
|
3
|
+
# * *
|
|
4
|
+
# * This program is free software; you can redistribute it and/or modify *
|
|
5
|
+
# * it under the terms of the DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE *
|
|
6
|
+
# * *
|
|
7
|
+
# * This program is distributed in the hope that it will be useful, *
|
|
8
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
9
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
10
|
+
# * *
|
|
11
|
+
# ***************************************************************************/
|
|
12
|
+
# This is a YAML file. Keep intact these three dashes.
|
|
13
|
+
---
|
|
14
|
+
# CONFIG_VERSION – DO NOT MODIFY
|
|
15
|
+
# If this version is smaller than that of the current program, this file
|
|
16
|
+
# may be updated automatically. Your settings should not be altered in
|
|
17
|
+
# the process, if they are still valid.
|
|
18
|
+
# ATTN! COMMENTS WILL BE REMOVED but a copy of your previous configuration
|
|
19
|
+
# will be saved in a file with a version suffix.
|
|
20
|
+
CONFIG_VERSION: 1.35
|
|
21
|
+
|
|
22
|
+
# FUP_NAME
|
|
23
|
+
# A Regular Expression, describing the string which contains the name of
|
|
24
|
+
# previous poster who is the author of a quoted post. This string is
|
|
25
|
+
# recognized in the original article and may be used with the fitting element
|
|
26
|
+
# from GROUP_INTRO, below. The Regexp-format is that of the Regexp class in
|
|
27
|
+
# Ruby, noted as a String. Beware to mask a backslash '\' by another one,
|
|
28
|
+
# like in the example. A capture-group '()' serves to extract the name from the
|
|
29
|
+
# match result.
|
|
30
|
+
# Leave this field empty to keep the default from the FLNews configuration
|
|
31
|
+
# intact.
|
|
32
|
+
# CONTENT: A String equivalent of a regular expression.
|
|
33
|
+
# DEFAULT: EMPTY
|
|
34
|
+
# EXAMPLE1: "Am \\d+.\\d+.\\d{2,4} um \\d+:\\d+ schrieb (.*):"
|
|
35
|
+
# EXAMPLE2: "(.*) wrote:"
|
|
36
|
+
FUP_NAME: "(.*) wrote in"
|
|
37
|
+
|
|
38
|
+
#FUP_GROUP
|
|
39
|
+
# A Regular Expression, describing the string which contains the newsgroup
|
|
40
|
+
# where the previous post, that you are referring to in the followup, had been
|
|
41
|
+
# published.
|
|
42
|
+
# Leave this field empty to ignore the precise group.
|
|
43
|
+
# CONTENT: A String equivalent of a regular expression.
|
|
44
|
+
# DEFAULT: EMPTY
|
|
45
|
+
# EXAMPLE: "wrote in (.*):"
|
|
46
|
+
FUP_GROUP: 'wrote in (.*)'
|
|
47
|
+
|
|
48
|
+
# GROUP_INTROS:
|
|
49
|
+
# Introductory strings, referring to the previous poster who is the author of a
|
|
50
|
+
# quoted post. If you match the newsgroup of the post (see FUP_GROUP), you can
|
|
51
|
+
# use these variables in the result.
|
|
52
|
+
# Currently only %fup_name% and %fup_group% are reproduced in the resulting
|
|
53
|
+
# introductory string.
|
|
54
|
+
# CONTENT: A newsgroup or regexp, followed by a colon, a space and a String.
|
|
55
|
+
# DEFAULT: As configured in FLNews
|
|
56
|
+
# EXAMPLE: alt.test: "Thus spoke #{fup_name} on that baleful #{fup_date}:"
|
|
57
|
+
GROUP_INTROS:
|
|
58
|
+
.*fr.test: "(%fup_name%) a écris :"
|
|
59
|
+
.*de.test: "%fup_name% hat in %fup_group% getestet:"
|
|
60
|
+
de\.*: "%fup_name% hat geschrieben:"
|
|
61
|
+
uk\.*: "%fup_name% wrote:"
|
|
62
|
+
fr\.*: "%fup_name% a écrit:"
|
|
63
|
+
|
|
64
|
+
# GROUP_SIGS
|
|
65
|
+
# A signature line per Newsgroup.
|
|
66
|
+
# ATTN! In multi line signatures, use \r\n for line breaks!
|
|
67
|
+
# CONTENT: A newsgroup or regexp, followed by a colon, a space and a String.
|
|
68
|
+
# DEFAULT: As configured in flnews
|
|
69
|
+
# EXAMPLE: alt.test: "Signature for alt.test\r\nsecond line"
|
|
70
|
+
GROUP_SIGS:
|
|
71
|
+
.*de.test: "newsgroup_hook .*de.test\r\nMit Zeilenumbruch"
|
|
72
|
+
.*fr.test: "newsgroup_hook .*fr.test\r\n2ème ligne, « guillemets »"
|
|
73
|
+
de.*: Geh Kaffee kochen
|
|
74
|
+
fr.*: "« Ton ordinateur de rêve ? » – « Le bleue. »\r\n "
|
|
75
|
+
|
|
76
|
+
# CUSTOM_HEADERS
|
|
77
|
+
# Additional headers for the outgoing article.
|
|
78
|
+
# If you wish to insert an "X-Post-Processor" header, the current version
|
|
79
|
+
# of the program will be appended.
|
|
80
|
+
# CONTENT: A dash and space, then a String, comprising the name of the header, ending in a
|
|
81
|
+
# colon and the value of the header
|
|
82
|
+
# DEFAULT: undefined
|
|
83
|
+
# EXAMPLE (two headers):
|
|
84
|
+
# - 'X-My-Header: nothing fancy'
|
|
85
|
+
# - 'X-Post-Processor: flnews_post_proc'
|
|
86
|
+
CUSTOM_HEADERS:
|
|
87
|
+
- "X-Post-Processor: flnews_post_proc"
|
|
88
|
+
|
|
89
|
+
# XNAY_GROUPS:
|
|
90
|
+
# The newsgroups, where a header X-No-Archive: YES shall be set.
|
|
91
|
+
# CONTENT: a dash and space, then a String, containing the name of the group
|
|
92
|
+
# or a regexp.
|
|
93
|
+
# DEFAULT: empty
|
|
94
|
+
# EXAMPLE: - "alt.test"
|
|
95
|
+
XNAY_GROUPS:
|
|
96
|
+
- ".*.test"
|
|
97
|
+
|
|
98
|
+
# DEBUG_LOG:
|
|
99
|
+
# The name of a file, where debug messages are written. Setting this
|
|
100
|
+
# variable will enable the log. Leave empty to disable logging.
|
|
101
|
+
# CONTENT: The name of a writable file, which will be overwritten.
|
|
102
|
+
# DEFAULT: empty
|
|
103
|
+
# EXAMPLE: '/tmp/a_log-file.txt'
|
|
104
|
+
DEBUG_LOG: '/tmp/flnews_post_proc.log'
|
|
105
|
+
|
|
106
|
+
# LOG LEVEL
|
|
107
|
+
# One of debug, fatal, error, info, warn
|
|
108
|
+
LOG_LEVEL: 'info'
|
|
109
|
+
|
|
110
|
+
# REFERENCES_SEPARATOR
|
|
111
|
+
# A symbol or sequence of symbols marking the end of the message-body and the
|
|
112
|
+
# beginning of a list of “references” or “footnotes”. It will only appear, if
|
|
113
|
+
# the original message-body contains text marked for use as such a footnote. See
|
|
114
|
+
# REFERENCES_DELIMITER.
|
|
115
|
+
# If the option is not defined or empty, the list of footnotes will appear
|
|
116
|
+
# below the last line of the message body and no separator will be inserted.
|
|
117
|
+
# CONTENT: A quoted symbol or sequence of symbols.
|
|
118
|
+
# DEFAULT: empty
|
|
119
|
+
# EXAMPLE: '---------'
|
|
120
|
+
# REFERENCES_SEPARATOR: "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁"
|
|
121
|
+
REFERENCES_SEPARATOR: "──────────────────"
|
|
122
|
+
|
|
123
|
+
# REFERENCE_DELIMITER
|
|
124
|
+
# A symbol or sequence of symbols marking the beginning of a text which will
|
|
125
|
+
# serve as footnote (or reference). The reversed sequence musst be used to mark
|
|
126
|
+
# the end of the text. The presence of this sequence or symbol in the origimal
|
|
127
|
+
# message body will cause the enclosed text to be moved below the message body.
|
|
128
|
+
# The REFERENCES_SEPARATOR, if defined, will separate the message from the list
|
|
129
|
+
# of footnotes.
|
|
130
|
+
# If this option is not defined or empty, footnotes are not created.
|
|
131
|
+
# CONTENT a quoted symbol or sequence of symbols.
|
|
132
|
+
# DEFAULT: none/empty
|
|
133
|
+
# EXAMPLE: '%?'
|
|
134
|
+
REFERENCES_DELIMITER: "%="
|
|
135
|
+
|
|
136
|
+
# REFERENCE_FORMAT
|
|
137
|
+
# A format-string, using %s for a number, replacing the reference-
|
|
138
|
+
# text in the message body.
|
|
139
|
+
# DEFAULT: " %s]" -> becomes 1) ... 2) ... 3)
|
|
140
|
+
# EXAMPLE: "(%s)" -> becomes (1) ... (2) ... (3)
|
|
141
|
+
REFERENCE_FORMAT: " ➤%s"
|
|
142
|
+
|
|
143
|
+
# VFY_URLS
|
|
144
|
+
# A Boolean constant. It determines if the program shall verify and possibly
|
|
145
|
+
# try to correct URLs. Even if URLs are identified as such, only a few
|
|
146
|
+
# manipulations are attempted :
|
|
147
|
+
# * Angular brackets '<' and '>' are added, if missing
|
|
148
|
+
# * Article-references are prepended with "news:", if missing
|
|
149
|
+
# * Slashes are added, if they are found missing after "http(s):"
|
|
150
|
+
# ATTN! The program is unable to discern "mailto:" and "news:" references. If
|
|
151
|
+
# neither is given, but '@' is present, "news:" is automatically prepended.
|
|
152
|
+
#
|
|
153
|
+
# If the variable is not set, a value 'yes' is assumed.
|
|
154
|
+
# CONTENT: One of YES, yes, NO, no, and other variations of case.
|
|
155
|
+
# DEFAULT: yes
|
|
156
|
+
# Example: ... I let you guess.
|
|
157
|
+
VFY_URLS: YES
|
|
158
|
+
|
|
159
|
+
# OVERRIDE_CONFIG
|
|
160
|
+
# A Boolean constant. You can choose to override the following
|
|
161
|
+
# configuration options before an article is posted:
|
|
162
|
+
# GROUP_SIGS, XNAY_GROUPS, CUSTOM_HEADERS and DEBUG_LOG.
|
|
163
|
+
# A dialog may be displayed which allows you to disable any of these
|
|
164
|
+
# four options, so that the defaults from flnews prevail.
|
|
165
|
+
# A fifth option (PP) can be used to disable post-processing completely.
|
|
166
|
+
#
|
|
167
|
+
# Set this option to no, NO or similar to disable the dialog.
|
|
168
|
+
# DEFAULT: yes
|
|
169
|
+
OVERRIDE_CONFIG: YES
|
|
170
|
+
|
|
171
|
+
# EOF
|
|
172
|
+
|