<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="221576" data-post-id="221576">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Aetherus" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Aetherus/120/17203_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Aetherus
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>And we can do something crazy (for practicing tail recursion, of course. You don’t wanna use this sorta code in production).</p>
<p>The following code assumes there’s no mismatching bracket, and there’s no nested bracket. The code is <strong>NOT</strong> unicode-safe.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule MyTemplate do

  @spec consolidate(String.t, %{optional(String.t) =&gt; String.t}) :: String.t
  def consolidate(template, replacements) do
    do_consolidate(template, replacements, [], nil)
  end

  # I know @doc has no effect on private functions,
  # but it looks better to write docs in this way.
  @doc """
  Handle the consolidation progress one character at a time.

  ## Params

    - `template`: the yet-to-handle part of the template string.
    - `replacements`: the map of replacements.
    - `acc`: an IO list. The accumulator of the content generated so far.
    - `placeholder`: `nil` or an IO list.
             If `placeholder` is `nil`, it means this function is handling a character out of any brackets,
             otherwise, this function is handling a character inside a bracket.

  ## Return value

    A string with all placeholders replaced with their corresponding values in `replacements`.
  """
  defp do_consolidate("[" &lt;&gt; rest, replacements, acc, nil) do
    # Encountered an open bracket outside any brackets.
    # The next few characters should be a placeholder,
    # so recurse with an empty IO list to store the placeholder.
    do_consolidate(rest, replacements, acc, [])
  end

  defp do_consolidate("]" &lt;&gt; rest, replacements, acc, placeholder) do
    # Encountered a close bracket.
    # `placeholder` should contain all the characters of a placeholder,
    # so we lookup the value in `replacements` and append it to `acc`.

    placeholder = IO.iodata_to_binary(placeholder)
    value = replacements[placeholder] || ""
    do_consolidate(rest, replacements, [acc, value], nil)
  end

  defp do_consolidate(&lt;&lt;char::binary-1, rest::binary&gt;&gt;, replacements, acc, nil) do
    # Encountered a non-bracket character outside any brackets.
    # Just append the character to `acc`.
    do_consolidate(rest, replacements, [acc, char], nil)
  end

  defp do_consolidate(&lt;&lt;char::binary-1, rest::binary&gt;&gt;, replacements, acc, placeholder) do
    # Encountered a non-bracket character inside a bracket.
    # This character should be part of a placeholder,
    # so we append this character to `placeholder`.
    do_consolidate(rest, replacements, acc, [placeholder, char])
  end

  defp do_consolidate("", _replacements, acc, nil) do
    # The whole template is handled.
    # Just convert `acc` to a string and return it.
    IO.iodata_to_binary(acc)
  end
end
</code></pre>
<p>You can try call it:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">template = """
Hi [name],
Thank you for your time in our office.

Thank you for booking at [company] for [time].

Regards
[salesguy]
"""

template
|&gt; MyTemplate.consolidate(%{
  "company" =&gt; "The Chocolate Factory",
  "name" =&gt; "Charlie Bucket",
  "salesguy" =&gt; "Willy Wonka",
  "time" =&gt; "Aug 12, 2021"
})
|&gt; IO.puts()
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="221576" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/i-stuck-with-this-text-file-manipulation-problem/41384/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-221576" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="221576"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #11"></div>
  </section>
</div>
    <div class="postbit" id="221588" data-post-id="221588">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="ambareesha7" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  ambareesha7
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I’m going with this approach for now but still I’m looking for better and easy solution</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="221588" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/i-stuck-with-this-text-file-manipulation-problem/41384/13">Post #12</a>
	                </div>
	            </div>
              <div id="likers-container-221588" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="221588"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #12"></div>
  </section>
</div>
    <div class="postbit" id="221589" data-post-id="221589">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="ambareesha7" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  ambareesha7
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>i spoke to one of my known senior developer he given me this solution:<br>
<code>read_file.exs</code> its a script file</p>
<code>
defmodule ReadFiles do
	@output_file "output.txt"
	def read_file(args) do
		case File.read("source.txt") do
			{:ok, body} -&gt;
				body
				|&gt; ReadFiles.replace_variables(parse_args(args))
				|&gt; ReadFiles.write_to_file()
			{:error, reason} -&gt;
				IO.puts "Error reading file. Reason: #{inspect reason}"
		end
	end
<pre><code>def replace_variables(body, vars) do
	body
	|&gt; String.split("\n")
	|&gt; Enum.map(&amp;replace_line(&amp;1, vars))
	|&gt; Enum.join("\n")
end

def replace_line(line, vars) do
	String.split(line, " ")
	|&gt; Enum.map(fn a -&gt;
		replace_word(a, vars)
	end)
	|&gt; Enum.join(" ")
end

defp replace_word(word, vars) do
	case String.match?(word, ~r/\[\w+\]/) do
		true -&gt;
			[prefix, var_name, suffix] = String.split(word, ["[", "]"])
			replacement = Map.get(vars, var_name)
			Enum.join([prefix, replacement, suffix], "")
		false -&gt;
			word
	end
end

def write_to_file(contents) do
	case File.write(@output_file, contents) do
		:ok -&gt;
			IO.puts "Wrote file: #{@output_file}.\n"
		_ -&gt;
			IO.puts "Failed to write file."
	end
end

defp parse_args(args) do
	args |&gt; Enum.map(&amp;String.split(&amp;1, "="))
	|&gt; Enum.reduce(%{}, fn [k|v], acc -&gt; Map.put(acc, k, v) end)
end
</code></pre>
<p>end</p>
<p>System.argv() |&gt; ReadFiles.read_file()</p>
</code>
and run the code like this:
<pre>elixir read_file.exs name=John company=Google time=3:30pm salesguy=Ralph
</pre>
this command will throwout a text file named "output.txt" with modified changes
<p>i really <strong>love elixir</strong> we can come up with different solutions as we pleased</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="221589" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/i-stuck-with-this-text-file-manipulation-problem/41384/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-221589" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="221589"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #13"></div>
  </section>
</div>
    <div class="postbit" id="221595" data-post-id="221595">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Aetherus" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Aetherus/120/17203_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Aetherus
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>And let’s do something even crazier: <strong>Metaprogramming</strong>!</p>
<p>Suppose you have a file (say, <code>placeholders.txt</code>) that lists all possible placeholders, like this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">name
company
time
salesguy
</code></pre>
<p>you can define the <code>MyTemplate</code> module like this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule MyTemplate do
  @placeholder_file_path "placeholders.txt"

  # Uncomment this if you want to leverage live recompile when the content of placeholder.txt changed
  # @external_resource @placeholder_file_path

  def consolidate(template, replacements) do
    do_consolidate(template, replacements, [])
  end

  # For each placeholder listed in the placeholders.txt,
  # define a function clause like:
  # 
  #     defp do_consolidate("[name]" &lt;&gt; rest, replacements, acc) do
  #       do_consolidate(rest, replacements, [acc, replacements["name"] || ""])
  #     end
  # 
  for placeholder &lt;- File.stream!(@placeholder_file_path) |&gt; Enum.map(&amp;String.trim/1) do
    defp do_consolidate(unquote("[#{placeholder}]") &lt;&gt; rest, replacements, acc) do
      do_consolidate(rest, replacements, [acc, replacements[unquote(placeholder)] || ""])
    end
  end

  defp do_consolidate(&lt;&lt;char::binary-1, rest::binary&gt;&gt;, replacements, acc) do
    do_consolidate(rest, replacements, [acc, char])
  end

  defp do_consolidate("", _replacements, acc) do
    IO.iodata_to_binary(acc)
  end
end
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="221595" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/i-stuck-with-this-text-file-manipulation-problem/41384/15">Post #14</a>
	                </div>
	            </div>
              <div id="likers-container-221595" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="221595"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #14"></div>
  </section>
</div>
    <div class="postbit" id="221761" data-post-id="221761">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="madlep" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/madlep/120/1134_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  madlep
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Had a play with this.</p>
<p>We can get even more funky using <code>defmacro</code> to add some compile time optimisation so the template is only ever parse once. Then we just assemble an <code>iolist</code> by transforming <em>only</em> the [param] values, leaving the rest of the string alone, and passing it out through <code>IO.iodata_to_binary/1</code> to generate the string (and if you were using it directly over a socket etc, you could even skip that).</p>
<p>This is kinda similar to how Phoenix renders templates - don’t parse a massive string every time, just figure out a representation with the separate parts once, then replace variables at runtime, and then concat output strings into an <code>iolist</code>.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Template do
  defmacro template(path) do
    # load and parse template *once only*, at compile time :)
    parts =
      path
      |&gt; File.read!()
      |&gt; parse()

    quote do
      # recompile template module this is run in when template text changes
      @external_resource unquote(path)

      # generate function render/1 function in template module with parsed parts baked in
      def render(params) do
        unquote(parts)
        |&gt; Template.render(params)
      end
    end
  end

  def parse(template) do
    # split template string on "[param_name]" blocks. `U` flag means "ungreedy",
    # so it will only consume ONE param, not look for the largest string between
    # the start and end of multiple [] params.
    ~r"\[.*\]"U
    # `include_captures` keeps the [param_name] blocks for us
    |&gt; Regex.split(template, include_captures: true)
    # we get a list of plain string parts, and "[param_name]" strings. Turn the
    # param name strings into tuple so we can distinguish them later when
    # rendering.
    |&gt; Enum.map(fn part -&gt;
      case Regex.run(~r"\[(?&lt;part&gt;.*)\]", part, capture: ["part"]) do
        [param_name] -&gt; {:param, String.to_atom(param_name)}
        nil -&gt; part
      end
    end)

    # parsed template looks something like:
    # [
    #   "Hi ",
    #   {:param, :name},
    #   ",\nThank you for your time in our office.\n\nThanks for booking at ",
    #   {:param, :company},
    #   " for ",
    #   {:param, :time},
    #   "\n\nRegards\n",
    #   {:param, :salesperson},
    #   "\n"
    # ]
  end

  def render(parts, params) do
    # rendering is super efficient, just replacing {:param, :param_name} with
    # the param name looked up from the params provided to build an iolist, then
    # converting it to a binary
    parts
    |&gt; Enum.map(&amp;render_part(&amp;1, params))
    |&gt; IO.iodata_to_binary()
  end

  defp render_part({:param, param_name}, params), do: Access.fetch!(params, param_name)
  defp render_part(part, _params) when is_binary(part), do: part
end
</code></pre>
<p>Then to use it, we just <code>import Template</code> into the module that defines the template.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule MyTemplate do
  import Template

  template("priv/source.txt")
end
</code></pre>
<p>And then call it with the params</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">MyTemplate.render(
  name: "Bob",
  company: "Awesome McAwesome Co",
  time: "3:00pm",
  salesperson: "Sally Sales"
)
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="221761" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/i-stuck-with-this-text-file-manipulation-problem/41384/16">Post #15</a>
	                </div>
	            </div>
              <div id="likers-container-221761" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="221761"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #15"></div>
  </section>
</div>
    <div class="postbit" id="246600" data-post-id="246600">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="pdgonzalez872" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/pdgonzalez872/120/28792_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  pdgonzalez872
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Seems like calling <code>String.replace/3</code> solves this nicely? Maybe I’m missing something.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">ExUnit.start()

defmodule Test do
  use ExUnit.Case

  describe "implementation for https://forum.elixirforum.com/t/i-stuck-with-this-text-file-manipulation-problem/41384" do

    defmodule ReplaceVariablesImplementation do
      def call(text, %{name: name, company: company, time: time, salesguy: salesguy} = _args) do
        text
        |&gt; String.replace("[name]", name)
        |&gt; String.replace("[company]", company)
        |&gt; String.replace("[time]", time)
        |&gt; String.replace("[salesguy]", salesguy)
      end
    end

    test "Replaces variables in text" do
      # Do File.read!/1 to get the file contents. Using a variable for brevity.
      text = """
      Hi [name],
      Thank you for your time in our office.

      Thank you for booking at [company] for [time].

      Regards
      [salesguy]
      """

      # Using a string for time for brevity, maybe this is your use case, maybe not.
      args = %{name: "Jane", company: "", time: "2022/3/25 13:00", salesguy: "Joe"}

      expected = """
      Hi Jane,
      Thank you for your time in our office.

      Thank you for booking at  for 2022/3/25 13:00.

      Regards
      Joe
      """

      result = ReplaceVariablesImplementation.call(text, args)

      assert result == expected
    end
  end
end
</code></pre>
<p>(the test passes)</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="246600" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/i-stuck-with-this-text-file-manipulation-problem/41384/17">Post #16</a>
	                </div>
	            </div>
              <div id="likers-container-246600" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="246600"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-solved cat-solved" title="Marked as solution"></div>
  </section>
</div>
    <div class="postbit" id="246636" data-post-id="246636">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="ambareesha7" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  ambareesha7
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Your code does the job<br>
Actually I was overthinking on that issue, that’s why I posted it here but after I solved it, it was simple</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="246636" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/i-stuck-with-this-text-file-manipulation-problem/41384/18">Post #17</a>
	                </div>
	            </div>
              <div id="likers-container-246636" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="246636"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-last-post cat-last-post" title="Last post!"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <span class="all-loaded">— All posts loaded —</span>
</div></template></turbo-stream>