Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package ex_doc for openSUSE:Factory checked 
in at 2026-09-08 16:59:00
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ex_doc (Old)
 and      /work/SRC/openSUSE:Factory/.ex_doc.new.1265 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "ex_doc"

Tue Sep  8 16:59:00 2026 rev:2 rq:1376369 version:0.40.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/ex_doc/ex_doc.changes    2025-06-02 
22:02:00.428820797 +0200
+++ /work/SRC/openSUSE:Factory/.ex_doc.new.1265/ex_doc.changes  2026-09-08 
17:02:11.783199449 +0200
@@ -1,0 +2,134 @@
+Wed Aug 05 23:15:57 UTC 2026 - Simon Lees <[email protected]>
+
+- Update to version 0.40.3:
+  * Fix record autolink - and bump CI (#2240)
+  * Update makeup_erlang version (#2236)
+  * Raise on extras that conflict with reserved filenames
+  * Prevent #search selector from impacting user content
+  * Fix inline code font size in headings (#2220)
+  * Raise on duplicate entries in files in :extras (#2226)
+  * Fix false positive warning when linking to asset files (#2225)
+  * Fix markdown backend code fence language and opaque type display (#2215)
+  * Skip HTML comments when computing synopsis (#2214)
+  * Bump versions in release CI for prebuilt archives (#2213)
+  * Enable use of admonition blocks in cheatsheets (#2210)
+  * Bump minimatch from 3.1.2 to 3.1.5 in /assets (#2205)
+  * Make minor doc language fixes in source_url/rel section
+  * Improve docstring headings, including admonition blocks (#2202)
+  * Tweak ExDoc module doc text (#2203)
+  * The code for :meta seems to have dissapeared, so remove it from docs 
(#2200)
+  * epub: fix links to extra documents (#2199)
+  * epub: void elements, such `wbr`, must be terminated by the matching 
end-tag (#2198)
+  * markdown: conditionally copy assets to target dir (#2195)
+  * html: improve consistency across private helpers (#2196)
+  * Replace Karma with Vitest for JavaScript testing (#2197)
+  * use File.write! for everything (as in the html formatter)
+  * group resources just one time
+  * based on llmstxt.org the description should be a blockquote
+  * Add rel="nofollow" to external links in HTML output (#2193)
+  * Turn whitespace minification back on
+  * Improve distinction between docstring headings (H2-H4)
+  * Change headings' levels to match their context
+  * Fix Summary Types heading size
+  * Improve word-breaking of module names and sizing of main page titles 
(#2190)
+  * Include a description in llms.txt
+  * Do not include source links in markdown files
+  * Generate link to view markdown source
+  * Generate llms.txt instead of index.md
+  * Start marking formatter API public
+  * Reduce the places we pass markdown globally and extend Erlang coverage
+  * Make ExDoc module public
+  * Unify formatter return type
+  * Generate markdown before epub
+  * Have config as first argument in formatter functions
+  * Pass autolink structs around instead of options
+  * Make tests with global state sync
+  * Make source_beam a required argument
+  * Run mix format
+  * Move GroupMatcher to Config
+  * Simplify before_closing_*_tag callback handling
+  * Run retriever just once per formatter
+  * Rename markdown build file
+  * Rename to_string to to_html in DocAST
+  * Normalize formatter dispatch
+  * Split formatter configuration into its own module
+  * Remove unused fields from config
+  * Start unifying autolink rendering
+  * Simplify formatter API
+  * Remove filter_list and nodes_map
+  * Unify markdown and epub nav
+  * Clean up tests to rely less on integration
+  * Extract extras into their module for easier testing
+  * Simplify outputs and formatter
+  * Also default to markdown in the CLI
+  * Assert on types/specs rendered in markdown
+  * Keep specs and source_specs as distinct fields
+  * Simplify markdown template rendering
+  * Only discard unselectable for bash prompts, closes #2180
+  * Update esbuild and turn off whitespace minification (#2186)
+  * Support anchors on redirects (#2185)
+  * Prevent recent heading size changes from applying to docstring headings 
(#2184)
+  * Only include .html links in Swup
+  * Add Markdown formatter (#2148)
+  * Add the option to trim down the footer
+  * Ensure IDs rather than hrefs are stripped, closes #2175
+  * Show go to latest for prereleases, closes #2173
+  * Fix docs: Move `source_url` to `project` (#2172)
+  * Prevent fake italic in autocomplete text (#2168)
+  * Rename Search Hexdocs link to Go to package docs
+  * Better differentiate headings/sections (#2164)
+  * Document double-colon notation when autolink to Erlang entities (#2161)
+- Patches for reproducable builds in erlang
+  * fix-upstream-reprod-epub.patch
+  * fix-upstream-reprod-html.patch
+  * fix-upstream-reprod-utils.patch
+- Add ex_doc-rpmlintrc to suppress the noarch warning
+
+-------------------------------------------------------------------
+Wed Nov 05 12:02:38 UTC 2025 - Matwey Kornilov <[email protected]>
+
+- Update to version 0.39.1:
+  * Release v0.39.1
+  * Italics for autocompletion header to not confuse with results
+  * Fix admonition on small screens
+  * Improve box shadow, minimize search button space
+  * Release v0.39.0
+  * Search style
+  * Tags are a list of atoms, closes #2159
+  * Update GitHub organization for Ecto in README (#2158)
+  * Support specifying packages under search
+  * Validate extra fields
+  * Allow multiple search engines (#2153)
+  * Streamline autocompletion
+  * Remove preview feature
+  * Remove tabindex from tabpanels
+  * Add inline code to admonition block test page headings (#2152)
+  * Update assets
+  * Update admonitions; simplify code bg/bdr colors
+  * Include admonition type in test block headings
+  * refactor: extract minimal shared logic between HTML and EPUB formatters 
(#2147)
+  * Add .cheatmd to epub to avoid broken links
+  * Clean up epub tests
+  * Safe guard exdoc:autocomplete-limit meta access (#2142)
+  * Release v0.38.4
+  * Fix position os stale icon
+  * Escape attributes
+  * Update assets
+  * Increase spacing in bottom actions
+  * Release v0.38.3
+  * Bump tmp from 0.2.1 to 0.2.4 in /assets (#2139)
+  * Load discovered makeup apps for cli (#2138)
+  * Fix typos (#2137)
+  * Improve docs for :skip_undefined_reference_warnings_on (#2136)
+  * Ensure default groups for docs are sorted alphabetically (#2132)
+  * Display description text in docs groups (#2113)
+  * Set minimum elixir on the `README.md` to 1.15 (#2130)
+  * Update assets
+  * Allow configuring autocomplete limit, and default it to 10 instead of 8 
(#2129)
+  * Release v0.38.2
+  * Do not add fancy anchors around verbatim headers, closes #2128
+  * Update assets
+  * Move external link margin to the left (#2126)
+  * Fix typo in CHANGELOG.md (#2125)
+
+-------------------------------------------------------------------

Old:
----
  ex_doc-0.38.1.tar.xz

New:
----
  build.specials.obscpio
  ex_doc-0.40.3.tar.xz
  ex_doc-rpmlintrc
  fix-upstream-reprod-epub.patch
  fix-upstream-reprod-html.patch
  fix-upstream-reprod-utils.patch

----------(New B)----------
  New:- Patches for reproducable builds in erlang
  * fix-upstream-reprod-epub.patch
  * fix-upstream-reprod-html.patch
  New:  * fix-upstream-reprod-epub.patch
  * fix-upstream-reprod-html.patch
  * fix-upstream-reprod-utils.patch
  New:  * fix-upstream-reprod-html.patch
  * fix-upstream-reprod-utils.patch
- Add ex_doc-rpmlintrc to suppress the noarch warning
----------(New E)----------

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

Other differences:
------------------
++++++ ex_doc.spec ++++++
--- /var/tmp/diff_new_pack.0y0XYi/_old  2026-09-08 17:02:12.701237822 +0200
+++ /var/tmp/diff_new_pack.0y0XYi/_new  2026-09-08 17:02:12.703237905 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           ex_doc
-Version:        0.38.1
+Version:        0.40.3
 Release:        0
 Summary:        ExDoc produces HTML and online documentation for Elixir 
projects
 License:        Apache-2.0 AND MIT
@@ -25,8 +25,15 @@
 URL:            https://github.com/elixir-lang/ex_doc
 Source0:        %{name}-%{version}.tar.xz
 Source1:        vendor.tar.gz
+Source2:        ex_doc-rpmlintrc
+# Upstream fixes for reproducable builds - [email protected]
+# https://github.com/erlang/otp/issues/4417
+Patch0:         fix-upstream-reprod-epub.patch
+Patch1:         fix-upstream-reprod-html.patch
+Patch2:         fix-upstream-reprod-utils.patch
 BuildRequires:  elixir >= 1.15
 BuildRequires:  elixir-hex
+BuildRequires:  strip-nondeterminism >= 1.15
 Obsoletes:      elixir-ex_doc < %{version}
 Provides:       elixir-ex_doc = %{version}
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
@@ -39,7 +46,7 @@
 are looking for documentation for Elixir itself, check out Elixir's website.
 
 %prep
-%autosetup -a1 -v
+%autosetup -a1 -v -p1
 
 %build
 export LANG=en_US.UTF-8
@@ -50,6 +57,7 @@
 %install
 sed -i -e '1s|/usr/bin/env escript|/usr/bin/escript|' ex_doc
 install -D -m 0755 ex_doc %{buildroot}%{_bindir}/ex_doc
+strip-nondeterminism --timestamp=${SOURCE_DATE_EPOCH:-1494270000} 
--clamp-timestamp %{buildroot}%{_bindir}/ex_doc
 
 %files
 %defattr(-,root,root)

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.0y0XYi/_old  2026-09-08 17:02:12.754240037 +0200
+++ /var/tmp/diff_new_pack.0y0XYi/_new  2026-09-08 17:02:12.759240246 +0200
@@ -1,6 +1,6 @@
-mtime: 1748878486
-commit: f476efe93376c5693568f23856e90a7d136b92ddeb660a8ffa50e272161b132e
-url: https://src.opensuse.org/erlang/ex_doc.git
-revision: f476efe93376c5693568f23856e90a7d136b92ddeb660a8ffa50e272161b132e
+mtime: 1785974149
+commit: f70cc7fc184cc8a925357e5f35cb996abcd5f31968d270663113e40c1aa72af0
+url: https://src.opensuse.org/erlang/ex_doc
+revision: f70cc7fc184cc8a925357e5f35cb996abcd5f31968d270663113e40c1aa72af0
 projectscmsync: https://src.opensuse.org/erlang/_ObsPrj.git
 

++++++ _service ++++++
--- /var/tmp/diff_new_pack.0y0XYi/_old  2026-09-08 17:02:12.791241584 +0200
+++ /var/tmp/diff_new_pack.0y0XYi/_new  2026-09-08 17:02:12.795241751 +0200
@@ -2,7 +2,7 @@
        <service name="tar_scm" mode="manual">
                <param name="url">https://github.com/elixir-lang/ex_doc</param>
                <param name="versionformat">@PARENT_TAG@</param>
-               <param name="revision">v0.38.1</param>
+               <param name="revision">v0.40.3</param>
                <param name="scm">git</param>
                <param name="changesgenerate">enable</param>
                <param name="versionrewrite-pattern">v(.+)</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.0y0XYi/_old  2026-09-08 17:02:12.827243088 +0200
+++ /var/tmp/diff_new_pack.0y0XYi/_new  2026-09-08 17:02:12.838243548 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/elixir-lang/ex_doc</param>
-              <param 
name="changesrevision">b28eaa93d568d519f949a110687e048faad41bb6</param></service></servicedata>
+              <param 
name="changesrevision">07a6abc805e98247d16eed02a49f6970d0440760</param></service></servicedata>
 (No newline at EOF)
 

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-08-06 01:55:49.000000000 +0200
@@ -0,0 +1,2 @@
+.osc/
+ex_doc/

++++++ ex_doc-0.38.1.tar.xz -> ex_doc-0.40.3.tar.xz ++++++
++++ 23848 lines of diff (skipped)

++++++ ex_doc-rpmlintrc ++++++
# ex_doc package IS arch dependent
# See https://github.com/elixir-lang/elixir/issues/2785 for details
addFilter("no-binary")

++++++ fix-upstream-reprod-epub.patch ++++++
>From 23cbd237c9913945096837bab50e14887e1972ad Mon Sep 17 00:00:00 2001
From: "Bernhard M. Wiedemann" <[email protected]>
Date: Mon, 27 Jul 2026 23:07:28 +0200
Subject: [PATCH] epub: Honor SOURCE_DATE_EPOCH for dcterms:modified and
 dc:identifier (#2256)

---
 lib/ex_doc/formatter/epub.ex | 37 ++++++++++++++++++------------------
 1 file changed, 19 insertions(+), 18 deletions(-)

diff --git a/lib/ex_doc/formatter/epub.ex b/lib/ex_doc/formatter/epub.ex
index 64cafbf54..5fdd9257b 100644
--- a/lib/ex_doc/formatter/epub.ex
+++ b/lib/ex_doc/formatter/epub.ex
@@ -59,7 +59,7 @@ defmodule ExDoc.Formatter.EPUB do
   end
 
   defp generate_content(config, modules, tasks, extras, static_files) do
-    uuid = "urn:uuid:#{uuid4()}"
+    uuid = "urn:uuid:#{uuid(config)}"
     datetime = format_datetime()
 
     static_files =
@@ -137,7 +137,17 @@ defmodule ExDoc.Formatter.EPUB do
 
   # Helper to format Erlang datetime tuple
   defp format_datetime do
-    {{year, month, day}, {hour, min, sec}} = :calendar.universal_time()
+    # Use SOURCE_DATE_EPOCH instead of the current time when set, for
+    # reproducible builds
+    # (https://reproducible-builds.org/docs/source-date-epoch/).
+    {{year, month, day}, {hour, min, sec}} =
+      with epoch when is_binary(epoch) <- System.get_env("SOURCE_DATE_EPOCH"),
+           {seconds, ""} <- Integer.parse(epoch) do
+        :calendar.system_time_to_universal_time(seconds, :second)
+      else
+        _ -> :calendar.universal_time()
+      end
+
     list = [year, month, day, hour, min, sec]
 
     "~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ"
@@ -145,21 +155,12 @@ defmodule ExDoc.Formatter.EPUB do
     |> IO.iodata_to_binary()
   end
 
-  @two_power_16 65536
-  @two_power_32 4_294_967_296
-  @two_power_48 281_474_976_710_656
-
-  defp uuid4 do
-    Enum.map_join(
-      [
-        <<:rand.uniform(@two_power_32) - 1::32>>,
-        <<:rand.uniform(@two_power_16) - 1::16>>,
-        <<:rand.uniform(@two_power_16) - 1::16>>,
-        <<:rand.uniform(@two_power_16) - 1::16>>,
-        <<:rand.uniform(@two_power_48) - 1::48>>
-      ],
-      <<45>>,
-      &Base.encode16(&1, case: :lower)
-    )
+  # For reproducible builds, derive the identifier from the project name
+  # and version instead of random data
+  defp uuid(config) do
+    <<a::binary-size(4), b::binary-size(2), c::binary-size(2), 
d::binary-size(2),
+      e::binary-size(6)>> = :erlang.md5("#{config.project} #{config.version}")
+
+    Enum.map_join([a, b, c, d, e], <<45>>, &Base.encode16(&1, case: :lower))
   end
 end

++++++ fix-upstream-reprod-html.patch ++++++
>From a37128a2c334b35c1612eb4be9ff8a6c19115edf Mon Sep 17 00:00:00 2001
From: "Bernhard M. Wiedemann" <[email protected]>
Date: Mon, 27 Jul 2026 09:48:59 +0200
Subject: [PATCH] Use a fixed makeup group_prefix for reproducible HTML output
 (#2255)

makeup's lexers default to a random 10-digit group_prefix, generated
anew on every lexer invocation, so every data-group-id attribute in
highlighted code blocks differs between two builds of the same docs.

Group ids only need to be unique within a single code block: the
hover-highlighting JS (assets/js/makeup.js) resolves them through
parentElement, never across blocks. Passing a fixed prefix therefore
changes nothing functionally while making the generated HTML (and the
content-hashed search_data/sidebar file names derived from it)
reproducible.
---
 lib/ex_doc/doc_ast.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/ex_doc/doc_ast.ex b/lib/ex_doc/doc_ast.ex
index c7f7ece2d..94a7b65a3 100644
--- a/lib/ex_doc/doc_ast.ex
+++ b/lib/ex_doc/doc_ast.ex
@@ -246,7 +246,7 @@ defmodule ExDoc.DocAST do
             try do
               Makeup.highlight_inner_html(code,
                 lexer: lexer,
-                lexer_options: lexer_opts,
+                lexer_options: Keyword.put_new(lexer_opts, :group_prefix, 
"group"),
                 formatter_options: opts
               )
             rescue

++++++ fix-upstream-reprod-utils.patch ++++++
>From 5220946a5e918155ef38c2437b8bc8d392c06b9a Mon Sep 17 00:00:00 2001
From: "Bernhard M. Wiedemann" <[email protected]>
Date: Mon, 27 Jul 2026 23:42:49 +0200
Subject: [PATCH] Sort @standard_token_types for reproducible compilation (#75)

---
 lib/makeup/token/utils.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/makeup/token/utils.ex b/lib/makeup/token/utils.ex
index 17f3d25..2317590 100644
--- a/deps/makeup/lib/makeup/token/utils.ex
+++ b/deps/makeup/lib/makeup/token/utils.ex
@@ -95,7 +95,7 @@ defmodule Makeup.Token.Utils do
 
   @precedence Hierarchy.hierarchy_to_precedence(@hierarchy)
   @token_to_class_map Hierarchy.style_to_class_map(@hierarchy)
-  @standard_token_types Map.keys(@token_to_class_map)
+  @standard_token_types @token_to_class_map |> Map.keys() |> Enum.sort()
 
   def precedence do
     @precedence

++++++ vendor.tar.gz ++++++
Binary files old/deps/makeup_erlang/.hex and new/deps/makeup_erlang/.hex differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/deps/makeup_erlang/CHANGELOG.md 
new/deps/makeup_erlang/CHANGELOG.md
--- old/deps/makeup_erlang/CHANGELOG.md 1970-01-01 01:00:00.000000000 +0100
+++ new/deps/makeup_erlang/CHANGELOG.md 2026-08-06 01:16:07.000000000 +0200
@@ -0,0 +1,94 @@
+# Changelog
+
+## v1.1.0 (2026-05-08)
+
+### Added
+
+  * Support for OTP 27+ triple-, quadruple-, and quintuple-quoted strings,
+    including the matching sigil delimiters.
+  * Support for OTP 27 underscore separators in numeric literals
+    (`1_000_000`, `16#FF_FF`, `0.1_5e1_0`).
+  * Support for OTP 25/27 `?=` maybe-match operator as a single token.
+  * Support for OTP 29 native records, including the external
+    `#Module:Name{...}` / `#Module:Name.field` forms and variable-shape /
+    keyword record names (`#State{}`, `#case{}`, `#fun{}`).
+  * Distinct token for parameterised macro calls (`?FOO(args)` →
+    `:name_function`) versus parameterless macro references (`?FOO` →
+    `:name_constant`).
+  * `:string_escape` sub-tokens emitted inside plain double-quoted strings
+    (previously only emitted inside triple-quoted strings).
+  * Multi-character escape sequences (`$\xFF`, `$\x{1F600}`, `$\077`,
+    `$\^A`) in `$\...` character literals tokenised as a single escape.
+  * Underscore-prefixed identifiers (`_5`, `_X`, `_unused`) lex as a single
+    `:name` variable rather than `_` + integer/name.
+
+### Changed
+
+  * BIF list is now generated at compile time from `erl_internal:bif/2`,
+    keeping the recognised builtins in sync with the OTP version
+    `makeup_erlang` is compiled against. Adds 30+ post-OTP-19 BIFs and
+    fixes one stale typo. Closes #13.
+  * Reserved words written adjacent to `(` (e.g. `fun(X)`) are recovered
+    as `:keyword` instead of misclassified `:name_function`.
+  * Erlang shell prompts (`1> ...`) are detected after multi-line
+    whitespace blocks, not only after a single immediate `\n`.
+
+## v1.0.3
+
+  * Do not tag newlines before prompts as unselectable.
+  * LICENSE and CI housekeeping.
+
+## v1.0.2
+
+### Added
+
+  * Support for strict generators in comprehensions.
+  * Support for parallel comprehensions.
+  * Group handling for binaries.
+  * Elixir 1.18 and Erlang 27 in the CI matrix.
+  * Tests for list and binary comprehensions.
+
+## v1.0.1
+
+  * Parse tabs correctly (#29).
+
+## v1.0.0
+
+  * Properly deal with unicode characters in `$` character literals.
+  * Fix publishing and generation of docs.
+
+## v0.1.5
+
+  * Highlight errors in shell examples correctly.
+
+## v0.1.4
+
+  * Support for `maybe ... end` expressions.
+  * Support for triple-quoted strings and sigils.
+  * CI updates.
+
+## v0.1.3
+
+  * Support for the Erlang shell prompt (`1> ...`) (#24).
+  * Use the SPDX-conformant license name.
+  * Run `mix format` over the codebase.
+
+## v0.1.2
+
+  * Require Elixir 1.6 or newer (#17).
+  * Drop unused `assert_value` dependency (#18).
+  * Remove `@inline` annotations (#22).
+  * CI configuration cleanup.
+
+## v0.1.1
+
+Initial public release.
+
+  * Tokenisation of module attributes (with optional parentheses), atoms,
+    binary syntax, charlists, escaped characters, operators and
+    punctuation.
+  * Tokenisation of records: creation, access, and update.
+  * Tokenisation of qualified function calls — module name tagged as
+    `:name_class`.
+  * Combinator for function/arity syntax (`fun/N`).
+  * Registers itself as a Makeup lexer on application boot.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/deps/makeup_erlang/LICENSE 
new/deps/makeup_erlang/LICENSE
--- old/deps/makeup_erlang/LICENSE      1970-01-01 01:00:00.000000000 +0100
+++ new/deps/makeup_erlang/LICENSE      2026-08-06 01:16:07.000000000 +0200
@@ -0,0 +1,25 @@
+BSD 2-Clause License
+
+Copyright (c) 2019 by the respective authors (see AUTHORS file).
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/deps/makeup_erlang/hex_metadata.config 
new/deps/makeup_erlang/hex_metadata.config
--- old/deps/makeup_erlang/hex_metadata.config  2025-05-26 18:34:20.000000000 
+0200
+++ new/deps/makeup_erlang/hex_metadata.config  2026-08-06 01:16:07.000000000 
+0200
@@ -1,17 +1,9 @@
 {<<"links">>,
  [{<<"GitHub">>,<<"https://github.com/elixir-makeup/makeup_erlang";>>}]}.
 {<<"name">>,<<"makeup_erlang">>}.
-{<<"version">>,<<"1.0.2">>}.
+{<<"version">>,<<"1.1.0">>}.
 {<<"description">>,<<"Erlang lexer for the Makeup syntax highlighter.">>}.
 {<<"elixir">>,<<"~> 1.6">>}.
-{<<"files">>,
- [<<"lib">>,<<"lib/makeup">>,<<"lib/makeup/lexers">>,
-  <<"lib/makeup/lexers/erlang_lexer.ex">>,
-  <<"lib/makeup/lexers/erlang_lexer">>,
-  <<"lib/makeup/lexers/erlang_lexer/testing.ex">>,
-  <<"lib/makeup/lexers/erlang_lexer/application.ex">>,
-  <<"lib/makeup/lexers/erlang_lexer/helper.ex">>,<<".formatter.exs">>,
-  <<"mix.exs">>,<<"README.md">>]}.
 {<<"app">>,<<"makeup_erlang">>}.
 {<<"licenses">>,[<<"BSD-2-Clause">>]}.
 {<<"requirements">>,
@@ -20,4 +12,12 @@
    {<<"optional">>,false},
    {<<"requirement">>,<<"~> 1.0">>},
    {<<"repository">>,<<"hexpm">>}]]}.
+{<<"files">>,
+ [<<"lib">>,<<"lib/makeup">>,<<"lib/makeup/lexers">>,
+  <<"lib/makeup/lexers/erlang_lexer.ex">>,
+  <<"lib/makeup/lexers/erlang_lexer">>,
+  <<"lib/makeup/lexers/erlang_lexer/testing.ex">>,
+  <<"lib/makeup/lexers/erlang_lexer/application.ex">>,
+  <<"lib/makeup/lexers/erlang_lexer/helper.ex">>,<<".formatter.exs">>,
+  <<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,<<"CHANGELOG.md">>]}.
 {<<"build_tools">>,[<<"mix">>]}.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/deps/makeup_erlang/lib/makeup/lexers/erlang_lexer.ex 
new/deps/makeup_erlang/lib/makeup/lexers/erlang_lexer.ex
--- old/deps/makeup_erlang/lib/makeup/lexers/erlang_lexer.ex    2025-05-26 
18:34:20.000000000 +0200
+++ new/deps/makeup_erlang/lib/makeup/lexers/erlang_lexer.ex    2026-08-06 
01:16:07.000000000 +0200
@@ -14,7 +14,7 @@
   # Step #1: tokenize the input (into a list of tokens)
   ###################################################################
 
-  whitespace = ascii_string([?\s, ?\f, ?\n, ?\t], min: 1) |> token(:whitespace)
+  whitespace = ascii_string([?\s, ?\f, ?\r, ?\n, ?\t], min: 1) |> 
token(:whitespace)
 
   # This is the combinator that ensures that the lexer will never reject a file
   # because of invalid input syntax
@@ -59,24 +59,29 @@
     ])
 
   # Numbers
-  digits = ascii_string([?0..?9], min: 1)
+  #
+  # Erlang/OTP 27 added underscore separators in numeric literals
+  # (`1_000_000`, `16#FF_FF`, `0.1_5e1_0`). Lexer-tolerant: underscores are
+  # accepted anywhere inside the digit run; we don't validate position.
+  digits = ascii_string([?0..?9, ?_], min: 1)
 
   number_integer =
     optional(ascii_char([?+, ?-]))
-    |> concat(digits)
+    |> ascii_char([?0..?9])
+    |> optional(ascii_string([?0..?9, ?_], min: 1))
     |> token(:number_integer)
 
   number_integer_in_weird_base =
     optional(ascii_char([?+, ?-]))
     |> concat(numeric_base)
     |> string("#")
-    |> ascii_string([?0..?9, ?a..?z, ?A..?Z], min: 1)
+    |> ascii_string([?0..?9, ?a..?z, ?A..?Z, ?_], min: 1)
     |> token(:number_integer)
 
   # Floating point numbers
   float_scientific_notation_part =
     ascii_string([?e, ?E], 1)
-    |> optional(string("-"))
+    |> optional(ascii_char([?+, ?-]))
     |> concat(digits)
 
   number_float =
@@ -91,6 +96,16 @@
     ascii_string([?A..?Z, ?_], 1)
     |> optional(ascii_string([?a..?z, ?_, ?0..?9, ?A..?Z], min: 1))
 
+  # An underscore followed by at least one identifier character (`_5`,
+  # `_X`, `_unused`). Bare `_` stays as a punctuation token (the wildcard
+  # pattern), but `_<id>` is a variable in Erlang grammar and should
+  # render as `:name`. Without this rule the `_` is matched first by
+  # the `punctuation` rule and the rest of the identifier falls through.
+  underscore_identifier =
+    string("_")
+    |> ascii_string([?a..?z, ?_, ?0..?9, ?A..?Z], min: 1)
+    |> token(:name)
+
   simple_atom_name =
     ascii_string([?a..?z], 1)
     |> optional(ascii_string([?a..?z, ?_, ?@, ?0..?9, ?A..?Z], min: 1))
@@ -141,6 +156,20 @@
 
   macro_name = choice([variable_name, atom_name])
 
+  # Parameterised macro reference: `?FOO(arg1, arg2)`. Tokenised
+  # separately from the parameterless form so themes can render the two
+  # distinctly (matches `makeup_elixir`'s split between `@foo` and
+  # `@foo(...)`). The macro head emits as `:name_function`; the trailing
+  # `(` opens the standard punctuation group so paren matching still
+  # works.
+  macro_call =
+    string("?")
+    |> concat(macro_name)
+    |> token(:name_function)
+    |> concat(optional(whitespace))
+    |> concat(token("(", :punctuation))
+
+  # Parameterless macro: `?FOO`. Constants by convention.
   macro =
     string("?")
     |> concat(macro_name)
@@ -152,10 +181,23 @@
     |> optional(string(".") |> concat(atom_name))
     |> token(:name_label)
 
+  # `$\xFF`, `$\x{1F600}`, `$\077`, `$\^A`, plus simple `$\n` / `$\t` / `$\\` /
+  # `$\"` / `$\'` etc. The structured escapes (octal, hex, ctrl) must be tried
+  # before the single-char fallback so multi-character sequences are consumed
+  # whole.
+  character_escape =
+    string("\\")
+    |> choice([
+      escape_hex,
+      escape_octal,
+      escape_ctrl,
+      utf8_char([])
+    ])
+
   character =
     string("$")
     |> choice([
-      string("\\") |> utf8_char([]),
+      character_escape,
       utf8_char(not: ?\\)
     ])
     |> token(:string_char)
@@ -166,18 +208,55 @@
     |> ascii_char(to_charlist("~#+BPWXb-ginpswx"))
     |> token(:string_interpol)
 
-  escape_double_quote = string(~s/\\"/)
-  erlang_string = string_like(~s/"/, ~s/"/, [escape_double_quote, 
string_interpol], :string)
-
+  # Sub-token emitted inside string literals for escape sequences. Mirrors
+  # the `character_escape` shape so multi-character escapes (`\xFF`,
+  # `\x{1F600}`, `\077`, `\^A`) are consumed whole instead of getting
+  # cut at the first byte. Themes can render these distinctly.
   escaped_char =
     string("\\")
-    |> utf8_string([], 1)
+    |> choice([
+      escape_hex,
+      escape_octal,
+      escape_ctrl,
+      utf8_char([])
+    ])
     |> token(:string_escape)
 
+  erlang_string = string_like(~s/"/, ~s/"/, [escaped_char, string_interpol], 
:string)
+
+  # Multi-quoted strings (OTP 27+). The opening run of `"""` (or more) on
+  # its own line opens the string; a matching run on its own line closes
+  # it. Use a quadruple/quintuple opener when the body needs to contain
+  # `"""` literally. Each variant is a separate rule because NimbleParsec
+  # doesn't support dynamic delimiter lengths; longer-quote variants must
+  # be tried first so the triple-quote rule doesn't claim them prematurely.
+  quintuple_quoted_string =
+    lookahead_string(
+      string(~s/"""""\n/),
+      string(~s/\n"""""/),
+      [escaped_char, string_interpol]
+    )
+
+  quadruple_quoted_string =
+    lookahead_string(
+      string(~s/""""\n/),
+      string(~s/\n""""/),
+      [escaped_char, string_interpol]
+    )
+
   triple_quoted_string =
     lookahead_string(string(~s/"""\n/), string(~s/\n"""/), [escaped_char, 
string_interpol])
 
+  # Longer-quote variants must come first so the longest matching delimiter
+  # wins for sigils like `~"""""..."""""` (quintuple) or `~""""..."""" `
+  # (quadruple) — these are needed when the sigil body has to contain
+  # `"""` or `""""` literally, mirroring the rule for plain multi-quoted
+  # strings above.
   sigil_delimiters = [
+    {~s["""""\n], ~s[\n"""""]},
+    {"'''''\n", "\n'''''"},
+    {~s[""""\n], ~s[\n""""]},
+    {"''''\n", "\n''''"},
     {~s["""\n], ~s[\n"""]},
     {"'''\n", "\n'''"},
     {"\"", "\""},
@@ -220,13 +299,47 @@
 
   syntax_operators =
     word_from_list(
-      ~W[+ - +? ++ = == -- * / < > /= =:= =/= =< >= ==? <- <:- <= <:= ! ? ?!],
+      ~W[+ - +? ++ = == -- * / < > /= =:= =/= =< >= ==? <- <:- <= <:= ! ? ?! 
?=],
       :operator
     )
 
+  # OTP 29 native records relax the record-name rule: per the spec
+  # (https://www.erlang.org/doc/system/data_types.html), "it is not necessary
+  # to quote atoms that look like variable names or keywords." So `#State{}`,
+  # `#div{}`, `#case{}` are all valid record references even though `State`
+  # is variable-shape and `div`/`case` are reserved words. Tuple-based records
+  # don't allow these forms, but the lexer can't tell the two record kinds
+  # apart from local context — so accept the union.
+  #
+  # The `record_name: true` meta marker tells postprocess to skip the
+  # keyword / builtin / word-operator conversion for this position. Without
+  # it, `#case{}` would tokenise as `[#, keyword case, {]` — visually
+  # confusing because `case` here names a record, not an expression keyword.
+  record_name =
+    choice([
+      token(atom_name, :string_symbol, %{record_name: true}),
+      token(variable_name, :string_symbol, %{record_name: true})
+    ])
+
+  # External native record construction / pattern / field access:
+  #     #Module:Name{F = V}
+  #     #Module:Name.field
+  # The `Module:Name` shape between `#` and `{` (or `.`) was added in OTP 29
+  # alongside native records. Local construction (`#Name{...}`) is identical
+  # in shape to a tuple-based record and is handled by the rule below.
+  native_record_external =
+    token(string("#"), :operator)
+    |> concat(token(atom_name, :name_class))
+    |> concat(token(":", :punctuation))
+    |> concat(record_name)
+    |> choice([
+      token("{", :punctuation),
+      token(".", :punctuation)
+    ])
+
   record =
     token(string("#"), :operator)
-    |> concat(atom)
+    |> concat(record_name)
     |> choice([
       token("{", :punctuation),
       token(".", :punctuation)
@@ -249,13 +362,26 @@
     |> concat(token("/", :punctuation))
     |> concat(number_integer)
 
-  # Erlang prompt
+  # Erlang prompt. Anchored to a line boundary by requiring the leading
+  # whitespace to contain at least one `\n`. The original rule required
+  # the `\n` immediately before the prompt body, which broke when the
+  # generic `whitespace` rule had already consumed the trailing `\n` of
+  # a multi-character whitespace block (see makeup_elixir #28). Allowing
+  # any leading non-newline whitespace before the `\n` and any further
+  # whitespace after lets the rule match in those cases without
+  # false-positiving on `1 > 2` or `x. 1> a.` (neither contains a `\n`
+  # in the relevant position).
   erl_prompt =
-    string("\n")
-    |> optional(string("(") |> concat(atom_name) |> string(")"))
-    |> optional(digits)
-    |> string("> ")
-    |> token(:generic_prompt, %{selectable: false})
+    ascii_string([?\s, ?\f, ?\r, ?\t], min: 0)
+    |> string("\n")
+    |> optional(ascii_string([?\s, ?\f, ?\r, ?\n, ?\t], min: 1))
+    |> token(:whitespace)
+    |> concat(
+      optional(string("(") |> concat(atom_name) |> string(")"))
+      |> optional(digits)
+      |> string("> ")
+      |> token(:generic_prompt, %{selectable: false})
+    )
 
   # Error in shell
   erl_shell_error =
@@ -295,12 +421,21 @@
         hashbang,
         whitespace,
         comment,
+        quintuple_quoted_string,
+        quadruple_quoted_string,
         triple_quoted_string,
         erlang_string
       ] ++
         all_sigils ++
         [
+          native_record_external,
           record,
+          underscore_identifier,
+          # Macros must be tried before `syntax_operators`, since the
+          # operator list contains `?` and `?=` and would otherwise eat the
+          # leading `?` of `?FOO` / `?FOO(X)`.
+          macro_call,
+          macro,
           punctuation,
           # `tuple` might be unnecessary
           tuple,
@@ -315,7 +450,6 @@
           function_arity,
           function,
           atom,
-          macro,
           character,
           label,
           # If we can't parse any of the above, we highlight the next 
character as an error
@@ -349,38 +483,45 @@
 
   @keywords ~W[after begin case catch cond end fun if let of query receive try 
when maybe else]
 
-  @builtins ~W[
-    abs append_element apply atom_to_list binary_to_list bitstring_to_list
-    binary_to_term bit_size bump_reductions byte_size cancel_timer
-    check_process_code delete_module demonitor disconnect_node display
-    element erase exit float float_to_list fun_info fun_to_list
-    function_exported garbage_collect get get_keys group_leader hash
-    hd integer_to_list iolist_to_binary iolist_size is_atom is_binary
-    is_bitstring is_boolean is_builtin is_float is_function is_integer
-    is_list is_number is_pid is_port is_process_alive is_record is_reference
-    is_tuple length link list_to_atom list_to_binary list_to_bitstring
-    list_to_existing_atom list_to_float list_to_integer list_to_pid
-    list_to_tuple load_module localtime_to_universaltime make_tuple
-    md5 md5_final md5_update memory module_loaded monitor monitor_node
-    node nodes open_port phash phash2 pid_to_list port_close port_command
-    port_connect port_control port_call port_info port_to_list
-    process_display process_flag process_info purge_module put read_timer
-    ref_to_list register resume_processround send send_after send_nosuspend
-    set_cookie setelement size spawn spawn_link spawn_monitor spawn_opt
-    split_binary start_timer statistics suspend_process system_flag
-    system_info system_monitor system_profile term_to_binary tl trace
-    trace_delivered trace_info trace_pattern trunc tuple_size tuple_to_list
-    universaltime_to_localtime unlink unregister whereis
-  ]
+  # Auto-imported BIFs, sourced at compile time from `erl_internal:bif/2` —
+  # the same predicate the Erlang compiler uses to decide what's auto-imported.
+  # Refreshed every time `makeup_erlang` is rebuilt, so the list stays in sync
+  # with the OTP version we compile against and never bit-rots.
+  @builtins :erlang.module_info(:exports)
+            |> Enum.filter(fn {name, arity} -> :erl_internal.bif(name, arity) 
end)
+            |> Enum.map(fn {name, _arity} -> Atom.to_string(name) end)
+            |> Enum.uniq()
+            |> Enum.sort()
 
   @word_operators ~W[and andalso band bnot bor bsl bsr bxor div not or orelse 
rem xor]
 
+  # Record names tagged by the `record_name` combinator should not be
+  # reclassified as keywords / builtins / word-operators even if their
+  # text happens to match. Strip the marker after acting on it so it
+  # doesn't leak into the rendered output.
+  defp postprocess_helper([{:string_symbol, %{record_name: true} = meta, 
value} | tokens]),
+    do: [{:string_symbol, Map.delete(meta, :record_name), value} | 
postprocess_helper(tokens)]
+
   defp postprocess_helper([{:string_symbol, meta, value} | tokens]) when value 
in @keywords,
     do: [{:keyword, meta, value} | postprocess_helper(tokens)]
 
+  # Keywords followed by `(` are first matched by the `function` rule and
+  # tagged `:name_function`. Recover them here. The most common case is
+  # `fun(X) -> ... end`; the rule also covers any other keyword that gets
+  # written next to `(` (e.g. `if(X)` in a teaching example of invalid
+  # syntax).
+  defp postprocess_helper([{:name_function, meta, value} | tokens]) when value 
in @keywords,
+    do: [{:keyword, meta, value} | postprocess_helper(tokens)]
+
   defp postprocess_helper([{:string_symbol, meta, value} | tokens]) when value 
in @builtins,
     do: [{:name_builtin, meta, value} | postprocess_helper(tokens)]
 
+  # Same recovery for builtins: when a BIF is called as `length(L)` it is
+  # first matched by the `function` rule and tagged `:name_function`. Closes
+  # makeup_erlang #13.
+  defp postprocess_helper([{:name_function, meta, value} | tokens]) when value 
in @builtins,
+    do: [{:name_builtin, meta, value} | postprocess_helper(tokens)]
+
   defp postprocess_helper([{:string_symbol, meta, value} | tokens]) when value 
in @word_operators,
     do: [{:operator_word, meta, value} | postprocess_helper(tokens)]
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/deps/makeup_erlang/mix.exs 
new/deps/makeup_erlang/mix.exs
--- old/deps/makeup_erlang/mix.exs      2025-05-26 18:34:20.000000000 +0200
+++ new/deps/makeup_erlang/mix.exs      2026-08-06 01:16:07.000000000 +0200
@@ -1,7 +1,7 @@
 defmodule MakeupErlang.Mixfile do
   use Mix.Project
 
-  @version "1.0.2"
+  @version "1.1.0"
   @url "https://github.com/elixir-makeup/makeup_erlang";
 
   def project do
@@ -56,7 +56,7 @@
 
     paths = Path.join(Mix.Project.build_path(), "lib/*/ebin")
     args = ["MakeupErlang", @version, Mix.Project.compile_path()]
-    opts = ~w[--main Makeup.Lexers.ErlangLexer --source-ref v#{@version} 
--source-url #{@url}]
+    opts = ~w[--source-ref v#{@version} --source-url #{@url} --config 
.ex_doc.exs]
     System.cmd(ex_doc, args ++ ["--paths", paths] ++ opts)
     Mix.shell().info("Docs built successfully")
   end

Reply via email to