ghoetker

ghoetker

I’m very new to Elixir and hoped someone might have a few moments to check over my first Elixir program. It’s a pretty direct translation of a Racket program I wrote, so I would especially value any feedback on more effective or Elixiry approaches.

The purpose of the program is to ingest a BibLaTeX file (@bibfile), which will be processed and written out to @outfile. Processing involves

  • Regularizing the keywords (conveniently on lines starting "“Keywords = {”), so each keyword is separated by commas (rather than comma or semicolon) and capitalized
  • Passing all of other lines unchanged

For example,

@book{Snoopy-Dark-01,
  Author = {Snoopy},
  Keywords = {fiction; Unfinished Books},
  Booktitle = {It was a Dark and Stormy Night}}

should end up as

@book{Snoopy-Dark-01,
Author = {Snoopy},
Keywords = {Fiction, Unfinished books},
 Booktitle = {It was a Dark and Stormy Night}}

Pretty basic, but a good learning experience.

Thank you very much in advance. I’m really enjoying both the Elixir language and community.

defmodule BibCleaner do
  @bibfile "/Users/ghoetker/BibDeskPapers/masterbibliography.bib"
  @outfile "test_out.bib"
  @moduledoc """
  Clean BibLaTeX files
  """

  def clean do
    {:ok, data} = File.read(@bibfile)
    {:ok, file} = File.open(@outfile, [:write, :utf8])

    data
    |> String.split("\n")
    |> Enum.map(&process(&1))
    |> Enum.map(&IO.puts(file, &1))

    File.close(file)
  end

  defp process(astring) do
    cond do
      String.contains?(astring, "\tKeywords = {") ->
        astring
        |> String.replace_leading("\tKeywords = {", "")
        |> String.replace_trailing("},", "")
        |> String.split([", ", "; "])
        |> Enum.map(&String.capitalize/1)
        |> Enum.join(", ")
        |> (fn x -> "\tKeywords = {" <> "#{x}" <> "}," end).()

      true ->
        "#{astring}"
    end
  end
end

Showing Posts 1 to 10

cdegroot

cdegroot

From a quick glance, lgtm. Having said that - and this has nothing to do with Elixir - if this is more than a one-off, I would parse that thing into some data structure, operate on the data structure, and then write it out to a string again. May be me, but I prefer to have the ugly string scanning stuff and the stuff I’m actually trying to accomplish separate :wink:

wmnnd

wmnnd

Instead of using cond, you could do this which seems a bit more Elixir-y to me:


  defp process(astring) do
    if String.contains?(astring, "\tKeywords = {"),
      do: do_process(astring),
      else: astring
  end

  defp do_process(astring)
    astring
    |> String.replace_leading("\tKeywords = {", "")
    |> String.replace_trailing("},", "")
    |> String.split([", ", "; "])
    |> Enum.map(&String.capitalize/1)
    |> Enum.join(", ")
    |> (fn x -> "\tKeywords = {" <> "#{x}" <> "}," end).()
  end

Was there any particular reason you were using "#{astring}"? It seems like this just creates a string that is identical to the previous string …

OvermindDL1

OvermindDL1

Eh, that itself is technically a shorter way of just calling to_string(astring), which can be important is astring is an IOList or integer or something.

wmnnd

wmnnd

I suppose, but the input seems to be coming from String.split anyways :slight_smile:

ghoetker

ghoetker OP

Thank you all very, very much.

Replacing cond makes a lot of sense. Not sure why I’d gone to "#{astring}", but just astring works just fine.

I agree that parsing into a data structure would make sense if I were doing anything more complex. Bib(La)TeX is infamous for being difficult to parse (in its defense, we’ve learned a lot in the 33 years since it was invented), so I’ve avoided that pain point for this simple task.

Very helpful and much appreciated. Thank you again.

CptnKirk

CptnKirk

If you’re already interpolating, you can avoid the binary concats.

(fn x -> “\tKeywords = { #{x} },” end).()

And in general this whole line is kind of a smell to me. Not in what it does, but in the way you’re forced to write it. I tried to come up with something better, but couldn’t. Would be nice if there was a Pipe module with convenience functions to help make this type of stuff prettier.

|> Pipe.map(“\tKeywords = { #{&1} },”)

or some such.

PS: As another elixir noobie, I love this thread. Wish there were many more.

ghoetker

ghoetker OP

Thanks for the lead on avoiding the binary concats. In terms of pipes going anywhere but the “front” of a function, that may just be a trade-off of what makes them so straight-forward. I could have written a private function to do that, I think, and probably will next time.

Glad you found the thread useful. I’ve really enjoyed the Forum, overall.

Happy Elixiring.

CptnKirk

CptnKirk

Sure, you can definitely write your own private map function and then do the same thing and clean up the syntax a bit. But I assume this is a very common problem, and I’m surprised this is the accepted solution. I’m still hoping for one of my betters to demonstrate something truly elegant that we’re apparently missing.

Also, what’s the best way to pipe to a file? Seems like an obvious thing people would want to do.

"contents"
|> <???>.write(path) # or <???>.write(iodevice)
blatyo

blatyo

Conduit Core Team

This isn’t ideal, but looks slightly better:

    astring
    |> String.replace_leading("\tKeywords = {", "")
    |> String.replace_trailing("},", "")
    |> String.split([", ", "; "])
    |> Enum.map(&String.capitalize/1)
    |> Enum.join(", ")
    |> String.replace_prefix("", "\tKeywords = {  ")
    |> String.replace_postfix("", " },")

I wouldn’t mind a Pipe.map/2. In this case a String.prepend/2 and String.append/2 would also work if they existed.

CptnKirk

CptnKirk

I also feel like this processing pipeline could be made more resilient and clear by using a regex to extract the stuff. Then process the stuff. Then interpolate back into a string.

I know that when I was playing around locally, the string I passed into the process function didn’t include a leading “\t”. And I didn’t get the expected output. Is it critical to always anchor on a leading tab? I dunno. Possibly not.

I don’t have a working regex example yet, but it would be something I’d look into. You’d only save 1 line, but clarity could be improved. There may be a String.replace function that is more easily piped. You’d supply your string containing an anchor to replace, and then your fixed keywords. Could work.

I also wonder if the pipe macro could be improved to accept an optional parameter position for injection. Something like:

"contents"
|> 1, File.write("path") # 0 index default

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
Aludel - LLM Evaluation Workbench Aludel is an embeddable Phoenix LiveView dashboard for evaluating and comparing LLM prompts across mult...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews