mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) Add PHONE_FORMAT function using the phonenumberslight library
Summary: Add phonenumberslite-8.12.57 to requirements Implement PHONE_FORMAT function. Test Plan: Added doctest test cases Reviewers: jarek Reviewed By: jarek Subscribers: jarek Differential Revision: https://phab.getgrist.com/D3665
This commit is contained in:
		
							parent
							
								
									62792329c3
								
							
						
					
					
						commit
						0a8ce2178a
					
				| @ -5,8 +5,9 @@ import numbers | |||||||
| import re | import re | ||||||
| 
 | 
 | ||||||
| import dateutil.parser | import dateutil.parser | ||||||
|  | import phonenumbers | ||||||
| import six | import six | ||||||
| from six import unichr | from six import unichr         # pylint: disable=redefined-builtin | ||||||
| from six.moves import xrange | from six.moves import xrange | ||||||
| 
 | 
 | ||||||
| from usertypes import AltText  # pylint: disable=import-error | from usertypes import AltText  # pylint: disable=import-error | ||||||
| @ -77,7 +78,7 @@ def CONCATENATE(string, *more_strings): | |||||||
|   >>> assert CONCATENATE(2,  " crème ",  "brûlée") == u'2 crème brûlée' |   >>> assert CONCATENATE(2,  " crème ",  "brûlée") == u'2 crème brûlée' | ||||||
|   """ |   """ | ||||||
|   return u''.join( |   return u''.join( | ||||||
|     val.decode('utf8') if isinstance(val, six.binary_type) else |     val.decode('utf8') if isinstance(val, six.binary_type) else   # pylint:disable=no-member | ||||||
|     six.text_type(val) |     six.text_type(val) | ||||||
|     for val in (string,) + more_strings |     for val in (string,) + more_strings | ||||||
|   ) |   ) | ||||||
| @ -100,7 +101,6 @@ def CONCAT(string, *more_strings): | |||||||
|   """ |   """ | ||||||
|   return CONCATENATE(string, *more_strings) |   return CONCATENATE(string, *more_strings) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def DOLLAR(number, decimals=2): | def DOLLAR(number, decimals=2): | ||||||
|   """ |   """ | ||||||
|   Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. |   Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. | ||||||
| @ -267,6 +267,103 @@ def MID(text, start_num, num_chars): | |||||||
|   return text[start_num - 1 : start_num - 1 + num_chars] |   return text[start_num - 1 : start_num - 1 + num_chars] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | output_formats = { | ||||||
|  |     "+":        phonenumbers.PhoneNumberFormat.INTERNATIONAL, | ||||||
|  |     "INTL":     phonenumbers.PhoneNumberFormat.INTERNATIONAL, | ||||||
|  |     "#":        phonenumbers.PhoneNumberFormat.NATIONAL, | ||||||
|  |     "NATL":     phonenumbers.PhoneNumberFormat.NATIONAL, | ||||||
|  |     "*":        phonenumbers.PhoneNumberFormat.E164, | ||||||
|  |     "E164":     phonenumbers.PhoneNumberFormat.E164, | ||||||
|  |     "tel":      phonenumbers.PhoneNumberFormat.RFC3966, | ||||||
|  |     "RFC3966":  phonenumbers.PhoneNumberFormat.RFC3966, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | def PHONE_FORMAT(value, country=None, format=None):  # pylint: disable=redefined-builtin | ||||||
|  |   """ | ||||||
|  |   Formats a phone number. | ||||||
|  | 
 | ||||||
|  |   With no optional arguments, the number must start with "+" and the international dialing prefix, | ||||||
|  |   and will be formatted as an international number, e.g. `+12345678901` becomes `+1 234-567-8901`. | ||||||
|  | 
 | ||||||
|  |   The `country` argument allows specifying a 2-letter country code (e.g. "US" or "GB") for | ||||||
|  |   interpreting phone numbers that don't start with "+". E.g. `PHONE_FORMAT('2025555555', 'US')` | ||||||
|  |   would be seen as a US number and formatted as "(202) 555-5555". Phone numbers that start with | ||||||
|  |   "+" ignore `country`. E.g. `PHONE_FORMAT('+33555555555', 'US')` is a French number because '+33' | ||||||
|  |   is the international prefix for France. | ||||||
|  | 
 | ||||||
|  |   The `format` argument specifies the output format, according to this table: | ||||||
|  | 
 | ||||||
|  |     - `"#"` or `"NATL"` (default) - use the national format, without the international dialing | ||||||
|  |       prefix, when possible. E.g. `(234) 567-8901` for "US", or `02 34 56 78 90` for "FR". If | ||||||
|  |       `country` is omitted, or the number does not correspond to the given country, the | ||||||
|  |       international format is used instead. | ||||||
|  |     - `"+"` or `"INTL"` - international format, e.g. `+1 234-567-8901` or | ||||||
|  |       `+33 2 34 56 78 90`. | ||||||
|  |     - `"*"` or `"E164"` - E164 format, like international but with no separators, e.g. | ||||||
|  |       `+12345678901`. | ||||||
|  |     - `"tel"` or `"RFC3966"` - format suitable to use as a [hyperlink](col-types.md#hyperlinks), | ||||||
|  |       e.g. 'tel:+1-234-567-8901'. | ||||||
|  | 
 | ||||||
|  |   When specifying the `format` argument, you may omit the `country` argument. I.e. | ||||||
|  |   `PHONE_FORMAT(value, "tel")` is equivalent to `PHONE_FORMAT(value, None, "tel")`. | ||||||
|  | 
 | ||||||
|  |   For more details, see the [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) | ||||||
|  |   Python library, which underlies this function. | ||||||
|  | 
 | ||||||
|  |   >>> PHONE_FORMAT("+12345678901") | ||||||
|  |   u'+1 234-567-8901' | ||||||
|  |   >>> PHONE_FORMAT("2345678901", "US") | ||||||
|  |   u'(234) 567-8901' | ||||||
|  |   >>> PHONE_FORMAT("2345678901", "GB") | ||||||
|  |   u'023 4567 8901' | ||||||
|  |   >>> PHONE_FORMAT("2345678901", "GB", "+") | ||||||
|  |   u'+44 23 4567 8901' | ||||||
|  |   >>> PHONE_FORMAT("+442345678901", "GB") | ||||||
|  |   u'023 4567 8901' | ||||||
|  |   >>> PHONE_FORMAT("+12345678901", "GB") | ||||||
|  |   u'+1 234-567-8901' | ||||||
|  |   >>> PHONE_FORMAT("(234) 567-8901")    # doctest: +IGNORE_EXCEPTION_DETAIL | ||||||
|  |   Traceback (most recent call last): | ||||||
|  |   ... | ||||||
|  |   NumberParseException: (0) Missing or invalid default region. | ||||||
|  |   >>> PHONE_FORMAT("(234)567 89-01", "US", "tel") | ||||||
|  |   u'tel:+1-234-567-8901' | ||||||
|  |   >>> PHONE_FORMAT("2/3456/7890", "FR", '#') | ||||||
|  |   u'02 34 56 78 90' | ||||||
|  |   >>> PHONE_FORMAT("+33234567890", '#') | ||||||
|  |   u'+33 2 34 56 78 90' | ||||||
|  |   >>> PHONE_FORMAT("+33234567890", 'tel') | ||||||
|  |   u'tel:+33-2-34-56-78-90' | ||||||
|  |   >>> PHONE_FORMAT("tel:+1-234-567-8901", country="US", format="*") | ||||||
|  |   u'+12345678901' | ||||||
|  |   """ | ||||||
|  |   if not value: | ||||||
|  |     return value | ||||||
|  |   if format is None and country in output_formats: | ||||||
|  |     format = country | ||||||
|  |     country = None | ||||||
|  |   parsed = phonenumbers.parse(str(value), country) | ||||||
|  |   out_fmt = output_formats.get(format or "#") | ||||||
|  |   if out_fmt is None: | ||||||
|  |     raise ValueError("Unrecognized phone format; try +, INTL, #, NATL, *, E164, tel, or RFC3966") | ||||||
|  | 
 | ||||||
|  |   if out_fmt == phonenumbers.PhoneNumberFormat.NATIONAL and not country: | ||||||
|  |     # With no country, we lose info in NATIONAL format (because numbers must be specified with an | ||||||
|  |     # international prefix, and the output would discard it). Use INTERNATIONAL instead. | ||||||
|  |     out_fmt = phonenumbers.PhoneNumberFormat.INTERNATIONAL | ||||||
|  | 
 | ||||||
|  |   result = phonenumbers.format_number(parsed, out_fmt) | ||||||
|  | 
 | ||||||
|  |   # If using a national format with a country, check that we don't garble numbers with a different | ||||||
|  |   # international prefix. If so, use an international format. E.g. for | ||||||
|  |   # PHONE_FORMAT('+12345678901', 'FR'), the output should include the US dialing prefix. | ||||||
|  |   if (out_fmt == phonenumbers.PhoneNumberFormat.NATIONAL and country and | ||||||
|  |       phonenumbers.parse(result, country) != parsed): | ||||||
|  |     result = phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.INTERNATIONAL) | ||||||
|  | 
 | ||||||
|  |   return result | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def PROPER(text): | def PROPER(text): | ||||||
|   """ |   """ | ||||||
|   Capitalizes each word in a specified string. It converts the first letter of each word to |   Capitalizes each word in a specified string. It converts the first letter of each word to | ||||||
| @ -541,7 +638,7 @@ def T(value): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @unimplemented | @unimplemented | ||||||
| def TEXT(number, format_type): | def TEXT(number, format_type):    # pylint: disable=unused-argument | ||||||
|   """ |   """ | ||||||
|   Converts a number into text according to a specified format. It is not yet implemented in |   Converts a number into text according to a specified format. It is not yet implemented in | ||||||
|   Grist. |   Grist. | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ chardet==4.0.0 | |||||||
| enum34==1.1.10 | enum34==1.1.10 | ||||||
| iso8601==0.1.12 | iso8601==0.1.12 | ||||||
| lazy_object_proxy==1.6.0 | lazy_object_proxy==1.6.0 | ||||||
|  | phonenumberslite==8.12.57 | ||||||
| python_dateutil==2.8.2 | python_dateutil==2.8.2 | ||||||
| roman==2.0.0 | roman==2.0.0 | ||||||
| singledispatch==3.6.2 | singledispatch==3.6.2 | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ chardet==4.0.0 | |||||||
| enum34==1.1.10 | enum34==1.1.10 | ||||||
| iso8601==0.1.12 | iso8601==0.1.12 | ||||||
| lazy_object_proxy==1.6.0 | lazy_object_proxy==1.6.0 | ||||||
|  | phonenumberslite==8.12.57 | ||||||
| python_dateutil==2.8.2 | python_dateutil==2.8.2 | ||||||
| roman==2.0.0 | roman==2.0.0 | ||||||
| singledispatch==3.6.2 | singledispatch==3.6.2 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user