ObjFW  Check-in [6dff0f5922]

Overview
Comment:Always use "." for the decimal point

This is achieved by replacing the locale's decimal point with "." after
formatting and replacing "." with the locale's decimal point before
parsing.

To still use the decimal point from the locale for formatting, the new
flag "," is introduced to formats. This is useful for just printing a
string to the user that is not saved to a file or sent via a network.

While this is an ugly hack, there is no better way to do this other than
implementing the functionality of printf and strtod myself, as POSIX
does not specify versions of these that take a locale as an argument.
While this is a lot of work and error-prone, I will most likely end up
doing this eventually.

This commit also enables the locale in OFApplication to notice when
things break. As a nice side effect, ofhttp now uses the locale's
decimal point in its user interface.

Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 6dff0f59220b3915dfe2e699b4278eaa20ae05ba2780621ef5f9409a54dec868
User & Date: js on 2017-01-07 02:34:39
Other Links: manifest | tags
Context
2017-01-07
03:26
Use strtof_l, strtod_l and asprintf_l if available check-in: 0ad678f125 user: js tags: trunk
02:34
Always use "." for the decimal point check-in: 6dff0f5922 user: js tags: trunk
00:37
Add of_ascii_{to{upper,lower},is{alpha,alnum}} check-in: d9eb7b50b3 user: js tags: trunk
Changes

Modified src/OFApplication.m from [e942c20c56] to [669a08ac5c].

16
17
18
19
20
21
22

23
24
25
26
27
28
29
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30







+








#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <locale.h>
#include <signal.h>

#import "OFApplication.h"
#import "OFString.h"
#import "OFArray.h"
#import "OFDictionary.h"
#import "OFSystemInfo.h"
96
97
98
99
100
101
102


103
104
105
106
107
108
109
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112







+
+







of_application_main(int *argc, char **argv[], Class cls)
{
	id <OFApplicationDelegate> delegate;
#ifdef OF_WINDOWS
	wchar_t **wargv, **wenvp;
	int wargc, si = 0;
#endif

	setlocale(LC_ALL, "");

	if ([cls isSubclassOfClass: [OFApplication class]]) {
		fprintf(stderr, "FATAL ERROR:\n  Class %s is a subclass of "
		    "class OFApplication, but class\n  %s was specified as "
		    "application delegate!\n  Most likely, you wanted to "
		    "subclass OFObject instead or specified\n  the wrong class "
		    "with OF_APPLICATION_DELEGATE().\n",

Modified src/OFNumber.m from [424f2af76e] to [114905a94e].

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
13
14
15
16
17
18
19

20
21
22
23
24
25
26







-







 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#include <inttypes.h>
#include <locale.h>
#include <math.h>

#import "OFNumber.h"
#import "OFString.h"
#import "OFXMLElement.h"
#import "OFXMLAttribute.h"
#import "OFDataArray.h"
903
904
905
906
907
908
909
910
911
912
913
914
915
916


917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933


934
935
936
937
938
939
940
941
942
943
902
903
904
905
906
907
908







909
910



911
912
913
914
915
916
917







918
919



920
921
922
923
924
925
926







-
-
-
-
-
-
-
+
+
-
-
-







-
-
-
-
-
-
-
+
+
-
-
-







	case OF_NUMBER_TYPE_INTMAX:
	case OF_NUMBER_TYPE_PTRDIFF:
	case OF_NUMBER_TYPE_INTPTR:
		return [OFString stringWithFormat: @"%jd", [self intMaxValue]];
	case OF_NUMBER_TYPE_FLOAT:
		ret = [OFMutableString stringWithFormat: @"%g", _value.float_];

		{
			void *pool = objc_autoreleasePoolPush();
			OFString *decimalPoint = [OFString stringWithUTF8String:
			    localeconv()->decimal_point];

			if (![ret containsString: decimalPoint])
			       [ret appendFormat: @"%@0", decimalPoint];
		if (![ret containsString: @"."])
			[ret appendString: @".0"];

			objc_autoreleasePoolPop(pool);
		}

		[ret makeImmutable];

		return ret;
	case OF_NUMBER_TYPE_DOUBLE:
		ret = [OFMutableString stringWithFormat: @"%g", _value.double_];

		{
			void *pool = objc_autoreleasePoolPush();
			OFString *decimalPoint = [OFString stringWithUTF8String:
			    localeconv()->decimal_point];

			if (![ret containsString: decimalPoint])
			       [ret appendFormat: @"%@0", decimalPoint];
		if (![ret containsString: @"."])
			[ret appendString: @".0"];

			objc_autoreleasePoolPop(pool);
		}

		[ret makeImmutable];

		return ret;
	default:
		@throw [OFInvalidFormatException exception];
	}

Modified src/OFString.m from [4bcce6446e] to [2a34be912f].

26
27
28
29
30
31
32

33
34
35
36
37
38
39
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40







+








#import "OFString.h"
#import "OFString_UTF8.h"
#import "OFString_UTF8+Private.h"
#import "OFArray.h"
#import "OFDictionary.h"
#import "OFDataArray.h"
#import "OFSystemInfo.h"
#ifdef OF_HAVE_FILES
# import "OFFile.h"
#endif
#import "OFURL.h"
#ifdef OF_HAVE_SOCKETS
# import "OFHTTPClient.h"
# import "OFHTTPRequest.h"
2331
2332
2333
2334
2335
2336
2337

2338



2339
2340
2341
2342
2343
2344
2345
2332
2333
2334
2335
2336
2337
2338
2339

2340
2341
2342
2343
2344
2345
2346
2347
2348
2349







+
-
+
+
+








	return value;
}

- (float)floatValue
{
	void *pool = objc_autoreleasePoolPush();
	OFString *decimalPoint = [OFSystemInfo decimalPoint];
	const char *UTF8String = [self UTF8String];
	const char *UTF8String = [[self
	    stringByReplacingOccurrencesOfString: @"."
				      withString: decimalPoint] UTF8String];
	char *endPointer = NULL;
	float value;

	while (*UTF8String == ' ' || *UTF8String == '\t' ||
	    *UTF8String == '\n' || *UTF8String == '\r' || *UTF8String == '\f')
		UTF8String++;

2357
2358
2359
2360
2361
2362
2363

2364



2365
2366
2367
2368
2369
2370
2371
2361
2362
2363
2364
2365
2366
2367
2368

2369
2370
2371
2372
2373
2374
2375
2376
2377
2378







+
-
+
+
+








	return value;
}

- (double)doubleValue
{
	void *pool = objc_autoreleasePoolPush();
	OFString *decimalPoint = [OFSystemInfo decimalPoint];
	const char *UTF8String = [self UTF8String];
	const char *UTF8String = [[self
	    stringByReplacingOccurrencesOfString: @"."
				      withString: decimalPoint] UTF8String];
	char *endPointer = NULL;
	double value;

	while (*UTF8String == ' ' || *UTF8String == '\t' ||
	    *UTF8String == '\n' || *UTF8String == '\r' || *UTF8String == '\f')
		UTF8String++;

Modified src/OFSystemInfo.h from [b6d210b0c4] to [0c8db08aa1].

47
48
49
50
51
52
53







54
55
56
57
58
59
60
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67







+
+
+
+
+
+
+







 * This is useful to encode strings correctly for passing them to operating
 * system calls.
 *
 * @return The native 8-bit string encoding of the operating system
 */
+ (of_string_encoding_t)native8BitEncoding;

/*!
 * @brief Returns the decimal point in the system's locale.
 *
 * @return The decimal point in the system's locale
 */
+ (OFString*)decimalPoint;

/*!
 * @brief Returns the path where user data for the application can be stored.
 *
 * On Unix systems, this adheres to the XDG Base Directory specification.@n
 * On Mac OS X and iOS, it uses the `NSApplicationSupportDirectory` directory.@n
 * On Windows, it uses the `APPDATA` environment variable.@n
 * On Haiku, it uses the `B_USER_SETTINGS_DIRECTORY` directory.

Modified src/OFSystemInfo.m from [7a2395920f] to [c6d392a88f].

20
21
22
23
24
25
26

27
28
29
30
31
32
33
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34







+








/* Work around __block being used by glibc */
#include <stdlib.h>	/* include any libc header to get the libc defines */
#ifdef __GLIBC__
# undef __USE_XOPEN
#endif

#include <locale.h>
#include <unistd.h>

#include "platform.h"

#ifdef OF_MAC_OS_X
# include <sys/sysctl.h>
#endif
144
145
146
147
148
149
150





151
152
153
154
155
156
157
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163







+
+
+
+
+







}

+ (of_string_encoding_t)native8BitEncoding
{
	/* FIXME */
	return OF_STRING_ENCODING_UTF_8;
}

+ (OFString*)decimalPoint
{
	return [OFString stringWithUTF8String: localeconv()->decimal_point];
}

+ (OFString*)userDataPath
{
#if defined(OF_MAC_OS_X) || defined(OF_IOS)
	void *pool = objc_autoreleasePoolPush();
	char pathC[PATH_MAX];
	OFMutableString *path;

Modified src/of_asprintf.m from [bff5bd9808] to [91cd8b629a].

22
23
24
25
26
27
28

29
30
31
32
33
34
35
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36







+







#include <stdarg.h>
#include <stdbool.h>
#include <wchar.h>

#include <sys/types.h>

#import "OFString.h"
#import "OFSystemInfo.h"

#define MAX_SUBFORMAT_LEN 64

#ifndef HAVE_ASPRINTF
/*
 * (v)asprintf might be declared, but HAVE_ASPRINTF not defined because
 * configure determined it is broken. In this case, we must make sure there is
62
63
64
65
66
67
68

69
70
71
72
73
74
75
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77







+







		LENGTH_MODIFIER_L,
		LENGTH_MODIFIER_LL,
		LENGTH_MODIFIER_J,
		LENGTH_MODIFIER_Z,
		LENGTH_MODIFIER_T,
		LENGTH_MODIFIER_CAPITAL_L
	} lengthModifier;
	bool useLocale;
};

#ifndef HAVE_ASPRINTF
static int
vasprintf(char **string, const char *format, va_list arguments)
{
	int length;
161
162
163
164
165
166
167




168
169
170
171
172
173
174
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180







+
+
+
+







	case ' ':
	case '#':
	case '0':
		if (!appendSubformat(ctx, ctx->format + ctx->i, 1))
			return false;

		break;
	case ',':
		/* ObjFW extension: Use decimal point from locale */
		ctx->useLocale = true;
		break;
	default:
		ctx->state = STATE_FORMAT_FIELD_WIDTH;
		ctx->i--;

		break;
	}

497
498
499
500
501
502
503





























504
505
506
507
508
509
510
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







		case LENGTH_MODIFIER_CAPITAL_L:
			tmpLen = asprintf(&tmp, ctx->subformat,
			    va_arg(ctx->arguments, long double));
			break;
		default:
			return false;
		}

		/*
		 * Ugly hack to undo locale, as there is nothing such as
		 * asprintf_l in POSIX.
		 */
		if (!ctx->useLocale) {
			void *pool = objc_autoreleasePoolPush();
			char *tmp2;

			@try {
				OFMutableString *tmpStr = [OFMutableString
				    stringWithUTF8String: tmp
						  length: tmpLen];
				OFString *decimalPoint =
				    [OFSystemInfo decimalPoint];
				[tmpStr replaceOccurrencesOfString: decimalPoint
							withString: @"."];
				if ([tmpStr UTF8StringLength] > INT_MAX)
					return false;
				tmpLen = (int)[tmpStr UTF8StringLength];
				tmp2 = malloc(tmpLen);
				memcpy(tmp2, [tmpStr UTF8String], tmpLen);
			} @finally {
				free(tmp);
				objc_autoreleasePoolPop(pool);
			}

			tmp = tmp2;
		}

		break;
	case 'c':
		switch (ctx->lengthModifier) {
		case LENGTH_MODIFIER_NONE:
			tmpLen = asprintf(&tmp, ctx->subformat,
			    va_arg(ctx->arguments, int));
608
609
610
611
612
613
614

615
616
617
618
619
620
621
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657







+








		free(tmp);
	}

	memset(ctx->subformat, 0, MAX_SUBFORMAT_LEN);
	ctx->subformatLen = 0;
	ctx->lengthModifier = LENGTH_MODIFIER_NONE;
	ctx->useLocale = false;

	ctx->last = ctx->i + 1;
	ctx->state = STATE_STRING;

	return true;
}

637
638
639
640
641
642
643

644
645
646
647
648
649
650
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687







+







	memset(ctx.subformat, 0, MAX_SUBFORMAT_LEN + 1);
	ctx.subformatLen = 0;
	va_copy(ctx.arguments, arguments);
	ctx.bufferLen = 0;
	ctx.last = 0;
	ctx.state = STATE_STRING;
	ctx.lengthModifier = LENGTH_MODIFIER_NONE;
	ctx.useLocale = false;

	if ((ctx.buffer = malloc(1)) == NULL)
		return -1;

	for (ctx.i = 0; ctx.i < ctx.formatLen; ctx.i++) {
		if (!states[ctx.state](&ctx)) {
			free(ctx.buffer);

Modified tests/OFStringTests.m from [461bffb20c] to [cd4f1dfd47].

520
521
522
523
524
525
526
527
528
529
530

531
532
533
534
535
536
537

538
539
540
541
542
543
544
520
521
522
523
524
525
526


527

528
529
530
531


532

533
534
535
536
537
538
539
540







-
-

-
+



-
-

-
+







	    OFInvalidFormatException, [@"0x" hexadecimalValue])
	EXPECT_EXCEPTION(@"Detect invalid chars in -[hexadecimalValue] #3",
	    OFInvalidFormatException, [@"$" hexadecimalValue])
	EXPECT_EXCEPTION(@"Detect invalid chars in -[hexadecimalValue] #4",
	    OFInvalidFormatException, [@"$ " hexadecimalValue])

	EXPECT_EXCEPTION(@"Detect invalid chars in -[floatValue] #1",
	    OFInvalidFormatException, [@"0,0" floatValue])
	EXPECT_EXCEPTION(@"Detect invalid chars in -[floatValue] #2",
	    OFInvalidFormatException, [@"0.0a" floatValue])
	EXPECT_EXCEPTION(@"Detect invalid chars in -[floatValue] #3",
	EXPECT_EXCEPTION(@"Detect invalid chars in -[floatValue] #2",
	    OFInvalidFormatException, [@"0 0" floatValue])

	EXPECT_EXCEPTION(@"Detect invalid chars in -[doubleValue] #1",
	    OFInvalidFormatException, [@"0,0" floatValue])
	EXPECT_EXCEPTION(@"Detect invalid chars in -[doubleValue] #2",
	    OFInvalidFormatException, [@"0.0a" floatValue])
	EXPECT_EXCEPTION(@"Detect invalid chars in -[doubleValue] #3",
	EXPECT_EXCEPTION(@"Detect invalid chars in -[doubleValue] #2",
	    OFInvalidFormatException, [@"0 0" floatValue])

	EXPECT_EXCEPTION(@"Detect out of range in -[decimalValue]",
	    OFOutOfRangeException,
	    [@"12345678901234567890123456789012345678901234567890"
	     @"12345678901234567890123456789012345678901234567890"
	    decimalValue])

Modified utils/ofhttp/OFHTTP.m from [676c6715db] to [3ef2bcb62b].

709
710
711
712
713
714
715
716

717
718
719
720

721
722
723
724

725
726
727
728
729
730
731
709
710
711
712
713
714
715

716
717
718
719

720
721
722
723

724
725
726
727
728
729
730
731







-
+



-
+



-
+







	if (!_quiet) {
		if (type == nil)
			type = @"unknown";

		if (_length >= 0) {
			if (_resumedFrom + _length >= GIBIBYTE)
				lengthString = [OFString stringWithFormat:
				    @"%.2f GiB",
				    @"%,.2f GiB",
				    (float)(_resumedFrom + _length) / GIBIBYTE];
			else if (_resumedFrom + _length >= MEBIBYTE)
				lengthString = [OFString stringWithFormat:
				    @"%.2f MiB",
				    @"%,.2f MiB",
				    (float)(_resumedFrom + _length) / MEBIBYTE];
			else if (_resumedFrom + _length >= KIBIBYTE)
				lengthString = [OFString stringWithFormat:
				    @"%.2f KiB",
				    @"%,.2f KiB",
				    (float)(_resumedFrom + _length) / KIBIBYTE];
			else
				lengthString = [OFString stringWithFormat:
				    @"%jd bytes", _resumedFrom + _length];
		} else
			lengthString = @"unknown";

Modified utils/ofhttp/ProgressBar.m from [dd50353974] to [b2906f802b].

123
124
125
126
127
128
129
130

131
132
133
134
135
136
137
138
139
140
141
142

143
144
145
146
147
148
149

150
151

152
153

154
155

156
157
158
159
160
161
162

163
164
165
166

167
168
169
170

171
172
173
174
175
176
177
178
179
180
181

182
183

184
185

186
187

188
189
190
191
192
193
194
123
124
125
126
127
128
129

130
131
132
133
134
135
136
137
138
139
140
141

142
143
144
145
146
147
148

149
150

151
152

153
154

155
156
157
158
159
160
161

162
163
164
165

166
167
168
169

170
171
172
173
174
175
176
177
178
179
180

181
182

183
184

185
186

187
188
189
190
191
192
193
194







-
+











-
+






-
+

-
+

-
+

-
+






-
+



-
+



-
+










-
+

-
+

-
+

-
+







		else
			[of_stdout writeString: @" "];

		for (size_t i = 0; i < barWidth - (size_t)bars - 1; i++)
			[of_stdout writeString: @" "];
	}

	[of_stdout writeFormat: @"▏ %6.2f%% ", percent];
	[of_stdout writeFormat: @"▏ %,6.2f%% ", percent];

	if (percent == 100) {
		double timeInterval = -[_startDate timeIntervalSinceNow];

		_BPS = (float)_received / (float)timeInterval;
		_ETA = timeInterval;
	}

	if (isinf(_ETA))
		[of_stdout writeString: @"--:--:-- "];
	else if (_ETA >= 99 * 3600)
		[of_stdout writeFormat: @"%4.2f d ", _ETA / (24 * 3600)];
		[of_stdout writeFormat: @"%,4.2f d ", _ETA / (24 * 3600)];
	else
		[of_stdout writeFormat: @"%2u:%02u:%02u ",
		    (uint8_t)(_ETA / 3600), (uint8_t)(_ETA / 60) % 60,
		    (uint8_t)_ETA % 60];

	if (_BPS >= GIBIBYTE)
		[of_stdout writeFormat: @"%7.2f GiB/s", _BPS / GIBIBYTE];
		[of_stdout writeFormat: @"%,7.2f GiB/s", _BPS / GIBIBYTE];
	else if (_BPS >= MEBIBYTE)
		[of_stdout writeFormat: @"%7.2f MiB/s", _BPS / MEBIBYTE];
		[of_stdout writeFormat: @"%,7.2f MiB/s", _BPS / MEBIBYTE];
	else if (_BPS >= KIBIBYTE)
		[of_stdout writeFormat: @"%7.2f KiB/s", _BPS / KIBIBYTE];
		[of_stdout writeFormat: @"%,7.2f KiB/s", _BPS / KIBIBYTE];
	else
		[of_stdout writeFormat: @"%7.2f B/s  ", _BPS];
		[of_stdout writeFormat: @"%,7.2f B/s  ", _BPS];
}

- (void)_drawReceived
{
	if (_resumedFrom + _received >= GIBIBYTE)
		[of_stdout writeFormat:
		    @"\r  %7.2f GiB ",
		    @"\r  %,7.2f GiB ",
		    (float)(_resumedFrom + _received) / GIBIBYTE];
	else if (_resumedFrom + _received >= MEBIBYTE)
		[of_stdout writeFormat:
		    @"\r  %7.2f MiB ",
		    @"\r  %,7.2f MiB ",
		    (float)(_resumedFrom + _received) / MEBIBYTE];
	else if (_resumedFrom + _received >= KIBIBYTE)
		[of_stdout writeFormat:
		    @"\r  %7.2f KiB ",
		    @"\r  %,7.2f KiB ",
		    (float)(_resumedFrom + _received) / KIBIBYTE];
	else
		[of_stdout writeFormat:
		    @"\r  %jd bytes ", _resumedFrom + _received];

	if (_stopped)
		_BPS = (float)_received /
		    -(float)[_startDate timeIntervalSinceNow];

	if (_BPS >= GIBIBYTE)
		[of_stdout writeFormat: @"%7.2f GiB/s", _BPS / GIBIBYTE];
		[of_stdout writeFormat: @"%,7.2f GiB/s", _BPS / GIBIBYTE];
	else if (_BPS >= MEBIBYTE)
		[of_stdout writeFormat: @"%7.2f MiB/s", _BPS / MEBIBYTE];
		[of_stdout writeFormat: @"%,7.2f MiB/s", _BPS / MEBIBYTE];
	else if (_BPS >= KIBIBYTE)
		[of_stdout writeFormat: @"%7.2f KiB/s", _BPS / KIBIBYTE];
		[of_stdout writeFormat: @"%,7.2f KiB/s", _BPS / KIBIBYTE];
	else
		[of_stdout writeFormat: @"%7.2f B/s  ", _BPS];
		[of_stdout writeFormat: @"%,7.2f B/s  ", _BPS];
}

- (void)draw
{
	if (_length > 0)
		[self _drawProgress];
	else