-
Notifications
You must be signed in to change notification settings - Fork 9
Improve .strings handling: unquoted strings, inline comments, and merge prefixing
#741
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
199bb5a
eee684e
b8ce452
07e29a5
9de2a6c
535c167
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -56,15 +56,20 @@ def self.find_duplicated_keys(params) | |||||
| language = File.basename(File.dirname(file), '.lproj') | ||||||
| path = File.join(params[:input_dir], file) | ||||||
|
|
||||||
| file_type = Fastlane::Helper::Ios::L10nHelper.strings_file_type(path: path) | ||||||
| if file_type == :text | ||||||
| duplicates = Fastlane::Helper::Ios::StringsFileValidationHelper.find_duplicated_keys(file: path) | ||||||
| duplicate_keys[language] = duplicates.map { |key, value| "`#{key}` was found at multiple lines: #{value.join(', ')}" } unless duplicates.empty? | ||||||
| else | ||||||
| status, payload = Fastlane::Helper::Ios::StringsFileValidationHelper.scan_for_duplicate_keys(file: path) | ||||||
| case status | ||||||
| when :scanned | ||||||
| duplicate_keys[language] = payload.map { |key, value| "`#{key}` was found at multiple lines: #{value.join(', ')}" } unless payload.empty? | ||||||
| when :unsupported_format | ||||||
| UI.important <<~WRONG_FORMAT | ||||||
| File `#{path}` is in #{file_type} format, while finding duplicate keys only make sense on files that are in ASCII-plist format. | ||||||
| Since your files are in #{file_type} format, you should probably disable the `check_duplicate_keys` option from this `#{action_name}` call. | ||||||
| File `#{path}` is in #{payload} format, while finding duplicate keys only make sense on files that are in ASCII-plist format. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick, even though it was in the previous version "only makes sense" is a bit condescending IMHO.
Suggested change
|
||||||
| Since your files are in #{payload} format, you should probably disable the `check_duplicate_keys` option from this `#{action_name}` call. | ||||||
| WRONG_FORMAT | ||||||
| when :unscannable | ||||||
| UI.important <<~UNSCANNABLE | ||||||
| Could not check `#{path}` for duplicate keys: #{payload.strip} | ||||||
| The file parses as a property list but isn't a flat `.strings` file the duplicate-key scanner understands, so duplicate detection was skipped for it. | ||||||
| UNSCANNABLE | ||||||
| end | ||||||
| end | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,18 +15,24 @@ class L10nHelper | |
| # Returns the type of a `.strings` file (XML, binary or ASCII) | ||
| # | ||
| # @param [String] path The path to the `.strings` file to check | ||
| # @param [Boolean] assume_valid Skip the `plutil -lint` validity check when the caller has already | ||
| # confirmed the file parses (e.g. via `read_strings_file_as_hash`), avoiding a redundant | ||
| # `plutil` invocation. Only the format detection (`file`) then runs. | ||
| # @return [Symbol] The file format used by the `.strings` file. Can be one of: | ||
| # - `:text` for the ASCII-plist file format (containing typical `"key" = "value";` lines) | ||
| # - `:xml` for XML plist file format (can be used if machine-generated, especially since there's no official way/tool to generate the ASCII-plist file format as output) | ||
| # - `:binary` for binary plist file format (usually only true for `.strings` files converted by Xcode at compile time and included in the final `.app`/`.ipa`) | ||
| # - `nil` if the file does not exist or is neither of those format (e.g. not a `.strings` file at all) | ||
| # | ||
| def self.strings_file_type(path:) | ||
| def self.strings_file_type(path:, assume_valid: false) | ||
| return :text if File.empty?(path) # If completely empty file, consider it as a valid `.strings` files in textual format | ||
|
|
||
| # Start by checking it seems like a valid property-list file (and not e.g. an image or plain text file) | ||
| _, status = Open3.capture2('/usr/bin/plutil', '-lint', path) | ||
| return nil unless status.success? | ||
| # Start by checking it seems like a valid property-list file (and not e.g. an image or plain text file). | ||
| # A caller that has already parsed the file can skip this redundant check via `assume_valid: true`. | ||
| unless assume_valid | ||
| _, status = Open3.capture2('/usr/bin/plutil', '-lint', path) | ||
| return nil unless status.success? | ||
| end | ||
|
|
||
| # If it is a valid property-list file, determine the actual format used | ||
| format_desc, status = Open3.capture2('/usr/bin/file', path) | ||
|
|
@@ -96,9 +102,17 @@ def self.merge_strings(paths:, output_path:) | |
| # Read line-by-line to reduce memory footprint during content copy | ||
| read_utf8_lines(input_file).each do |line| | ||
| unless prefix.nil? || prefix.empty? | ||
| # The `/u` modifier on the RegExps is to make them UTF-8 | ||
| # The `/u` modifier on the RegExps is to make them UTF-8. | ||
| # The unquoted-key character set below matches what `plutil` accepts (alphanumerics plus | ||
| # `_ . - $ : /`, as in `StringsFileValidationHelper::UNQUOTED_STRING_CHARACTER`) so that keys | ||
| # containing `. - $ : /` (e.g. reverse-DNS keys) are prefixed like any other. | ||
| line.gsub!(/^(\s*")/u, "\\1#{prefix}") # Lines starting with a quote are considered to be start of a key; add prefix right after the quote | ||
| line.gsub!(/^(\s*)([A-Z0-9_]+)(\s*=\s*")/ui, "\\1\"#{prefix}\\2\"\\3") # Lines starting with an identifier followed by a '=' are considered to be an unquoted key (typical in InfoPlist.strings files for example) | ||
| # Lines starting with an unquoted key followed by a `=` and a *quoted* value (typical in `InfoPlist.strings`). | ||
| line.gsub!(%r{^(\s*)([a-zA-Z0-9_.$:/-]+)(\s*=\s*")}u, "\\1\"#{prefix}\\2\"\\3") | ||
| # Lines starting with an unquoted key followed by a `=` and an *unquoted* value (e.g. `CFBundleName = WordPress;`). | ||
| # The value is required to be a single unquoted token ending in `;` — an unquoted string can't contain spaces, so | ||
| # this won't mistake comment prose like `and = a red herring for when…` (spaces in the "value") for a key/value pair. | ||
| line.gsub!(%r{^(\s*)([a-zA-Z0-9_.$:/-]+)(\s*=\s*)([a-zA-Z0-9_.$:/-]+\s*;)}u, "\\1\"#{prefix}\\2\"\\3\\4") | ||
| end | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My AI noticed that Could this logic handle the same inter-token comment placements that For example, I added failing specs in #742 to pin the gap. AI suggests: A tokenizer-backed rewrite would be ideal, but a comment-aware interim rewrite would also close the regression. |
||
| tmp_file.write(line) | ||
| end | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice appraoch.