pg 1.4.4-x86-mingw32 → 1.4.6-x86-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.appveyor.yml +14 -8
- data/.github/workflows/binary-gems.yml +42 -11
- data/.github/workflows/source-gem.yml +24 -18
- data/.gitignore +8 -2
- data/.travis.yml +2 -2
- data/{History.rdoc → History.md} +174 -150
- data/README.ja.md +266 -0
- data/README.md +272 -0
- data/Rakefile +13 -4
- data/Rakefile.cross +7 -11
- data/certs/larskanis-2023.pem +24 -0
- data/ext/errorcodes.def +4 -0
- data/ext/errorcodes.txt +2 -1
- data/ext/pg.c +3 -0
- data/ext/pg_connection.c +110 -27
- data/lib/2.5/pg_ext.so +0 -0
- data/lib/2.6/pg_ext.so +0 -0
- data/lib/2.7/pg_ext.so +0 -0
- data/lib/3.0/pg_ext.so +0 -0
- data/lib/3.1/pg_ext.so +0 -0
- data/lib/3.2/pg_ext.so +0 -0
- data/lib/pg/connection.rb +21 -19
- data/lib/pg/exceptions.rb +7 -0
- data/lib/pg/version.rb +1 -1
- data/lib/pg.rb +0 -6
- data/lib/x86-mingw32/libpq.dll +0 -0
- data/pg.gemspec +4 -2
- data/rakelib/task_extension.rb +1 -1
- data/translation/.po4a-version +7 -0
- data/translation/po/all.pot +875 -0
- data/translation/po/ja.po +868 -0
- data/translation/po4a.cfg +9 -0
- data.tar.gz.sig +0 -0
- metadata +89 -31
- metadata.gz.sig +2 -1
- data/README.ja.rdoc +0 -13
- data/README.rdoc +0 -233
data/ext/errorcodes.txt
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# errcodes.txt
|
3
3
|
# PostgreSQL error codes
|
4
4
|
#
|
5
|
-
# Copyright (c) 2003-
|
5
|
+
# Copyright (c) 2003-2022, PostgreSQL Global Development Group
|
6
6
|
#
|
7
7
|
# This list serves as the basis for generating source files containing error
|
8
8
|
# codes. It is kept in a common format to make sure all these source files have
|
@@ -222,6 +222,7 @@ Section: Class 22 - Data Exception
|
|
222
222
|
2203D E ERRCODE_TOO_MANY_JSON_ARRAY_ELEMENTS too_many_json_array_elements
|
223
223
|
2203E E ERRCODE_TOO_MANY_JSON_OBJECT_MEMBERS too_many_json_object_members
|
224
224
|
2203F E ERRCODE_SQL_JSON_SCALAR_REQUIRED sql_json_scalar_required
|
225
|
+
2203G E ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE sql_json_item_cannot_be_cast_to_target_type
|
225
226
|
|
226
227
|
Section: Class 23 - Integrity Constraint Violation
|
227
228
|
|
data/ext/pg.c
CHANGED
@@ -679,6 +679,9 @@ Init_pg_ext(void)
|
|
679
679
|
rb_define_const(rb_mPGconstants, "INVALID_OID", INT2FIX(InvalidOid));
|
680
680
|
rb_define_const(rb_mPGconstants, "InvalidOid", INT2FIX(InvalidOid));
|
681
681
|
|
682
|
+
/* PostgreSQL compiled in default port */
|
683
|
+
rb_define_const(rb_mPGconstants, "DEF_PGPORT", INT2FIX(DEF_PGPORT));
|
684
|
+
|
682
685
|
/* Add the constants to the toplevel namespace */
|
683
686
|
rb_include_module( rb_mPG, rb_mPGconstants );
|
684
687
|
|
data/ext/pg_connection.c
CHANGED
@@ -702,7 +702,10 @@ static VALUE
|
|
702
702
|
pgconn_port(VALUE self)
|
703
703
|
{
|
704
704
|
char* port = PQport(pg_get_pgconn(self));
|
705
|
-
|
705
|
+
if (!port || port[0] == '\0')
|
706
|
+
return INT2NUM(DEF_PGPORT);
|
707
|
+
else
|
708
|
+
return INT2NUM(atoi(port));
|
706
709
|
}
|
707
710
|
|
708
711
|
/*
|
@@ -2443,8 +2446,9 @@ pgconn_async_flush(VALUE self)
|
|
2443
2446
|
VALUE socket_io = pgconn_socket_io(self);
|
2444
2447
|
events = RB_NUM2INT(pg_rb_io_wait(socket_io, RB_INT2NUM(PG_RUBY_IO_READABLE | PG_RUBY_IO_WRITABLE), Qnil));
|
2445
2448
|
|
2446
|
-
if (events & PG_RUBY_IO_READABLE)
|
2449
|
+
if (events & PG_RUBY_IO_READABLE){
|
2447
2450
|
pgconn_consume_input(self);
|
2451
|
+
}
|
2448
2452
|
}
|
2449
2453
|
return Qtrue;
|
2450
2454
|
}
|
@@ -3123,8 +3127,14 @@ pgconn_async_get_last_result(VALUE self)
|
|
3123
3127
|
* conn.discard_results()
|
3124
3128
|
*
|
3125
3129
|
* Silently discard any prior query result that application didn't eat.
|
3126
|
-
* This is
|
3127
|
-
*
|
3130
|
+
* This is internally used prior to Connection#exec and sibling methods.
|
3131
|
+
* It doesn't raise an exception on connection errors, but returns +false+ instead.
|
3132
|
+
*
|
3133
|
+
* Returns:
|
3134
|
+
* * +nil+ when the connection is already idle
|
3135
|
+
* * +true+ when some results have been discarded
|
3136
|
+
* * +false+ when a failure occured and the connection was closed
|
3137
|
+
*
|
3128
3138
|
*/
|
3129
3139
|
static VALUE
|
3130
3140
|
pgconn_discard_results(VALUE self)
|
@@ -3132,8 +3142,12 @@ pgconn_discard_results(VALUE self)
|
|
3132
3142
|
PGconn *conn = pg_get_pgconn(self);
|
3133
3143
|
VALUE socket_io;
|
3134
3144
|
|
3135
|
-
|
3136
|
-
|
3145
|
+
switch( PQtransactionStatus(conn) ) {
|
3146
|
+
case PQTRANS_IDLE:
|
3147
|
+
case PQTRANS_INTRANS:
|
3148
|
+
case PQTRANS_INERROR:
|
3149
|
+
return Qnil;
|
3150
|
+
default:;
|
3137
3151
|
}
|
3138
3152
|
|
3139
3153
|
socket_io = pgconn_socket_io(self);
|
@@ -3146,10 +3160,21 @@ pgconn_discard_results(VALUE self)
|
|
3146
3160
|
* To avoid this call pg_rb_io_wait() and PQconsumeInput() without rb_raise().
|
3147
3161
|
*/
|
3148
3162
|
while( gvl_PQisBusy(conn) ){
|
3149
|
-
|
3150
|
-
|
3151
|
-
|
3152
|
-
|
3163
|
+
int events;
|
3164
|
+
|
3165
|
+
switch( PQflush(conn) ) {
|
3166
|
+
case 1:
|
3167
|
+
events = RB_NUM2INT(pg_rb_io_wait(socket_io, RB_INT2NUM(PG_RUBY_IO_READABLE | PG_RUBY_IO_WRITABLE), Qnil));
|
3168
|
+
if (events & PG_RUBY_IO_READABLE){
|
3169
|
+
if ( PQconsumeInput(conn) == 0 ) goto error;
|
3170
|
+
}
|
3171
|
+
break;
|
3172
|
+
case 0:
|
3173
|
+
pg_rb_io_wait(socket_io, RB_INT2NUM(PG_RUBY_IO_READABLE), Qnil);
|
3174
|
+
if ( PQconsumeInput(conn) == 0 ) goto error;
|
3175
|
+
break;
|
3176
|
+
default:
|
3177
|
+
goto error;
|
3153
3178
|
}
|
3154
3179
|
}
|
3155
3180
|
|
@@ -3159,7 +3184,9 @@ pgconn_discard_results(VALUE self)
|
|
3159
3184
|
status = PQresultStatus(cur);
|
3160
3185
|
PQclear(cur);
|
3161
3186
|
if (status == PGRES_COPY_IN){
|
3162
|
-
gvl_PQputCopyEnd(conn, "COPY terminated by new
|
3187
|
+
while( gvl_PQputCopyEnd(conn, "COPY terminated by new query or discard_results") == 0 ){
|
3188
|
+
pgconn_async_flush(self);
|
3189
|
+
}
|
3163
3190
|
}
|
3164
3191
|
if (status == PGRES_COPY_OUT){
|
3165
3192
|
for(;;) {
|
@@ -3168,10 +3195,7 @@ pgconn_discard_results(VALUE self)
|
|
3168
3195
|
if( st == 0 ) {
|
3169
3196
|
/* would block -> wait for readable data */
|
3170
3197
|
pg_rb_io_wait(socket_io, RB_INT2NUM(PG_RUBY_IO_READABLE), Qnil);
|
3171
|
-
if ( PQconsumeInput(conn) == 0 )
|
3172
|
-
pgconn_close_socket_io(self);
|
3173
|
-
return Qfalse;
|
3174
|
-
}
|
3198
|
+
if ( PQconsumeInput(conn) == 0 ) goto error;
|
3175
3199
|
} else if( st > 0 ) {
|
3176
3200
|
/* some data retrieved -> discard it */
|
3177
3201
|
PQfreemem(buffer);
|
@@ -3184,6 +3208,10 @@ pgconn_discard_results(VALUE self)
|
|
3184
3208
|
}
|
3185
3209
|
|
3186
3210
|
return Qtrue;
|
3211
|
+
|
3212
|
+
error:
|
3213
|
+
pgconn_close_socket_io(self);
|
3214
|
+
return Qfalse;
|
3187
3215
|
}
|
3188
3216
|
|
3189
3217
|
/*
|
@@ -3640,6 +3668,14 @@ pgconn_send_flush_request(VALUE self)
|
|
3640
3668
|
* LARGE OBJECT SUPPORT
|
3641
3669
|
**************************************************************************/
|
3642
3670
|
|
3671
|
+
#define BLOCKING_BEGIN(conn) do { \
|
3672
|
+
int old_nonblocking = PQisnonblocking(conn); \
|
3673
|
+
PQsetnonblocking(conn, 0);
|
3674
|
+
|
3675
|
+
#define BLOCKING_END(th) \
|
3676
|
+
PQsetnonblocking(conn, old_nonblocking); \
|
3677
|
+
} while(0);
|
3678
|
+
|
3643
3679
|
/*
|
3644
3680
|
* call-seq:
|
3645
3681
|
* conn.lo_creat( [mode] ) -> Integer
|
@@ -3660,7 +3696,10 @@ pgconn_locreat(int argc, VALUE *argv, VALUE self)
|
|
3660
3696
|
else
|
3661
3697
|
mode = NUM2INT(nmode);
|
3662
3698
|
|
3663
|
-
|
3699
|
+
BLOCKING_BEGIN(conn)
|
3700
|
+
lo_oid = lo_creat(conn, mode);
|
3701
|
+
BLOCKING_END(conn)
|
3702
|
+
|
3664
3703
|
if (lo_oid == 0)
|
3665
3704
|
pg_raise_conn_error( rb_ePGerror, self, "lo_creat failed");
|
3666
3705
|
|
@@ -3705,7 +3744,10 @@ pgconn_loimport(VALUE self, VALUE filename)
|
|
3705
3744
|
|
3706
3745
|
Check_Type(filename, T_STRING);
|
3707
3746
|
|
3708
|
-
|
3747
|
+
BLOCKING_BEGIN(conn)
|
3748
|
+
lo_oid = lo_import(conn, StringValueCStr(filename));
|
3749
|
+
BLOCKING_END(conn)
|
3750
|
+
|
3709
3751
|
if (lo_oid == 0) {
|
3710
3752
|
pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn));
|
3711
3753
|
}
|
@@ -3723,11 +3765,16 @@ pgconn_loexport(VALUE self, VALUE lo_oid, VALUE filename)
|
|
3723
3765
|
{
|
3724
3766
|
PGconn *conn = pg_get_pgconn(self);
|
3725
3767
|
Oid oid;
|
3768
|
+
int ret;
|
3726
3769
|
Check_Type(filename, T_STRING);
|
3727
3770
|
|
3728
3771
|
oid = NUM2UINT(lo_oid);
|
3729
3772
|
|
3730
|
-
|
3773
|
+
BLOCKING_BEGIN(conn)
|
3774
|
+
ret = lo_export(conn, oid, StringValueCStr(filename));
|
3775
|
+
BLOCKING_END(conn)
|
3776
|
+
|
3777
|
+
if (ret < 0) {
|
3731
3778
|
pg_raise_conn_error( rb_ePGerror, self, "%s", PQerrorMessage(conn));
|
3732
3779
|
}
|
3733
3780
|
return Qnil;
|
@@ -3758,7 +3805,11 @@ pgconn_loopen(int argc, VALUE *argv, VALUE self)
|
|
3758
3805
|
else
|
3759
3806
|
mode = NUM2INT(nmode);
|
3760
3807
|
|
3761
|
-
|
3808
|
+
BLOCKING_BEGIN(conn)
|
3809
|
+
fd = lo_open(conn, lo_oid, mode);
|
3810
|
+
BLOCKING_END(conn)
|
3811
|
+
|
3812
|
+
if(fd < 0) {
|
3762
3813
|
pg_raise_conn_error( rb_ePGerror, self, "can't open large object: %s", PQerrorMessage(conn));
|
3763
3814
|
}
|
3764
3815
|
return INT2FIX(fd);
|
@@ -3783,8 +3834,12 @@ pgconn_lowrite(VALUE self, VALUE in_lo_desc, VALUE buffer)
|
|
3783
3834
|
if( RSTRING_LEN(buffer) < 0) {
|
3784
3835
|
pg_raise_conn_error( rb_ePGerror, self, "write buffer zero string");
|
3785
3836
|
}
|
3786
|
-
|
3787
|
-
|
3837
|
+
BLOCKING_BEGIN(conn)
|
3838
|
+
n = lo_write(conn, fd, StringValuePtr(buffer),
|
3839
|
+
RSTRING_LEN(buffer));
|
3840
|
+
BLOCKING_END(conn)
|
3841
|
+
|
3842
|
+
if(n < 0) {
|
3788
3843
|
pg_raise_conn_error( rb_ePGerror, self, "lo_write failed: %s", PQerrorMessage(conn));
|
3789
3844
|
}
|
3790
3845
|
|
@@ -3812,7 +3867,12 @@ pgconn_loread(VALUE self, VALUE in_lo_desc, VALUE in_len)
|
|
3812
3867
|
pg_raise_conn_error( rb_ePGerror, self, "negative length %d given", len);
|
3813
3868
|
|
3814
3869
|
buffer = ALLOC_N(char, len);
|
3815
|
-
|
3870
|
+
|
3871
|
+
BLOCKING_BEGIN(conn)
|
3872
|
+
ret = lo_read(conn, lo_desc, buffer, len);
|
3873
|
+
BLOCKING_END(conn)
|
3874
|
+
|
3875
|
+
if(ret < 0)
|
3816
3876
|
pg_raise_conn_error( rb_ePGerror, self, "lo_read failed");
|
3817
3877
|
|
3818
3878
|
if(ret == 0) {
|
@@ -3842,7 +3902,11 @@ pgconn_lolseek(VALUE self, VALUE in_lo_desc, VALUE offset, VALUE whence)
|
|
3842
3902
|
int lo_desc = NUM2INT(in_lo_desc);
|
3843
3903
|
int ret;
|
3844
3904
|
|
3845
|
-
|
3905
|
+
BLOCKING_BEGIN(conn)
|
3906
|
+
ret = lo_lseek(conn, lo_desc, NUM2INT(offset), NUM2INT(whence));
|
3907
|
+
BLOCKING_END(conn)
|
3908
|
+
|
3909
|
+
if(ret < 0) {
|
3846
3910
|
pg_raise_conn_error( rb_ePGerror, self, "lo_lseek failed");
|
3847
3911
|
}
|
3848
3912
|
|
@@ -3862,7 +3926,11 @@ pgconn_lotell(VALUE self, VALUE in_lo_desc)
|
|
3862
3926
|
PGconn *conn = pg_get_pgconn(self);
|
3863
3927
|
int lo_desc = NUM2INT(in_lo_desc);
|
3864
3928
|
|
3865
|
-
|
3929
|
+
BLOCKING_BEGIN(conn)
|
3930
|
+
position = lo_tell(conn, lo_desc);
|
3931
|
+
BLOCKING_END(conn)
|
3932
|
+
|
3933
|
+
if(position < 0)
|
3866
3934
|
pg_raise_conn_error( rb_ePGerror, self, "lo_tell failed");
|
3867
3935
|
|
3868
3936
|
return INT2FIX(position);
|
@@ -3880,8 +3948,13 @@ pgconn_lotruncate(VALUE self, VALUE in_lo_desc, VALUE in_len)
|
|
3880
3948
|
PGconn *conn = pg_get_pgconn(self);
|
3881
3949
|
int lo_desc = NUM2INT(in_lo_desc);
|
3882
3950
|
size_t len = NUM2INT(in_len);
|
3951
|
+
int ret;
|
3952
|
+
|
3953
|
+
BLOCKING_BEGIN(conn)
|
3954
|
+
ret = lo_truncate(conn,lo_desc,len);
|
3955
|
+
BLOCKING_END(conn)
|
3883
3956
|
|
3884
|
-
if(
|
3957
|
+
if(ret < 0)
|
3885
3958
|
pg_raise_conn_error( rb_ePGerror, self, "lo_truncate failed");
|
3886
3959
|
|
3887
3960
|
return Qnil;
|
@@ -3898,8 +3971,13 @@ pgconn_loclose(VALUE self, VALUE in_lo_desc)
|
|
3898
3971
|
{
|
3899
3972
|
PGconn *conn = pg_get_pgconn(self);
|
3900
3973
|
int lo_desc = NUM2INT(in_lo_desc);
|
3974
|
+
int ret;
|
3975
|
+
|
3976
|
+
BLOCKING_BEGIN(conn)
|
3977
|
+
ret = lo_close(conn,lo_desc);
|
3978
|
+
BLOCKING_END(conn)
|
3901
3979
|
|
3902
|
-
if(
|
3980
|
+
if(ret < 0)
|
3903
3981
|
pg_raise_conn_error( rb_ePGerror, self, "lo_close failed");
|
3904
3982
|
|
3905
3983
|
return Qnil;
|
@@ -3916,8 +3994,13 @@ pgconn_lounlink(VALUE self, VALUE in_oid)
|
|
3916
3994
|
{
|
3917
3995
|
PGconn *conn = pg_get_pgconn(self);
|
3918
3996
|
Oid oid = NUM2UINT(in_oid);
|
3997
|
+
int ret;
|
3998
|
+
|
3999
|
+
BLOCKING_BEGIN(conn)
|
4000
|
+
ret = lo_unlink(conn,oid);
|
4001
|
+
BLOCKING_END(conn)
|
3919
4002
|
|
3920
|
-
if(
|
4003
|
+
if(ret < 0)
|
3921
4004
|
pg_raise_conn_error( rb_ePGerror, self, "lo_unlink failed");
|
3922
4005
|
|
3923
4006
|
return Qnil;
|
data/lib/2.5/pg_ext.so
CHANGED
Binary file
|
data/lib/2.6/pg_ext.so
CHANGED
Binary file
|
data/lib/2.7/pg_ext.so
CHANGED
Binary file
|
data/lib/3.0/pg_ext.so
CHANGED
Binary file
|
data/lib/3.1/pg_ext.so
CHANGED
Binary file
|
data/lib/3.2/pg_ext.so
ADDED
Binary file
|
data/lib/pg/connection.rb
CHANGED
@@ -196,11 +196,19 @@ class PG::Connection
|
|
196
196
|
yield res
|
197
197
|
rescue Exception => err
|
198
198
|
errmsg = "%s while copy data: %s" % [ err.class.name, err.message ]
|
199
|
-
|
200
|
-
|
201
|
-
|
199
|
+
begin
|
200
|
+
put_copy_end( errmsg )
|
201
|
+
rescue PG::Error
|
202
|
+
# Ignore error in cleanup to avoid losing original exception
|
203
|
+
end
|
204
|
+
discard_results
|
205
|
+
raise err
|
202
206
|
else
|
203
|
-
|
207
|
+
begin
|
208
|
+
put_copy_end
|
209
|
+
rescue PG::Error => err
|
210
|
+
raise PG::LostCopyState.new("#{err} (probably by executing another SQL query while running a COPY command)", connection: self)
|
211
|
+
end
|
204
212
|
get_last_result
|
205
213
|
ensure
|
206
214
|
self.encoder_for_put_copy_data = old_coder if coder
|
@@ -213,24 +221,17 @@ class PG::Connection
|
|
213
221
|
self.decoder_for_get_copy_data = coder
|
214
222
|
end
|
215
223
|
yield res
|
216
|
-
rescue Exception
|
224
|
+
rescue Exception
|
217
225
|
cancel
|
218
|
-
|
219
|
-
|
220
|
-
end
|
221
|
-
rescue PG::Error
|
222
|
-
# Ignore error in cleanup to avoid losing original exception
|
223
|
-
end
|
224
|
-
while get_result
|
225
|
-
end
|
226
|
-
raise err
|
226
|
+
discard_results
|
227
|
+
raise
|
227
228
|
else
|
228
229
|
res = get_last_result
|
229
|
-
if !res
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
230
|
+
if !res
|
231
|
+
discard_results
|
232
|
+
raise PG::LostCopyState.new("Lost COPY state (probably by executing another SQL query while running a COPY command)", connection: self)
|
233
|
+
elsif res.result_status != PGRES_COMMAND_OK
|
234
|
+
discard_results
|
234
235
|
raise PG::NotAllCopyDataRetrieved.new("Not all COPY data retrieved", connection: self)
|
235
236
|
end
|
236
237
|
res
|
@@ -723,6 +724,7 @@ class PG::Connection
|
|
723
724
|
# This requires PostgreSQL-10+, so no DNS resolving is done on earlier versions.
|
724
725
|
ihosts = iopts[:host].split(",", -1)
|
725
726
|
iports = iopts[:port].split(",", -1)
|
727
|
+
iports = [nil] if iports.size == 0
|
726
728
|
iports = iports * ihosts.size if iports.size == 1
|
727
729
|
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
|
728
730
|
|
data/lib/pg/exceptions.rb
CHANGED
data/lib/pg/version.rb
CHANGED
data/lib/pg.rb
CHANGED
data/lib/x86-mingw32/libpq.dll
CHANGED
Binary file
|
data/pg.gemspec
CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
|
18
18
|
spec.metadata["homepage_uri"] = spec.homepage
|
19
19
|
spec.metadata["source_code_uri"] = "https://github.com/ged/ruby-pg"
|
20
|
-
spec.metadata["changelog_uri"] = "https://github.com/ged/ruby-pg/blob/master/History.
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/ged/ruby-pg/blob/master/History.md"
|
21
21
|
spec.metadata["documentation_uri"] = "http://deveiate.org/code/pg"
|
22
22
|
|
23
23
|
# Specify which files should be added to the gem when it is released.
|
@@ -28,5 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.extensions = ["ext/extconf.rb"]
|
29
29
|
spec.require_paths = ["lib"]
|
30
30
|
spec.cert_chain = ["certs/ged.pem"]
|
31
|
-
spec.rdoc_options = ["--main", "README.
|
31
|
+
spec.rdoc_options = ["--main", "README.md",
|
32
|
+
"--title", "PG: The Ruby PostgreSQL Driver"]
|
33
|
+
spec.extra_rdoc_files = `git ls-files -z *.rdoc *.md lib/*.rb lib/*/*.rb ext/*.c ext/*.h`.split("\x0")
|
32
34
|
end
|
data/rakelib/task_extension.rb
CHANGED
@@ -0,0 +1,7 @@
|
|
1
|
+
po4a version 0.69.
|
2
|
+
Written by Martin Quinson and Denis Barbier.
|
3
|
+
|
4
|
+
Copyright © 2002-2022 Software in the Public Interest, Inc.
|
5
|
+
This is free software; see source code for copying
|
6
|
+
conditions. There is NO warranty; not even for
|
7
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|