Ad "programmatically double the number of #":
The discussions
How to have hashes doubled and things expanded?
and
Double hashes inside macro definition?
might be of interest to you.
There you find approaches to the matter using l3regex or taking advantage of the fact that things like \write and \scantokens double hashes.
For the sake of having fun I did my own thing with the code below.
For recent TeX engines where \detokenize of the ε-TeX extensions is available I can offer a routine \ReplicateEveryHash which can serve as a workaround in some situations.
Syntax of the routine \ReplicateEveryHash is
\ReplicateEveryHash{⟨number⟩}{⟨balanced text⟩}
The routine \ReplicateEveryHash by means of \romannumeral-expansion recursively replicates ⟨number⟩ times every explicit character token of category 6(parameter) that is contained in the ⟨balanced text⟩.
The gist of the check for a hash is: \string# delivers a single hash-character-token of category 12(other) while with \detokenize hash-doubling takes place and therefore \detokenize{#} delivers two hash-character-tokens of category 12(other). (The edge case of an explicit space character of category 6 needs separate treatment.)
In case the ⟨balanced text⟩-argument of \ReplicateEveryHash contains matching pairs of explicit character tokens of category 1(begin group) and 2(end group), each of these pairs triggers another level of \romannumeral-expansion. Therefore excessive nesting of braces within the ⟨balanced text⟩-argument of \ReplicateEveryHash will take its toll on the semantic nest.
Besides this \ReplicateEveryHash does replace matching pairs of explicit character tokens of category 1(begin group) and 2(end group) of the ⟨balanced text⟩-argument by matching pairs of opening curly braces of category 1 and closing curly braces of category 2.
I suppose this won't be a problem in most situations as usually the curly braces are the only characters of category 1/2.
But this must be mentioned because this means that \ReplicateEveryHash is suitable only for situations where replacing explicit begin-grouping-character-tokens and explicit end-grouping-character-tokens by explicit curly-brace-tokens of the same kind doesn't matter.
Caveats/possible pitfalls:
If you place \ReplicateEveryHash{2}{...} into an \edef, due to \romannumeral-expansion hash-doubling will—in contrast with the \edef\macro{\unexpanded\expandafter{\expanded{#1}}}-approach—take place before expanding the tokens that form the ⟨balanced text⟩-argument of \ReplicateEveryHash. Therefore \edef\macro{\ReplicateEveryHash{2}{\string#1}} (or \ReplicateEveryHash{2}{\edef\macro{\string#1}} if you prefer) will yield an error-message about an illegal parameter number because in the hash-doubling-step you will get two hashes trailed by the digit 1. The first hash will be stringified. The second hash, which is trailed by the digit 1, will not be stringified and therefore will be taken for a parameter #1 while the ⟨parameter text⟩ of \macro is empty.
\makeatletter
%%////////////// Begin of code for \ReplicateEveryHash //////////////////////
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@stopromannumeral,
%% \UD@firstoftwo, \UD@secondoftwo,
%% \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%% \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%% \UD@CheckWhetherLeadingExplicitSpace, \UD@replicate, \UD@ExtractFirstArg
%%=============================================================================
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@removespace{\UD@firstoftwo{\def\UD@removespace}{} {}}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
\expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is an explicit character of
%% category 1:
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has a leading
%% explicit catcode-1-character-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked does not have a
%% leading explicit catcode-1-character-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
\romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{%
\expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with an explicit space-token:
%%.............................................................................
%% \UD@CheckWhetherLeadingExplicitSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does have a
%% leading explicit space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does not have a
%% a leading explicit space-token>}%
\newcommand\UD@CheckWhetherLeadingExplicitSpace[1]{%
\romannumeral\UD@CheckWhetherNull{#1}%
{\expandafter\UD@stopromannumeral\UD@secondoftwo}%
{%
% Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
% and thus do not disturb when the test is carried out within \halign/\valign:
\expandafter\UD@firstoftwo\expandafter{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\romannumeral\expandafter\UD@secondoftwo
\string{\UD@CheckWhetherLeadingExplicitSpaceB.#1 }{}%
}{}%
}%
}%
\@ifdefinable\UD@CheckWhetherLeadingExplicitSpaceB{%
\long\def\UD@CheckWhetherLeadingExplicitSpaceB#1 {%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo\expandafter{\string}%
}%
}%
%------------------------------------------------------------------------------
% \UD@replicate{<number>}{<tokens>}
%------------------------------------------------------------------------------
\newcommand\UD@replicateloop[3]{%
\if m#3\expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
{\UD@replicateloop{#1}{#2#1}}{\UD@stopromannumeral#2}%
}%
\newcommand\UD@replicate[2]{%
\romannumeral
\expandafter\UD@Exchange\expandafter{\romannumeral\number\number#1 000}%
{\UD@replicateloop{#2}{}}\relax
}%
%%=============================================================================
%% Extract first inner undelimited argument:
%%
%% \UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% \UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%
%% Due to \romannumeral-expansion the result is delivered after two
%% expansion-steps/after "hitting" \UD@ExtractFirstArg with \expandafter
%% twice.
%%
%% \UD@ExtractFirstArg's argument must not be blank.
%% This case can be cranked out via \UD@CheckWhetherBlank before calling
%% \UD@ExtractFirstArg.
%%
%% Use frozen-\relax as delimiter for speeding things up.
%% I chose frozen-\relax because David Carlisle pointed out in
%% <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \UD@ExtractFirstArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
%%
%%.............................................................................
\@ifdefinable\UD@RemoveTillFrozenrelax{%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}%
{\long\def\UD@RemoveTillFrozenrelax#1#2}{{#1}}%
}%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral\expandafter
\UD@PassFirstToSecond\expandafter{\romannumeral
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral#1}%
}{%
\UD@stopromannumeral\romannumeral\UD@ExtractFirstArgLoop
}%
}{%
\newcommand\UD@ExtractFirstArg[1]%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@stopromannumeral#1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%%=============================================================================
%% \ReplicateEveryHash{<number>}{<balanced text>}%
%%
%% Each explicit category-6(parameter)-character-token of the
%% <balanced text> is replicated <number> times.
%%
%% You obtain the result after two expansion-steps, i.e.,
%% in expansion-contexts you get the result after "hitting"
%% \ReplicateEveryHash by two \expandafter.
%%
%% As a side-effect, the routine does replace matching pairs of explicit
%% character tokens of catcode 1 and 2 by matching pairs of curly braces
%% of catcode 1 and 2.
%% I suppose this won't be a problem in most situations as usually the
%% curly braces are the only characters of category code 1 / 2...
%%
%% This routine needs \detokenize from the eTeX extensions.
%%-----------------------------------------------------------------------------
\newcommand\ReplicateEveryHash[2]{%
\romannumeral
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral\number\number#1 000}{%
\UD@ReplicateEveryHashLoop{#2}{}%
}%
}%
\newcommand\UD@ReplicateEveryHashLoop[3]{%
\UD@CheckWhetherNull{#1}{\UD@stopromannumeral#2}{%
\UD@CheckWhetherLeadingExplicitSpace{#1}{%
\expandafter\UD@ReplicateEveryHashLoop
\expandafter{\UD@removespace#1}{#2 }{#3}%
}{%
\UD@CheckWhetherBrace{#1}{%
\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\expandafter\expandafter\expandafter{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral%
\expandafter\expandafter\expandafter\UD@ReplicateEveryHashLoop
\UD@ExtractFirstArg{#1}{}{#3}%
}{#2}}%
{\expandafter\UD@ReplicateEveryHashLoop
\expandafter{\UD@firstoftwo{}#1}%
}{#3}%
}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherHash
\UD@ExtractFirstArg{#1}{#1}{#2}{#3}%
}%
}%
}%
}%
\newcommand\UD@CheckWhetherHash[4]{%
\expandafter\UD@CheckWhetherLeadingExplicitSpace\expandafter{\string#1}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter\UD@removespace\string#1}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter\UD@removespace\detokenize{#1}}{%
% something whose stringification yields a single space
\UD@secondoftwo
}{% explicit space of catcode 6
\UD@firstoftwo
}%
}{% something whose stringification has a leading space
\UD@secondoftwo
}%
}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter\UD@firstoftwo
\expandafter{\expandafter}\string#1}{%
\expandafter\expandafter\expandafter\UD@CheckWhetherNull
\expandafter\expandafter\expandafter{%
\expandafter\UD@firstoftwo
\expandafter{\expandafter}\detokenize{#1}}{%
% no hash
\UD@secondoftwo
}{% hash
\UD@firstoftwo
}%
}{% no hash
\UD@secondoftwo
}%
}%
{% hash
\expandafter\UD@PassFirstToSecond
\expandafter{%
\romannumeral\expandafter
\UD@Exchange\expandafter{%
\romannumeral\UD@replicateloop{#1}{}#4\relax
}{\UD@stopromannumeral#3}%
}{%
\expandafter\UD@ReplicateEveryHashLoop
\expandafter{\UD@firstoftwo{}#2}%
}%
}{% no hash
\expandafter\UD@ReplicateEveryHashLoop
\expandafter{\UD@firstoftwo{}#2}{#3#1}%
}{#4}%
}%
%%=============================================================================
%%////////////// End of code for \ReplicateEveryHash ////////////////////////
\makeatother
\documentclass{article}
\usepackage[dvipsnames]{xcolor}
\makeatletter
\NewDocumentCommand\DeclareFoo{O{\emph{##1}}}{%
% \DeclareFoo's #1 denotes the tokens that form
% \Foo's default-definition-text of the macro
% @style.
% At the time of carrying out \DeclareFoo the
% definition-text of @style is a second-level-
% definition where hashes of macro-parameters need
% to be doubled.
% Therefore hashes in \DeclareFoo's #1, which denotes
% a possible definition-text of @style, need to be
% doubled, otherwise the user has to do so when
% specifying a non-default value for \DeclareFoo's
% optional argument/a non-default definition-text for
% the second-level-definition of @style.
\ReplicateEveryHash{2}{\DeclareDocumentCommand\Foo{O{#1} m}}{{%
\def@style####1{##1}%
@style{##2}%
}}%
% The following might be better for keeping the
% definition of @style local because @style isn't
% (re)defined any more when its replacement-text is
% carried out:
% \ReplicateEveryHash{2}{\DeclareDocumentCommand\Foo{O{#1} m}}{{%
% \begingroup
% \def@style####1{\endgroup##1}%
% @style{##2}%
% }}%
}
\makeatother
\begin{document}
\DeclareFoo
Hello, \Foo{world}!
Hello, \Foo[\textbf{#1}]{world}!
\DeclareFoo[\color{blue}\textbf{#1}]%
This is \Foo{nice}!
\DeclareFoo[\color{red}\textbf{#1}]%
Is this \Foo{nice, too}?
Is this \Foo[\color[named]{ForestGreen}\textsc{#1}]{nice, too}?
Is this \Foo{nice, too}?
\end{document}
