flnews_post_proc 1.7

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,336 @@
1
+ =======================
2
+ flnews_post_proc
3
+ =======================
4
+ ------------------------------------------
5
+ Post-Processor for the flnews newsreader
6
+ ------------------------------------------
7
+
8
+ SYNOPSIS
9
+ =======================
10
+
11
+ An article is piped-in to the post-processor. This is normally done
12
+ automatically as soon as the variable “post_proc“ in the flnews configuration
13
+ file is set to the flnews_post_proc.
14
+
15
+ Locally stored articles can be piped in to the post-processor, as needed
16
+ for testing purposes, with a command-line like the following:
17
+
18
+ **flnews_post_proc < article**
19
+
20
+ DESCRIPTION
21
+ =======================
22
+ The flnews newsreader is sufficient for Usenet access, i.e. to receive and read
23
+ articles from -, as well as to write and post articles to newsgroups.
24
+
25
+ When you compare news-clients, you will always notice the differences and
26
+ choose the software that you prefer. Flnews however, has the charm that you can
27
+ influence how the program itself works but also modify posts that flnews
28
+ produces, just before the program will transmit them to the chosen nntp-server.
29
+
30
+ The flnews_post_proc can add and change details of a post, in ways that are
31
+ currently not possible with flnews alone. As the program is configurable, it
32
+ can probably respond to the needs of some Usenet users. You should, however,
33
+ rather take it as an example for what can be done and an inspiration for your
34
+ own creations.
35
+
36
+ The limits of a basic newsreader — what the program can do
37
+ ----------------------------------------------------------
38
+ While the articles that flnews creates, are complete and ready to be posted,
39
+ some users may not always agree with the result and for arbitrary reasons:
40
+
41
+ * There may be inconveniences when you post to different newsgroups in
42
+ different languages, as an introductory line which refers to a previous
43
+ post can only be set once in the flnews-configuration. The consequence
44
+ can be that your post to a french newsgroup begins with an introduction
45
+ in English.
46
+ My post-processor program can set an introductory line specifically
47
+ chosen for one or several newsgroups.
48
+
49
+ * The same conflict arises, when you have set a standard signature-text and
50
+ would like to replace it against another, based on the newsgroup you are
51
+ about to post to.
52
+ The post-processor program sets specific signatures as configured for one
53
+ or several newsgroups. You can even specify that a signature should be
54
+ picked randomly from a file, containing several signatures.
55
+
56
+ * Some custom headers may serve to convey additional information to
57
+ interested readers of your post, like GnuPG key IDs, your language skills
58
+ or the like. The signature may be a better choice than custom headers.
59
+ You are free. I just mention face and x-face but prefer that you do not
60
+ remember I did.
61
+ Custom-headers may be defined in the configuration file for the program
62
+ and will then be added to each outgoing post.
63
+
64
+ * The Archive- and the X-No-Archive header are sometimes set to avoid that an
65
+ article be saved and stays available to search-engines (Google, notably).
66
+ Test- postings, for example, do probably not justify at all that they would
67
+ be referenced in search-results.
68
+ The post-processor program can impose both headers for all posts to certain
69
+ newsgroups.
70
+
71
+ **ATTN** As of 2024, the header “X-No-Archive“ has lost most of its utility
72
+ and it is the decision of server operators to honor it or not.
73
+
74
+ * If a news post contains many references to either other posts or Web
75
+ pages, the text can be cluttered with URLs.
76
+ The post-processor can identify marked text fragments and transform them
77
+ into footnotes, which will be attached as a list at the bottom of the
78
+ post.
79
+ This works almost like the <ref/> link in Wikipedia, but the delimiter
80
+ can be determined in the configuration file.
81
+ Example (with %=):
82
+ This is an object %=and this becomes the footnote, describing the object
83
+ further=%
84
+
85
+ Dialog to override settings
86
+ ---------------------------
87
+ You can have a dialog displayed just before the post-processor is invoked, to
88
+ **disable** some configuration options. Provided that either YAD, Zenity,
89
+ Whiptail or only xterm are available on your computer, you can choose from
90
+ the following options. You **cannot** use the dialog to enable options which
91
+ have not yet been set in the configuration.
92
+
93
+ * Signatures, as set in the configuration **can be ignored**. Either
94
+ a default signature will appear as set in flnews or none.
95
+ * Custom Headers, if configured, can be **omitted**.
96
+ * The Archive- and X-No-Archive headers, if set for the current newsgroup,
97
+ **can be ignored**.
98
+ * Logging can be **switched off**, if set.
99
+
100
+ Pushing Esc or the cancle-button of the dialog interrupts the process, flnews
101
+ will not post the article.
102
+
103
+ You can disable the dialog, which ensures that all configured options will be
104
+ applied, without the need for further interaction (see below, option
105
+ OVERRIDE_CONFIG).
106
+
107
+ CONFIGURATION
108
+ ===============
109
+ On first execution of the program, a copy of the original configuration file is
110
+ created in */home/[user]/.flnews_post_proc.conf*
111
+ It is this file which is used from then on. If you delete it, it will be
112
+ recreated, on the next occasion, but your own changes will be lost.
113
+
114
+ The configuration file is in YAML syntax and full of explanations. The
115
+ variables defined in this file can be classified as belonging to one of two
116
+ categories:
117
+
118
+ * Variables describing values originally set by flnews, which should be used or
119
+ replaced. The important elements are usually matched in a capture group.
120
+
121
+ * Variables defining the new or altered content.
122
+
123
+ **FUP_NAME**
124
+ A Regular Expression, describing the string which contains the name of
125
+ previous poster who is the author of a quoted post. This string is
126
+ recognized in the original article and may be used with the fitting element
127
+ from *GROUP_INTRO*, below. The Regexp-format is that of the Regexp class in
128
+ Ruby, noted as a String. Beware to mask a backslash '\\' by another one,
129
+ like in the example. A capture-group '()' serves to extract the name from the
130
+ match result.
131
+
132
+ Leave this field empty to keep the default from the FLNews configuration
133
+ intact.
134
+
135
+ CONTENT: A String equivalent of a regular expression.
136
+
137
+ DEFAULT: EMPTY
138
+
139
+ EXAMPLE1: "On \\\\d+.\\\\d+.\\\\d{2,4} at \\\\d+:\\\\d+ **(.*)** wrote:"
140
+
141
+ EXAMPLE2: "**(.*)** wrote:"
142
+
143
+
144
+ **FUP_GROUP**
145
+ A Regular Expression, describing the string which contains the newsgroup
146
+ where the previous post, that you are referring to in the followup, had been
147
+ published.
148
+
149
+ Leave this field empty to ignore the precise group.
150
+
151
+ CONTENT: A String equivalent of a regular expression.
152
+
153
+ DEFAULT: EMPTY
154
+
155
+ EXAMPLE: "wrote in **(.*)**:"
156
+
157
+ **GROUP_INTROS**
158
+ Introductory strings, referring to the previous poster who is the author of a
159
+ quoted post. If you match the newsgroup of the post (see FUP_GROUP), you can
160
+ use these variables in the result.
161
+ Currently only %fup_name% and %fup_group% are reproduced in the resulting
162
+ introductory string.
163
+
164
+ CONTENT: A newsgroup or regexp per line, followed by a colon, a space and a String
165
+
166
+ DEFAULT: As configured in FLNews
167
+
168
+ EXAMPLE: alt.test: "Thus spoke %fup_name% on that baleful %fup_date%:"
169
+
170
+ **GROUP_SIGS**
171
+ A signature line per Newsgroup or a file path. The file should contain signatures,
172
+ already formatted and separated by 1 empty line. The program will randomly pick
173
+ one signature from the list.
174
+
175
+ ATTN! In multi line signatures, you have to use \\r\\n for line breaks.
176
+
177
+ CONTENT: A newsgroup or regexp per line, followed by a colon, a space and a String.
178
+
179
+ DEFAULT: As configured in flnews
180
+
181
+ EXAMPLE: alt.test: "Signature for alt.test\\r\\nsecond line"
182
+
183
+ EXAMPLE: comp.*: /home/[user]/.my_sigs
184
+
185
+ **CUSTOM_HEADERS**
186
+ Additional headers for the outgoing article
187
+
188
+ CONTENT: 1 line per header : a dash and space, then a String, comprising the
189
+ name of the header, ending in a colon and the value of the header.
190
+
191
+ DEFAULT: undefined
192
+
193
+ | EXAMPLE (2 headers):
194
+ | - 'X-My-Header: nothing fancy'
195
+ | - 'X-Another-Header: care not!'
196
+
197
+ **NO_ARCHIVE_GROUPS**
198
+ The newsgroups, where the headers “Archive: no” and “X-No-Archive: YES” shall
199
+ be set.
200
+
201
+ CONTENT: a dash and space, then a String, containing the name of the group or a regexp.
202
+
203
+ DEFAULT: empty
204
+
205
+ | EXAMPLE (1 group, 1 hierarchy):
206
+ | - "alt.test"
207
+ | - "^news.*"
208
+
209
+
210
+ **DEBUG_LOG**
211
+ The name of a file, where debug messages are written. Setting this
212
+ variable will enable the log. Leave empty to disable logging.
213
+
214
+ CONTENT: The name of a writable file, which will be created if inexistent
215
+ and overwritten if need be.
216
+
217
+ DEFAULT: empty
218
+
219
+ EXAMPLE: '/tmp/a_log-file.txt'
220
+
221
+ **LOG LEVEL**
222
+ One of debug, fatal, error, info, warn
223
+
224
+ **REFERENCES_SEPARATOR**
225
+ A symbol or sequence of symbols marking the end of the message-body and the
226
+ beginning of a list of “references” or “footnotes”. It will only appear, if
227
+ the original message-body contains text marked for use as such a footnote.
228
+ See *REFERENCES_DELIMITER*.
229
+
230
+ If the option is not defined or empty, the list of footnotes will appear
231
+ below the last line of the message body and no separator will be inserted.
232
+
233
+ CONTENT: A quoted symbol or sequence of symbols.
234
+
235
+ DEFAULT: empty
236
+
237
+ EXAMPLE: '---------'
238
+
239
+ **REFERENCES_DELIMITER**
240
+ A sequence of at least two symbols marking the beginning of a text which will
241
+ serve as footnote (or reference). The **reversed sequence** musst be used to
242
+ mark the end of the text. The presence of this sequence or symbol in the
243
+ original message body will cause the enclosed text to be moved below the
244
+ message body. The *REFERENCES_SEPARATOR*, if defined, will separate the
245
+ message from the list of footnotes.
246
+ If this option is not defined or empty, footnotes are not created.
247
+
248
+ CONTENT a quoted symbol or sequence of symbols.
249
+
250
+ DEFAULT: none/empty
251
+
252
+ EXAMPLE: '%?'
253
+
254
+ **REFERENCE_FORMAT**
255
+ A format-string, using %s for a number, replacing the reference-
256
+ text in the message body.
257
+
258
+ DEFAULT: " %s)" -> becomes 1) ... 2) ... 3)
259
+
260
+ EXAMPLE: "(%s)" -> becomes (1) ... (2) ... (3)
261
+
262
+ **VFY_URLS**
263
+ A Boolean constant. It determines if the program shall verify and possibly
264
+ try to correct URLs. Even if URLs are identified as such, only a few
265
+ manipulations are attempted :
266
+ * Angular brackets '<' and '>' are added, if missing
267
+ * Slashes are added, if they are found missing after "http(s):"
268
+
269
+ If the variable is not set, a value 'yes' is assumed.
270
+
271
+ CONTENT: One of YES, yes, NO, no, and other variations of case.
272
+
273
+ DEFAULT: yes
274
+
275
+ Example: ... I let you guess.
276
+
277
+ **OVERRIDE_CONFIG**
278
+ A Boolean constant. You can choose to override the following configuration
279
+ options before an article is posted: GROUP_SIGS, XNAY_GROUPS, CUSTOM_HEADERS,
280
+ DEBUG_LOG and VFY_URLS. A dialog may be displayed which allows you to disable
281
+ any of these five options, so that the defaults from flnews prevail.
282
+
283
+ ATTN! Canceling the dialog or pushing the Esc-key does interrupt the process.
284
+ Flnews will not post the article.
285
+
286
+ Set this option to no, NO or similar to disable the dialog.
287
+
288
+ DEFAULT: yes
289
+
290
+ EXAMPLE: No
291
+
292
+ Other Information
293
+ =================
294
+
295
+ Testing
296
+ -------
297
+ The effects that the execution of the program will have on a posting can be
298
+ verified in two ways:
299
+
300
+ 1. By piping-in a post that had previously been saved to a file:
301
+
302
+ **:~$ /usr/local/bin/[post-processor] < [test-article]**
303
+
304
+ This will show the resulting new version of the article on screen, but you
305
+ can also pipe the output into another file. This is a great way to test a
306
+ program during development or to test your own configuration of the program.
307
+ 2. By posting directly into a test-newsgroup (like alt.test or similar). This
308
+ is mandatory before you really post to thematic newsgroups and when the
309
+ settings of the post-processor will affect the article.
310
+
311
+
312
+ Source-Code
313
+ -----------
314
+ The gem-file that you get with the gem-utility or from rubygems.org contains
315
+ all the code of the program and some documentation (this page notably). To read
316
+ its content, you must
317
+
318
+ 1. untar the gem-file with tar -xf flnews_post_proc-0.1.gem
319
+ 2. uncompress the data.gz archive: gunzip data.gz
320
+ 3. untar the resultig data.tar archive: tar -xf data.tar
321
+
322
+ This creates the directories bin, doc and lib.
323
+
324
+ License
325
+ -------
326
+ flnews_post_proc is distributed under the conditions of the WTFPL-2.0 or later
327
+ License (see http://www.wtfpl.net/txt/copying/ or license-text in the doc
328
+ directory of the gem-file).
329
+
330
+ Author
331
+ ------
332
+ | flnews_post_proc has been developed by
333
+ | Michael Uplawski <michael.uplawski@uplawski.eu>
334
+
335
+ Ω
336
+ ==
@@ -0,0 +1,2 @@
1
+ # UNUSED
2
+ " > >> > testerei ei ei".strip.gsub( /^>+(\s+>+)*/) {|m| m.to_s.delete(" ")}
@@ -0,0 +1,202 @@
1
+ #!/bin/env ruby
2
+ #encoding: UTF-8
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
+ #
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
+ def self.is_muted?(obj)
45
+ name = obj.class == Class ? obj.name.dup : obj.class.name
46
+ @@muted.include?(name)
47
+ end
48
+
49
+ # set the log level
50
+ def set_level(lv)
51
+ if lv.respond_to?(:to_str) && Levels.keys.include?(lv.strip.to_sym)
52
+ lv = Levels[lv.to_sym]
53
+ elsif lv.respond_to?(:to_sym) && Levels.keys.include?(lv)
54
+ lv = Levels[lv]
55
+ end
56
+
57
+ if(!lv || (lv.respond_to?(:to_int) && lv >= DEBUG && lv <= FATAL) )
58
+ @@log_level = lv
59
+ else
60
+ msg = __FILE__.dup << ": ERROR : invalid log level \"" << lv.to_s << "\""
61
+ msg << "\n" << "Keepinng old log level " << Levels.keys.detect {| k| Levels[k] == @@log_level}.to_s
62
+ STDERR.puts msg
63
+ puts msg
64
+ end
65
+ end
66
+
67
+ # set the log target
68
+ def set_target(tg)
69
+ if tg.respond_to?(:to_io)
70
+ @@target = tg
71
+ elsif(!File::exist?(tg) || ( File.file?(tg) && File.writable?(tg) ) )
72
+ @@target = File.open(tg, 'w+')
73
+ elsif !tg || tg.respond_to?(:to_str) && tg.strip.empty?
74
+ @@target = nil
75
+ else
76
+ STDERR.puts __FILE__.dup << ': ERROR : target ' << tg << ' cannot be set'
77
+ STDERR.puts "Keeping old target " << @@target.inspect
78
+ return
79
+ end
80
+ end
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.
84
+ def log(message)
85
+ if !BasicLogging.is_muted?(self)
86
+ # how has this method been called?
87
+ mlevel = __callee__
88
+ if Levels.has_key?(mlevel) && Levels[mlevel] <= FATAL
89
+ # output only for levels equal or above the value that corresponds to
90
+ # the calling alias.
91
+ format_log( message, mlevel) if @@log_level && Levels[mlevel] >= @@log_level
92
+ else
93
+ STDERR.puts __FILE__.dup << ": ERROR : invalid log level \"" << mlevel.to_s << "\""
94
+ end
95
+ end
96
+ end
97
+
98
+ def target
99
+ @@target.path if @@target
100
+ end
101
+
102
+ def level
103
+ @@level.to_s if @@level
104
+ end
105
+
106
+ # Clear the log (-file)
107
+ def clear_log
108
+ if @@target && @@target.respond_to?(:truncate)
109
+ lock_target{ @@target.truncate(0) }
110
+ end
111
+ end
112
+
113
+ alias :debug :log
114
+ alias :info :log
115
+ alias :warn :log
116
+ alias :error :log
117
+ alias :fatal :log
118
+
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
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