flnews_post_proc 1.94 → 1.97

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.
@@ -0,0 +1,202 @@
1
+ #!/bin/env ruby
2
+ #encoding: UTF-8
3
+ =begin
4
+ /***************************************************************************
5
+ * 2023-2025, 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
+ #
18
+ # Simplified logging.
19
+ # See example code at the bottom of this file.
20
+ # Execute this file to see the output.
21
+ module BasicLogging
22
+
23
+ DEBUG = 0
24
+ INFO = 1
25
+ WARN = 2
26
+ ERROR = 3
27
+ FATAL = 4
28
+ UNKNOWN = nil
29
+
30
+ # this is mainly for the translation of method calls into log levels
31
+ Levels = {:debug => DEBUG, :info => INFO, :warn => WARN, :error => ERROR,
32
+ :fatal => FATAL, :unknown => UNKNOWN}
33
+
34
+ @@log_level = UNKNOWN
35
+ @@target = STDOUT
36
+ @@muted = []
37
+
38
+ # do not log, if caller is obj (class or instance)
39
+ def self.mute(obj)
40
+ name = obj.class == Class ? obj.name.dup : obj.class.name
41
+ @@muted << name
42
+ end
43
+
44
+ # return true if obj is muted and will not log.
45
+ def self.is_muted?(obj)
46
+ name = obj.class == Class ? obj.name.dup : obj.class.name
47
+ @@muted.include?(name)
48
+ end
49
+
50
+ # set the log level
51
+ def set_level(lv)
52
+ if lv.respond_to?(:to_str) && Levels.keys.include?(lv.strip.to_sym)
53
+ lv = Levels[lv.to_sym]
54
+ elsif lv.respond_to?(:to_sym) && Levels.keys.include?(lv)
55
+ lv = Levels[lv]
56
+ end
57
+
58
+ if(!lv || (lv.respond_to?(:to_int) && lv >= DEBUG && lv <= FATAL) )
59
+ @@log_level = lv
60
+ else
61
+ msg = __FILE__.dup << ": ERROR : invalid log level \"" << lv.to_s << "\""
62
+ msg << "\n" << "Keepinng old log level " << Levels.keys.detect {| k| Levels[k] == @@log_level}.to_s
63
+ STDERR.puts msg
64
+ puts msg
65
+ end
66
+ end
67
+
68
+ # set the log target
69
+ def set_target(tg)
70
+ if tg.respond_to?(:to_io)
71
+ @@target = tg
72
+ elsif(!File::exist?(tg) || ( File.file?(tg) && File.writable?(tg) ) )
73
+ @@target = File.open(tg, 'w+')
74
+ elsif !tg || tg.respond_to?(:to_str) && tg.strip.empty?
75
+ @@target = nil
76
+ else
77
+ STDERR.puts __FILE__.dup << ': ERROR : target ' << tg << ' cannot be set'
78
+ STDERR.puts "Keeping old target " << @@target.inspect
79
+ return
80
+ end
81
+ end
82
+
83
+ # Output log messages, depending on the log level set for the calling class
84
+ # and the name of the alias method which is actually called.
85
+ def log(message)
86
+ if !BasicLogging.is_muted?(self)
87
+ # how has this method been called?
88
+ mlevel = __callee__
89
+ if Levels.has_key?(mlevel) && Levels[mlevel] <= FATAL
90
+ # output only for levels equal or above the value that corresponds to
91
+ # the calling alias.
92
+ format_log( message, mlevel) if @@log_level && Levels[mlevel] >= @@log_level
93
+ else
94
+ STDERR.puts __FILE__.dup << ": ERROR : invalid log level \"" << mlevel.to_s << "\""
95
+ end
96
+ end
97
+ end
98
+
99
+ def target
100
+ @@target.path if @@target
101
+ end
102
+
103
+ def level
104
+ @@level.to_s if @@level
105
+ end
106
+
107
+ # Clear the log (-file)
108
+ def clear_log
109
+ if @@target && @@target.respond_to?(:truncate)
110
+ lock_target{ @@target.truncate(0) }
111
+ end
112
+ end
113
+
114
+ alias :debug :log
115
+ alias :info :log
116
+ alias :warn :log
117
+ alias :error :log
118
+ alias :fatal :log
119
+
120
+ private
121
+
122
+ def lock_target(&block)
123
+ begin
124
+ if @@target.respond_to?(:flock)
125
+ @@target.flock(File::LOCK_EX)
126
+ block.call
127
+ @@target.flock(File::LOCK_UN)
128
+ elsif @@target.respond_to?(:to_io)
129
+ block.call
130
+ end
131
+ rescue => ex
132
+ STDERR.puts __FILE__.dup << ": ERROR : cannot lock target (" << ex.message << ")"
133
+ end
134
+ end
135
+
136
+ # 1 format_log for all loggers.
137
+ def format_log(message, mlevel)
138
+ if @@target
139
+ # indicate if a registered class or the registered object of a class is calling.
140
+ name = self.class == Class ? self.name.dup << ' [class]' : (self.class.name == 'Object' ? 'Top Level' : self.class.name)
141
+ lock_target{@@target.puts '' << name << ' ' << mlevel.to_s << ' ' << Time.now.strftime("%H:%M:%S:%6N") << ': ' << message.gsub("\n", "\n |")}
142
+ end
143
+ end
144
+ end
145
+ #---------test: execute file----------
146
+ if $0 == __FILE__
147
+ Array.extend(BasicLogging)
148
+ Array.set_level(BasicLogging::INFO)
149
+ Array.info('TEST')
150
+ ar = Array.new
151
+ ar.extend(BasicLogging)
152
+ # --- no output :
153
+ l = __LINE__
154
+ ar.debug(l.next.to_s << ': debug-test 0')
155
+ # output
156
+ ar.set_level(BasicLogging::DEBUG)
157
+ l = __LINE__
158
+ ar.debug(l.next.to_s << ': debug-test 1')
159
+
160
+ obj = Object.new
161
+ obj.extend(BasicLogging)
162
+ obj.set_level(BasicLogging::DEBUG)
163
+ puts "--------debug-----------"
164
+ obj.debug('debug')
165
+ obj.info('info')
166
+ obj.warn('warn')
167
+ obj.error('error')
168
+ obj.fatal('fatal')
169
+ puts "--------info-----------"
170
+ obj.set_level("info")
171
+ obj.debug('debug')
172
+ obj.info('info')
173
+ obj.warn('warn')
174
+ obj.error('error')
175
+ obj.fatal('fatal')
176
+ puts "--------fatal-----------"
177
+ obj.set_level("fatal")
178
+ obj.debug('debug')
179
+ obj.info('info')
180
+ obj.warn('warn')
181
+ obj.error('error')
182
+ obj.fatal('fatal')
183
+ puts "--------UNKNOWN-----------"
184
+ obj.set_level(nil)
185
+ obj.debug('debug')
186
+ obj.info('info')
187
+ obj.warn('warn')
188
+ obj.error('error')
189
+ obj.fatal('fatal')
190
+ puts " ------ Output into file ----"
191
+ obj.set_target "/tmp/test_log.log"
192
+ puts " ------ INFO -----------"
193
+ obj.set_level BasicLogging::INFO
194
+ obj.info('info output')
195
+
196
+ obj.info('info output 2')
197
+ puts "---------- invalid -------"
198
+ obj.set_target "/dev/sr0"
199
+ obj.set_level "power"
200
+ end
201
+
202
+ # EOF
data/lib/basic_logging.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  #encoding: UTF-8
3
3
  =begin
4
4
  /***************************************************************************
5
- * 2023-2025, Michael Uplawski <michael.uplawski@uplawski.eu> *
5
+ * 2023-2026, Michael Uplawski <michael.uplawski@uplawski.eu> *
6
6
  * This program is free software; you can redistribute it and/or modify *
7
7
  * it under the terms of the WTFPL 2.0 or later, see *
8
8
  * http://www.wtfpl.net/about/ *
@@ -34,7 +34,7 @@ module BasicLogging
34
34
  @@log_level = UNKNOWN
35
35
  @@target = STDOUT
36
36
  @@muted = []
37
-
37
+
38
38
  # do not log, if caller is obj (class or instance)
39
39
  def self.mute(obj)
40
40
  name = obj.class == Class ? obj.name.dup : obj.class.name
@@ -57,7 +57,7 @@ module BasicLogging
57
57
  if(!lv || (lv.respond_to?(:to_int) && lv >= DEBUG && lv <= FATAL) )
58
58
  @@log_level = lv
59
59
  else
60
- msg = __FILE__.dup << ": ERROR : invalid log level \"" << lv.to_s << "\""
60
+ msg = calling_method << ": ERROR : invalid log level \"" << lv.to_s << "\""
61
61
  msg << "\n" << "Keepinng old log level " << Levels.keys.detect {| k| Levels[k] == @@log_level}.to_s
62
62
  STDERR.puts msg
63
63
  puts msg
@@ -73,14 +73,14 @@ module BasicLogging
73
73
  elsif !tg || tg.respond_to?(:to_str) && tg.strip.empty?
74
74
  @@target = nil
75
75
  else
76
- STDERR.puts __FILE__.dup << ': ERROR : target ' << tg << ' cannot be set'
76
+ STDERR.puts calling_method << ': ERROR : target ' << tg.to_str << ' cannot be set'
77
77
  STDERR.puts "Keeping old target " << @@target.inspect
78
78
  return
79
79
  end
80
80
  end
81
81
 
82
- # Output of log messages, depending on the log level set for the calling class
83
- # and the name of the alias method which is actually called.
82
+ # Output of log messages, depending on the set log level and the name of the
83
+ # alias method which is actually called.
84
84
  def log(message)
85
85
  if !BasicLogging.is_muted?(self)
86
86
  # how has this method been called?
@@ -90,7 +90,7 @@ module BasicLogging
90
90
  # the calling alias.
91
91
  format_log( message, mlevel) if @@log_level && Levels[mlevel] >= @@log_level
92
92
  else
93
- STDERR.puts __FILE__.dup << ": ERROR : invalid log level \"" << mlevel.to_s << "\""
93
+ STDERR.puts calling_method << ": ERROR : invalid log level \"" << mlevel.to_s << "\""
94
94
  end
95
95
  end
96
96
  end
@@ -110,6 +110,21 @@ module BasicLogging
110
110
  end
111
111
  end
112
112
 
113
+ # find the name of the calling object's class or the file name in case of the
114
+ # top level object... or any dumb instance of Object
115
+ def log_name()
116
+ if !@log_name
117
+ clname = self.class.name
118
+ # limit analysis of the call stack to 'Object'.
119
+ if clname == 'Object'
120
+ @log_name = File::basename(caller.last.split(':')[0])
121
+ else
122
+ @log_name = self.class == Class ? self.name.dup << ' [class]' : clname
123
+ end
124
+ end
125
+ @log_name
126
+ end
127
+
113
128
  alias :debug :log
114
129
  alias :info :log
115
130
  alias :warn :log
@@ -118,6 +133,31 @@ module BasicLogging
118
133
 
119
134
  private
120
135
 
136
+ # Find the method that called, or 'main' in case of a top level Object.
137
+ # This needs a bunch of memory and slows down the process.
138
+ # Use of this method should be limited.
139
+ def calling_method()
140
+ # find instances other than the current module.
141
+ stack_item = caller.detect do |item|
142
+ item.split(':')[0] != __FILE__
143
+ end
144
+ if stack_item
145
+ if stack_item.include?('#')
146
+ m = stack_item.split(':').last.split("#")[1]
147
+ m.gsub!("'", '') if m && m.include?("'")
148
+ # looks cooler with a class name in front.
149
+ return '#' << m << '()'
150
+ else
151
+ # top level Object
152
+ return stack_item.split(':').last
153
+ end
154
+ else
155
+ # This is dumb but won't hurt a lot.
156
+ return 'from main'
157
+ end
158
+ end
159
+
160
+ # could be useful
121
161
  def lock_target(&block)
122
162
  begin
123
163
  if @@target.respond_to?(:flock)
@@ -128,19 +168,27 @@ module BasicLogging
128
168
  block.call
129
169
  end
130
170
  rescue => ex
131
- STDERR.puts __FILE__.dup << ": ERROR : cannot lock target (" << ex.message << ")"
171
+ STDERR.puts calling_method << ": ERROR : cannot lock target (" << ex.message << ")"
132
172
  end
133
173
  end
134
174
 
135
175
  # 1 format_log for all loggers.
136
176
  def format_log(message, mlevel)
137
177
  if @@target
178
+ location = log_name().dup << ' '
179
+ # Limit the memory-hungry analysis of the call stack
180
+ # This also takes about 2.5 times as long.
181
+ if mlevel != :info
182
+ location = log_name().dup << ' ' << calling_method << ' '
183
+ end
138
184
  # indicate if a registered class or the registered object of a class is calling.
139
- name = self.class == Class ? self.name.dup << ' [class]' : self.class.name
140
- lock_target{@@target.puts '' << name << ' ' << mlevel.to_s << ' ' << Time.now.strftime("%H:%M:%S:%6N") << ': ' << message.gsub("\n", "\n |")}
185
+ lock_target{@@target.puts '' << location << mlevel.to_s << ' ' << Time.now.strftime("%H:%M:%S:%6N") << ': ' << message.gsub("\n", "\n |")}
141
186
  end
142
187
  end
143
- end
188
+ end # Module end
189
+
190
+
191
+
144
192
  #---------test: execute file----------
145
193
  if $0 == __FILE__
146
194
  Array.extend(BasicLogging)
@@ -186,14 +234,14 @@ if $0 == __FILE__
186
234
  obj.warn('warn')
187
235
  obj.error('error')
188
236
  obj.fatal('fatal')
189
- puts " ------ Output into file ----"
237
+ # ---- into file ---
190
238
  obj.set_target "/tmp/test_log.log"
191
- puts " ------ INFO -----------"
239
+ puts "------ INFO into #{obj.target}-----------"
192
240
  obj.set_level BasicLogging::INFO
193
241
  obj.info('info output')
194
242
 
195
243
  obj.info('info output 2')
196
- puts "---------- invalid -------"
244
+ puts "---------- invalid (fails) -------"
197
245
  obj.set_target "/dev/sr0"
198
246
  obj.set_level "power"
199
247
  end
data/lib/body.rb CHANGED
@@ -20,6 +20,7 @@ require_relative 'configuration'
20
20
  WRAP_LENGTH = 65
21
21
  # Endow the String class with a wrap function.
22
22
  # This is not yet applicable to the message body, itself.
23
+
23
24
  class String
24
25
  include BasicLogging
25
26
  public
data/lib/color_output.rb CHANGED
@@ -33,22 +33,48 @@ STYLES = {:regular => REGULAR, :bold => BOLD, :underline => UNDERLINE, :blink =>
33
33
  # Colorizes the given text. Color-code is either an escape-sequence or one of
34
34
  # the symbols representing color-names in the COLORS hash.
35
35
  def colorize(text, color_code)
36
- if (COLORS.keys.include?(color_code) )
37
- "\033[3#{COLORS[color_code]}m#{text}\033[0m"
36
+ # ensure that colors are supported
37
+ if $stdout.tty? && ENV['TERM'] != 'dumb'
38
+ if (COLORS.keys.include?(color_code) )
39
+ "\033[3#{COLORS[color_code]}m#{text}\033[0m"
40
+ else
41
+ "#{color_code}#{text}\033[0m"
42
+ end
43
+ # if Terminal does not support colors
38
44
  else
39
- "#{color_code}#{text}\033[0m"
45
+ text
40
46
  end
41
47
  end
42
48
 
49
+ # rgb foreground
50
+ def colorize_RGB(text, r, g, b)
51
+ code = "%s;%s;%s" %[r, g, b]
52
+ "\033[38;2;#{code}m#{text}\033[0m"
53
+ end
54
+
55
+ # rgb background
56
+ def background_RGB(text, r, g, b)
57
+ code = "%s;%s;%s" %[r, g, b]
58
+ "\033[48;2;#{code}m#{text}\033[0m"
59
+ end
60
+
61
+ # duplicate code without much intelligence
43
62
  def style(text, style_code)
44
63
  "#{style_code}#{text}\033[0m"
45
64
  end
65
+
46
66
  # a function which allows to manipulate every known aspect of the ansi-output.
47
67
  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]
68
+ if $stdout.tty? && ENV['TERM'] != 'dumb'
69
+ "\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]
70
+ else
71
+ # fallback for terminals that do not support colors.
72
+ output_text
73
+ end
49
74
  end
50
75
 
51
76
  # convenience functions
77
+ def black(text); colorize(text, "\033[30m"); end
52
78
  def red(text); colorize(text, "\033[31m"); end
53
79
  def green(text); colorize(text, "\033[32m"); end
54
80
  def yellow(text); colorize(text, "\033[33m"); end
@@ -57,9 +83,20 @@ def cyan(text); colorize(text, "\033[36m"); end
57
83
  def blue(text); colorize(text, "\033[34m"); end
58
84
  def white(text); colorize(text, "\033[37m"); end
59
85
 
86
+ # RGB
87
+ # TODO: Fallbacks from the 256 color palette
88
+ def orange(text); colorize(text, "\033[38;2;255;165;0m"); end
89
+ def pink(text); colorize(text, "\033[38;2;255;0;165m"); end
90
+ def lightred(text); colorize(text, "\033[38;2;255;100;100m"); end
91
+ def lightgreen(text); colorize(text, "\033[38;2;100;255;100m"); end
92
+ def brown(text); colorize(text, "\033[38;2;153;76;0m"); end
93
+ def crimson(text); colorize(text, "\033[38;2;220;20;60m"); end
94
+
95
+ # quick contrast
60
96
  def black_on_white(text); colorize(colorize(text, "\033[30m"), "\033[47m");end
61
97
  def white_on_black(text); colorize(colorize(text, "\033[37m"), "\033[40m");end
62
98
 
99
+ # styles
63
100
  def bold(text); style(text, "\033[01m");end
64
101
  def underline(text); style(text, "\033[04m");end
65
102
 
data/lib/configuration.rb CHANGED
@@ -78,6 +78,7 @@ class Configuration
78
78
  def update_config(i_config)
79
79
  if @conf
80
80
  conf_version = @conf[:CONFIG_VERSION]
81
+ debug 'conf_version is ' << conf_version.to_s
81
82
  if !conf_version || conf_version < PROGVERSION.to_f
82
83
  info "configuration has an older version number, looking for changes"
83
84
  i_conf = YAML::load_file(i_config)
@@ -17,7 +17,7 @@
17
17
  # the process, if they are still valid.
18
18
  # ATTN! COMMENTS WILL BE REMOVED but a copy of your previous configuration
19
19
  # will be saved in a file with a version suffix.
20
- CONFIG_VERSION: 1.35
20
+ CONFIG_VERSION: 1.95
21
21
 
22
22
  # FUP_NAME
23
23
  # A Regular Expression, describing the string which contains the name of
@@ -169,5 +169,19 @@ VFY_URLS: No
169
169
  # DEFAULT: yes
170
170
  OVERRIDE_CONFIG: YES
171
171
 
172
+ # USE_OLD
173
+ # A Boolean constant. The post processor can handle subject changes in the way, that
174
+ # old subjects which are preceded by variations of the string '(Was:' are automatically
175
+ # removed from the header. This imposes, that our own subject changes must be ignored!
176
+ # This behavior is activated here and necessitates that our own subject changes
177
+ # are indicated with a prefix "(Old:". It will be replaced by "(Was:" in the
178
+ # posted article.
179
+ #
180
+ # Set this option to true, when you want to use the prefix
181
+ # "(Old:", or to false, no, NO or similar if you do not want subject changes
182
+ # handled by the program.
183
+ # DEFAULT: No
184
+ USE_OLD: false
185
+
172
186
  # EOF
173
187
 
data/lib/headers.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  #encoding: UTF-8
2
2
  =begin
3
3
  /***************************************************************************
4
- * 2023-2025, Michael Uplawski <michael.uplawski@uplawski.eu> *
4
+ * 2023-2026, Michael Uplawski <michael.uplawski@uplawski.eu> *
5
5
  * This program is free software; you can redistribute it and/or modify *
6
6
  * it under the terms of the WTFPL 2.0 or later, see *
7
7
  * http://www.wtfpl.net/about/ *
@@ -13,6 +13,7 @@
13
13
  ***************************************************************************/
14
14
  =end
15
15
 
16
+ require 'rfc_2047'
16
17
  require_relative 'basic_logging'
17
18
  require_relative 'configuration'
18
19
  require_relative 'newsgroups'
@@ -81,10 +82,9 @@ class Headers
81
82
  headers[cur_header] = val
82
83
  end
83
84
  end
84
-
85
+
85
86
  return headers
86
87
  end
87
-
88
88
  def self::supersedes?(article_text)
89
89
  headers(article_text).keys.include?(:Supersedes)
90
90
  end
@@ -124,6 +124,10 @@ class Headers
124
124
  @headers["X-No-Archive".to_sym] = no_archive
125
125
  @headers["Archive".to_sym] = 'no'
126
126
  end
127
+ # ------->
128
+ # check for altered Subject and impose the newer value
129
+ alterSubject()
130
+ # <----
127
131
  if @config.CUSTOM_HEADERS
128
132
  ch = @config.CUSTOM_HEADERS
129
133
  debug('setting custom headers : ' << ch.inspect)
@@ -131,15 +135,18 @@ class Headers
131
135
  ch = pair.split(':')
132
136
  hn = ch[0].strip
133
137
  hv = ch[1].strip
134
- # Ensure header is as only
138
+ # Ensure header is ascii only
135
139
  if hv.ascii_only? && hn.ascii_only?
136
140
  # <---------- special treatment Post-Processor ---------->
137
141
  hv << ' ' << PROGVERSION.to_s if hn == 'X-Post-Processor' && hv == 'flnews_post_proc'
138
142
  # >----------<
139
143
  @headers[hn.to_sym] = hv
140
144
  else
141
- warn "Custom header [#{hn}:#{hv}] should be ASCII only! Header is ignored!"
145
+ warn "Custom header [#{hn}:#{hv}] should be ASCII only! But I try to encode it..."
146
+ hv = hv.split.collect{|w| (w.ascii_only? ? w : Rfc2047.encode(w, encoding: :Q)) }.join(' ')
147
+ debug ' custom header encoded'
142
148
  end
149
+ @headers[hn.to_sym] = hv
143
150
  end
144
151
  @headers.compact!
145
152
  end
@@ -173,6 +180,74 @@ class Headers
173
180
 
174
181
  attr_reader :lines, :newsgroups
175
182
 
183
+ private
184
+
185
+ # Check for was, war, était and clip the old subject header.
186
+ # Currently UNUSED
187
+ def alterSubject
188
+ if @config.USE_OLD
189
+ refs = @headers[:References]
190
+ # only if older articles exist in the thread
191
+ if refs && !refs.empty?
192
+ hv = @headers[:Subject]
193
+ if hv
194
+ debug 'checking for altered Subject'
195
+
196
+
197
+ # Our own very first article with an altered subject line, uses:
198
+ # (old:
199
+ # because this is what we do.
200
+
201
+ old = /\(Old:/i
202
+ if hv.match(old)
203
+ debug "\tfound old"
204
+ if hv.split(old).length == 2
205
+ @headers[:Subject]= hv.sub(old, '(Was:')
206
+ debug 'Subject is now: ' << @headers[:Subject]
207
+ return
208
+ end
209
+ end
210
+ # Handle previous subject changes.
211
+ # Create some regexps
212
+ olds = Array.new
213
+ olds << /\s*\(was:.*/i
214
+ olds << /\s*\(war:.*/i
215
+ olds << /\s*\(était\s?:.*/i
216
+ olds << /\s*\(etait\s?:.*/i
217
+
218
+ #decode subject from header value
219
+ subj = Rfc2047.decode(hv)
220
+ debug 'subject encoding: ' << hv.encoding.to_s
221
+ debug 'decoded subject: ' << subj
222
+ # check for each of those regexps
223
+ olds.each do |old|
224
+ m = subj.match(old)
225
+ debug "matches prefix #{old}?" << (m ? m.to_s : ' NO!')
226
+ # works too, but contains empty strings, if not
227
+ # parts = subj.partition(old) if m
228
+ # simpler:
229
+ parts = subj.split(old) if m
230
+ debug 'partitioned subject ' << parts.inspect
231
+ if parts
232
+ # ---> do not understand why this is needed:
233
+ new_subject = parts[0].strip('(')
234
+ # <--- but it is
235
+ # encode if you must.
236
+ new_subject = new_subject.split.collect{|w| (w.ascii_only? ? w : Rfc2047.encode(w, encoding: :Q)) }.join(' ')
237
+ # encode everything.
238
+ #new_subject = new_subject.ascii_only? ? new_subject : Rfc2047.encode(new_subject, encoding: :Q)
239
+ # probably unneeded
240
+ @headers[:Subject] = new_subject.force_encoding('UTF-8')
241
+ debug 'subject is now: ' << @headers[:Subject]
242
+ return
243
+ end
244
+ end
245
+ end
246
+ end
247
+ else
248
+ info('subject changes are ignored')
249
+ end
250
+ end
176
251
  end
177
252
  # EOF
178
253
 
data/lib/version.rb CHANGED
@@ -14,8 +14,8 @@
14
14
  =end
15
15
 
16
16
  PROGNAME = 'flnews_post_proc'
17
- PROGVERSION = "1.94"
17
+ PROGVERSION = "1.97"
18
18
  AUTHORS = "Michael Uplawski"
19
19
  EMAIL = "michael.uplawski@uplawski.eu"
20
20
  YEARS = "2023 - 2026"
21
- SUMMARY = "Documentation improved (concerns mainly the PDF)"
21
+ SUMMARY = "Custom headers are rfc2047 encoded, if need be"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flnews_post_proc
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.94'
4
+ version: '1.97'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Uplawski
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-01-26 00:00:00.000000000 Z
10
+ date: 2026-06-21 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: diffy
@@ -68,6 +68,7 @@ files:
68
68
  - doc/pdf/flnews_post_proc.pdf
69
69
  - doc/rst/flnews_post_proc.rst
70
70
  - lib/_quoting_style_regexp
71
+ - lib/bak_basic_logging.rb
71
72
  - lib/basic_logging.rb
72
73
  - lib/body.rb
73
74
  - lib/color_output.rb
@@ -101,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
102
  - !ruby/object:Gem::Version
102
103
  version: '0'
103
104
  requirements: []
104
- rubygems_version: 4.0.4
105
+ rubygems_version: 4.0.13
105
106
  specification_version: 4
106
- summary: Documentation improved (concerns mainly the PDF)
107
+ summary: Custom headers are rfc2047 encoded, if need be
107
108
  test_files: []