scribe
scribe copied to clipboard
nested maps or lists
I wonder is there merit in handling nested maps and/or lists...
You mean tables within tables or accessing nested data?
@hpopp Yes...
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.
Forced map-conversion has now been removed in v0.4.0. Structs behave like structs now, and you can do things like Ecto preloads.
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
- In my examples:
:author => :nameare like::parent_key => :current_keyand by default it should not adds parents of (here):authorparent.
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: 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.
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: Example code;
get_in(%{a: %{b: %{c: %{d: 5}}}}, [:a, :b, :c, :d])
# returns: 5
It works with structs as well.
@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
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}
]
}
]
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' |
+-----------+----------------+-------------------------------------+--------------------------------+---------------------+------------------+
Beautiful. Thanks
It's looking pretty good. Thanks.
