scribe icon indicating copy to clipboard operation
scribe copied to clipboard

nested maps or lists

Open stephenmoloney opened this issue 8 years ago • 15 comments

I wonder is there merit in handling nested maps and/or lists...

stephenmoloney avatar Feb 18 '17 18:02 stephenmoloney

You mean tables within tables or accessing nested data?

hpopp avatar Feb 26 '17 20:02 hpopp

@hpopp Yes...

stephenmoloney avatar Mar 16 '17 08:03 stephenmoloney

I don't like the idea of a table within a table visually. Perhaps some sort of optional SQL join-style table that adds extra columns for sub structs.

But for accessing nested data, you can use custom function columns.

iex> post_1 = %{msg: "msg 1", author: %{name: "author 1"}}
iex> post_2 = %{msg: "msg 1", author: %{name: "author 2"}}
iex> [post_1, post_2] |> Scribe.print([:msg, {"Author Name", fn(x) -> x.author.name end}])
+---------+---------------+
| :msg    | "Author Name" |
+---------+---------------+
| "msg 1" | "author 1"    |
+---------+---------------+
| "msg 1" | "author 2"    |
+---------+---------------+

Edit: Worth noting though.. structs get force-converted to maps, so things like Ecto preloads will probably break.

hpopp avatar Mar 16 '17 21:03 hpopp

Forced map-conversion has now been removed in v0.4.0. Structs behave like structs now, and you can do things like Ecto preloads.

hpopp avatar Mar 17 '17 00:03 hpopp

I have few ideas here …

1. Separate maps in lists like:

# 1
+------------------+
| :msg             |
+------------------+
| "msg 1"          |
+------------------+
| :author => :name |
+------------------+
| "author 1"       |
+------------------+

# 2
+------------------+
| :msg             |
+------------------+
| "msg 2"          |
+------------------+
| :author => :name |
+------------------+
| "author 2"       |
+------------------+

This is useful when you have small amounts of items to list and if we have lots of nested data.

2. All data in one table:

+---------+------------------+
| :msg    | :author => :name |
+---------+------------------+
| "msg 1" | "author 1"       |
+---------+------------------+
| "msg 1" | "author 2"       |
+---------+------------------+

This is useful for your examples. :smile:

3. Use &Kernel.get_in/2

So we can make your example much simpler:

[post_1, post_2] |> Scribe.print(%{"Author name => [:author, :name]})

We can also name it automatically (just convert it to string and upcase first character), so finally shortest example could be:

[post_1, post_2] |> Scribe.print([[:author, :name]])

4. We could also support Emacs

You can run a program inside an Emacs window, and Emacs displays it in a dumb terminal of infinite width and height. If the cursor is at the end of the buffer, the window will scroll as the program produces output; if you move the cursor around, the window will stay put as the output grows.

Source: https://unix.stackexchange.com/a/70572

Notes

  1. In my examples: :author => :name are like: :parent_key => :current_key and by default it should not adds parents of (here) :author parent.

Eiji7 avatar Feb 16 '18 22:02 Eiji7

Funny enough you can make a function column a Scribe.print/2 call and it will print another table below. Still not sure I'm sold on the vertical list yet.

I really like the idea of auto-flattening all of the keys, but I'll have to think on the API for #3. Obviously get_in/2 will fail for most structs, and forced map conversion will lose nice things like preloads. I'm not sure there's a right answer here, but I'll think on it.

Also, Scribe allows infinite width tables by default, or rather, as wide as the columns need to be to avoid truncating. I don't know much about emacs (vim guy), so I'm not sure what would need to be changed.

hpopp avatar Feb 17 '18 20:02 hpopp

@hpopp: I'm not sure what you mean by forced map conversion and why get_in will fail for most structs.

I don't know much about both emacs and vim - I have only found this answer and for me it looks really interesting at least for consider adding support for it (of course if possible).

Update: I found this article. Hope it will help you.

Eiji7 avatar Feb 17 '18 23:02 Eiji7

get_in requires Access behaviour yeah? Early versions of Scribe would force-convert structs to maps by stripping out the :__struct__ key, but now since that's no longer the case, it should fail for anything that doesn't implement Access

hpopp avatar Feb 18 '18 00:02 hpopp

@hpopp: Example code;

get_in(%{a: %{b: %{c: %{d: 5}}}}, [:a, :b, :c, :d])
# returns: 5

It works with structs as well.

Eiji7 avatar Feb 19 '18 14:02 Eiji7

Yes, only if they implement the Access behaviour

Edit: it's the main reason I wrote this package

hpopp avatar Feb 19 '18 15:02 hpopp

@hpopp: Ok, I missed that. How about this example:

defmodule MyLib do
  def my_get_in(map_or_struct, data) do
    new_data = Enum.map(data, &map_data/1)
    get_in(map_or_struct, new_data)
  end

  defp map_data(function) when is_function(function), do: function
  defp map_data(value), do: Access.key(value)
end

defmodule Example do
  defstruct [:sample]
end

MyLib.my_get_in(%Example{sample: 5}, [:sample])                 
# returns: 5
MyLib.my_get_in(%{sample: 5}, [:sample])       
# returns: 5

Eiji7 avatar Feb 19 '18 18:02 Eiji7

Is the preferred method for nested maps then to extract those maps and create separate Scribe tables?

[
  %{
    hostname: 'scanme.nmap.org',
    ip: '45.33.32.156',
    ports: [
      %{
        id: '22',
        name: 'ssh',
        product: 'OpenSSH',
        version: '6.6.1p1 Ubuntu 2ubuntu2.11'
      },
      %{id: '80', name: 'http', product: 'Apache httpd', version: '2.4.7'},
      %{id: '2000', name: 'tcpwrapped', product: nil, version: nil},
      %{id: '5060', name: 'tcpwrapped', product: nil, version: nil},
      %{
        id: '8008',
        name: 'http',
        product: 'Fortinet FortiGuard block page',
        version: nil
      },
      %{
        id: '8010',
        name: 'http-proxy',
        product: 'FortiGate Web Filtering Service',
        version: nil
      },
      %{id: '9929', name: 'nping-echo', product: 'Nping echo', version: nil},
      %{id: '31337', name: 'tcpwrapped', product: nil, version: nil}
    ]
  }
]

ghost avatar Feb 11 '19 14:02 ghost

Yup, though in your case I would print the ports list and flatten hostname and ip into it:

iex(7)> Scribe.print(data[:ports], data: [:id, :name, :product, :version, {:hostname, fn _ -> data[:hostname] end}, {:ip, fn _ -> data[:ip] end}])
+-----------+----------------+-------------------------------------+--------------------------------+---------------------+------------------+
| :id       | :name          | :product                            | :version                       | :hostname           | :ip              |
+-----------+----------------+-------------------------------------+--------------------------------+---------------------+------------------+
| '22'      | 'ssh'          | 'OpenSSH'                           | '6.6.1p1 Ubuntu 2ubuntu2.11'   | 'scanme.nmap.org'   | '45.33.32.156'   |
| '80'      | 'http'         | 'Apache httpd'                      | '2.4.7'                        | 'scanme.nmap.org'   | '45.33.32.156'   |
| '2000'    | 'tcpwrapped'   | nil                                 | nil                            | 'scanme.nmap.org'   | '45.33.32.156'   |
| '5060'    | 'tcpwrapped'   | nil                                 | nil                            | 'scanme.nmap.org'   | '45.33.32.156'   |
| '8008'    | 'http'         | 'Fortinet FortiGuard block page'    | nil                            | 'scanme.nmap.org'   | '45.33.32.156'   |
| '8010'    | 'http-proxy'   | 'FortiGate Web Filtering Service'   | nil                            | 'scanme.nmap.org'   | '45.33.32.156'   |
| '9929'    | 'nping-echo'   | 'Nping echo'                        | nil                            | 'scanme.nmap.org'   | '45.33.32.156'   |
| '31337'   | 'tcpwrapped'   | nil                                 | nil                            | 'scanme.nmap.org'   | '45.33.32.156'   |
+-----------+----------------+-------------------------------------+--------------------------------+---------------------+------------------+

hpopp avatar Feb 11 '19 18:02 hpopp

Beautiful. Thanks

ghost avatar Feb 12 '19 09:02 ghost

It's looking pretty good. Thanks. screen shot 2019-02-13 at 17 04 56

ghost avatar Feb 13 '19 17:02 ghost