4

I want to write a macro such that \ghpr{myrepo#1234} expands to \href{https://github.com/my-username/myrepo/pull/1234}{myrepo#1234}. (I know how to do it for \ghpr{myrepo}{1234}, but I'd prefer to keep the # separator if possible.)

My attempt at doing this is:

\documentclass{standalone}

\usepackage{xstring}
\usepackage{hyperref}
\newcommand*\ghpr[1]{%
  \StrSubstitute{#1}{##}{/pull/}[\tmpurl]%
  \href{https://github.com/my-username/\tmpurl}{#1}%
}

\begin{document}
\ghpr{myrepo#1234}
\end{document}

but this produces Illegal parameter number in definition of \xs_arg_ii when I try to use it.

I assume the problem here is that I'm not escaping the # correctly; how should I be writing this command?

7
  • @Teepeemm: Yes, it works fine if I write the macro that way, but then at the call site I have to write \ghpr{mathlib/pull/8105}; I wanted to be able to write \ghpr{mathlib#8105} and have the macro insert the /pull/ string for me.
    – Eric
    Commented Sep 28, 2023 at 17:14
  • Ah, I think I understand your point now; I somehow need to tell LaTeX that it should not parse the # at the callsite. Is this possible?
    – Eric
    Commented Sep 28, 2023 at 17:18
  • \newcommand\ghpr[2]{\href{https://github.com/my-username/#1/pull/#2}{#1\##2} and then \ghpr{myrepo}{1234}.
    – Skillmon
    Commented Sep 28, 2023 at 17:20
  • @Skillmon: sure, I know I can do it that way. I was hoping I could write it as a single argument with a # between the parts. If you think that's a really bad idea (or know it's impossible), I'd also accept an answer explaining why.
    – Eric
    Commented Sep 28, 2023 at 17:22
  • Any particular reason why you want to use #? It would be easy to do with another separator, if there's a suitable alternative which won't occur in the values. For example, it would easy to make it work for something like \ghpr{mathlib:8105} or \ghpr{mathlib:8015,mathslib:8106,mathxlib:9834} (for 3 links). Then you can just split the argument as the first step.
    – cfr
    Commented Sep 28, 2023 at 19:35

2 Answers 2

4

I can understand your wish for # as the argument delimiter, but note that this is a hard to parse for character in TeX due to its nature as the parameter token.

Anyway, the following proposes 3 solutions to make this work.

  • \ghprDetok would break any macros or special characters (read: non-ASCII in pdfTeX) in your argument, but is the fastest of them
  • \ghprEtl is the second fastest (but stable)
  • \ghprRegex is the slowest (but stable)

In none of the three do I check whether an # was actually part of the argument (which would result in a low level TeX error in the \ghprDetok and \ghprEtl and lead to an empty numeric part in \ghprRegex). If you need detection here I can add it for all three variants.

The \ghprEtl variant reuses the internal of the \ghprDetok, which wouldn't be necessary but it'd need an auxiliary so why not reuse the other one.

\documentclass{article}

\usepackage{hyperref}
\usepackage{etl}

\newcommand*\ghprfastest[2]
  {\href{https://github.com/my-username/#1/pull/#2}{#1\##2}}%

\newcommand*\ghprDetok[1]
  {\expandafter\ghprDetokAUX\detokenize{#1}\stop}
\expanded{\unexpanded{\def\ghprDetokAUX#1}\string#\string#}#2\stop
  {\ghprfastest{#1}{#2}}

\ExplSyntaxOn
\cs_new_protected_nopar:Npn \ghprEtl #1
  {%
    \exp_last_unbraced:Ne \ghprDetokAUX
      { \etl_token_replace_once:nNe {#1} ## { \c_hash_str\c_hash_str } }
    \stop
  }
\cs_generate_variant:Nn \etl_token_replace_once:nNn { nNe }

\cs_new_protected_nopar:Npn \ghprRegex #1
  {
    \regex_split:nnN { \# } {#1} \l_tmpa_seq
    \exp_args:Nee \ghprfastest
      { \seq_item:Nn \l_tmpa_seq 1 }
      { \seq_item:Nn \l_tmpa_seq 2 }
  }
\ExplSyntaxOff

\begin{document}
\ghprfastest{myrepo}{1234}

\ghprDetok{myrepo#1234}

\ghprEtl{myrepo#1234}

\ghprRegex{myrepo#1234}
\end{document}
2

You can change catcode of # locally:

\def\ghpr{\bgroup \catcode`\#=12 \ghprA}
\def\ghprA #1{\egroup \href{https://github.com/my-username/myrepo/pull/1234}{#1}}

\ghpr{myrepo#1234} % expands to \href{https://github.com/my-username/myrepo/pull/1234}{myrepo#1234}
7
  • Nice solution, unfortunately it works only if a category code change still works. Anyway +1.
    – Skillmon
    Commented Sep 28, 2023 at 17:26
  • Can you explain what you mean by "still works" @Skillmon?
    – Eric
    Commented Sep 28, 2023 at 17:27
  • @Eric this would fail if \ghpr is part of an argument to another macro (\textbf{\ghpr{abc#123}} would fail).
    – Skillmon
    Commented Sep 28, 2023 at 17:28
  • 1
    In plain TeX, \footnote{See \ghpr{abc#123}} doesn't fail, because {...} is not macro parameter here. And we write {\bf something}, so there isn't macro parameter too.
    – wipet
    Commented Sep 28, 2023 at 17:54
  • 1
    But the #1 if footnote mark, not the footnote text. OK, I bring the confusion here because I didn't write my example correctly in my previous comment. Should be correct: \footnote1{See \ghpr{abc#123}
    – wipet
    Commented Sep 28, 2023 at 18:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .