15 Jul 2013, 03:42
Dave_gnome_head_isolated_pragsmall

Dave Thomas (340 posts)

  • (Tricky) Before reading the next section, see if you can write the code to format the data into columns, like the sample output at the start of the chapter. This is probably the longest piece of Elixir code you’ll have written. Try to do it without using if or cond.
29 Oct 2013, 16:48
Mac 128k logic board 96x96_pragsmall

Roger Turner (7 posts)


   defmodule Issues.Tabulate do

    def print_table_for_columns(list_of_dicts, columns) do

      cells = lc dict inlist list_of_dicts, do: 
        (lc {key, value} inlist Enum.to_list(dict), key in columns, do: 
            to_string(value) )

      headed = Enum.concat([columns], cells)

      lengths = lc row inlist headed, do: 
        Enum.map(row, &String.length/1)

      widths = lc col inlist List.zip(lengths), do: 
        Enum.max(Kernel.tuple_to_list(col))

      padded = lc row inlist headed, do:
        (lc {cell, width} inlist Enum.zip(row, widths), do: 
          String.ljust(cell, width))

      divider = Enum.map(widths, &(String.duplicate("-", &1)))

      IO.puts Enum.join(Enum.at(padded, 0), " | ")
      IO.puts Enum.join(divider, "-+-")
      lc row inlist Enum.drop(padded, 1), do: IO.puts Enum.join(row, " | ")

    end

   end

13 Jul 2014, 15:50
Generic-user-small

Paul Bickford (1 post)

The above code looks much more succinct than mine. (I would be interested to understand the “lc value inlist list, do:” format - what is lc?).

With the following, I tried to format the output for multi-line titles, splitting the lines between words. I also set the column width of the terminal in the config.exs file.

config.exs

[
  issues: [github_url: "https://api.github.com"],
  issues: [command_line_width: 80]
]

cli.ex

...

@command_line_width Application.get_env(:issues, :command_line_width)

...

def process({user, project, count}) do
  Issues.GithubIssues.fetch(user, project)
    |> decode_response
    |> convert_to_list_of_hashdicts
    |> sort_into_ascending_order
    |> Enum.take(count)
    |> display_table
end

...

def display_table(list) do
  line1 = "   #   | created_at" <> String.rjust("| title", 19)
  line2 = String.rjust("+", 8,?-) <> String.rjust("+", 24, ?-) <> String.duplicate("-", @command_line_width - 32)
  IO.write line1 <> "\n" <> line2 <> "\n"
  for entry <- list, do: IO.puts get_row_string(entry)
end
   
def get_row_string(entry) do
  row = " " <> String.ljust(Integer.to_string(entry["number"]), 6) <> "| " <> String.ljust(entry["created_at"], 22) <> "| " <> String.ljust(entry["title"], @command_line_width - 33)
  format_row(row, String.length(row))
end

def format_row(line, len) when len <= @command_line_width, do: line
def format_row(line, len) when len > @command_line_width do
  {l1, l2} = String.split_at(line, splitpoint(line, @command_line_width))
  l1 <> format_additional_row(l2, String.length(l2))
end
  
def format_additional_row(line, len) when len <= @command_line_width-33 do
  "\n" <> String.rjust("| ", 33) <> line
end
def format_additional_row(line, len) when len > @command_line_width-33 do
  pad = "\n" <> String.rjust("| ", 33)
  {l1, l2} = String.split_at(line,splitpoint(line,@command_line_width-33))
  pad <> l1 <> format_additional_row(l2, String.length(l2))
end
  
def splitpoint(string, initial_splitpoint) do
  case {String.at(string, initial_splitpoint), String.at(string, initial_splitpoint + 1)} do
    {" ", _} -> initial_splitpoint
    {_, " "} -> initial_splitpoint
    _        -> splitpoint(string, initial_splitpoint-1)
  end
end
22 Nov 2014, 22:59
Generic-user-small

Elliot Finley (11 posts)

My take on a table formatter


defmodule Issues.TableFormatter do
  def print_table_for_columns(list_of_hashdicts, list_of_column_title_maps) do
    list_of_column_title_maps
    |> Enum.map(&add_width(&1, list_of_hashdicts))
    |> output_title
    |> output_table(list_of_hashdicts)
  end

  def output_table(list_of_column_map, list_of_hashdicts) do
    list_of_hashdicts
    |> Enum.map(&output_line(&1, list_of_column_map))
  end
  
  def output_line(hashdict, list_of_column_map) do
    for %{column: key, width: width} <- list_of_column_map do
      hashdict
      |> HashDict.fetch!(key)
      |> to_string
      |> String.ljust(width)
    end
    |> Enum.join(" | ")
    |> IO.puts
  end

  def output_title(list_of_column_map) do
    list_of_column_map
    |> Enum.map(fn %{title: title, width: width} -> String.ljust(title, width) end)
    |> Enum.join(" | ")
    |> IO.puts

    list_of_column_map
    |> Enum.map(fn %{width: width} -> String.duplicate("-", width) end)
    |> Enum.join("-+-")
    |> IO.puts

    list_of_column_map
  end

  def add_width(column_map, list_of_hashdicts) do
    %{column: key} = column_map
    width =
    list_of_hashdicts
    |> Enum.map(fn hd -> HashDict.fetch!(hd, key) |> to_string |> String.length end )
    |> Enum.max
    
    Dict.put(column_map, :width, width)
  end
end

31 Dec 2014, 14:31
Generic-user-small

Pierre Sugar (56 posts)

  def create_table_of_issues(list) do
    header = [ String.ljust(" #", 5), 
               String.ljust(" Created at", 22),
               " Title" ]
    border = [ String.duplicate("-", 5),
               String.duplicate("-", 22),
               String.duplicate("-", 50) ]
    IO.puts Enum.join(header, "|")
    IO.puts Enum.join(border, "+")
    create_table_row(list)
  end

  def create_table_row([]), do: IO.puts String.duplicate("-", 79)
  def create_table_row([head|tail]) do
    line = [head["number"], head["created_at"], head["title"]]
    |> Enum.join(" | ")
    |> String.slice(0..78)

    IO.puts line

    create_table_row(tail)
  end

Output:

iex(3)> Issues.CLI.process({"elixir-lang", "elixir", 20})                       
 #   | Created at           | Title                                             
-----+----------------------+-------------------------------------------------- 
2722 | 2014-08-27T04:33:39Z | Added first draft of File.mv for moving files aro 
2728 | 2014-08-29T14:28:30Z | Should elixir (and other applications) have stick 
2762 | 2014-09-09T21:42:34Z | Improve UndefinedFunctionError                      
2763 | 2014-09-10T00:37:44Z | Improve Agent debugging                           
2779 | 2014-09-17T21:47:56Z | Relups cause problems for Elixir, IEx and Logger  
2785 | 2014-09-23T14:21:48Z | Compiled regular expressions are endian-dependent 
2786 | 2014-09-23T19:03:49Z | Tasks forces the compilation of project          
2794 | 2014-09-28T16:07:32Z | Consider emitting warnings for variables defined 
2795 | 2014-09-30T12:37:55Z | Consider moving spawn, spawn_link and spawn_monit 
2798 | 2014-10-03T09:11:48Z | Compiler warning when using a variable bound in a 
2801 | 2014-10-04T23:06:20Z | Move application state out of application env      
2804 | 2014-10-05T18:17:26Z | Consider shipping with `consolidate_protocols: Mi 
2836 | 2014-10-21T04:04:02Z | Error in a test for mix                           
2860 | 2014-10-29T14:42:42Z | Logger exception with metadata[:function]         
2862 | 2014-10-29T21:06:41Z | Mix app.start does not run main app as permanent  
2870 | 2014-10-31T23:56:15Z | ASN.1 compiler proposal for mix.                 
2880 | 2014-11-06T14:09:17Z | Allow inspect opts to be given to error logger ha
2908 | 2014-11-29T22:58:49Z | priv is only re-copied on Windows if changes were
2912 | 2014-12-02T18:33:11Z | Enforce ranges first and last to have the same ty 
2923 | 2014-12-06T11:46:46Z | IEx.pry also won't work in dumb shells            
-------------------------------------------------------------------------------
  You must be logged in to comment