1defprotocol IEx.Info do 2 @fallback_to_any true 3 4 @spec info(term()) :: [{info_name :: String.Chars.t(), info :: String.t()}] 5 def info(term) 6end 7 8defimpl IEx.Info, for: Tuple do 9 def info(_tuple) do 10 [ 11 {"Data type", "Tuple"}, 12 {"Reference modules", "Tuple"} 13 ] 14 end 15end 16 17defimpl IEx.Info, for: Atom do 18 def info(atom) do 19 specific_info = 20 cond do 21 module?(atom) -> 22 info_module(atom) 23 24 match?("Elixir." <> _, Atom.to_string(atom)) -> 25 info_module_like_atom(atom) 26 27 true -> 28 info_atom(atom) 29 end 30 31 description = 32 if atom == IEx.dont_display_result() do 33 description = """ 34 This atom is returned by IEx when a function that should not print its 35 return value on screen is executed. 36 """ 37 38 [{"Description", description}] 39 else 40 [] 41 end 42 43 [{"Data type", "Atom"}] ++ description ++ specific_info 44 end 45 46 defp module?(atom) do 47 case :code.get_object_code(atom) do 48 :error -> 49 Code.ensure_loaded?(atom) 50 51 {^atom, beam, _path} -> 52 info = :beam_lib.info(beam) 53 Keyword.fetch(info, :module) == {:ok, atom} 54 end 55 end 56 57 defp info_module(mod) do 58 extra = 59 case Code.fetch_docs(mod) do 60 {:docs_v1, _, _, _, %{}, _, _} -> "Use h(#{inspect(mod)}) to access its documentation.\n" 61 _ -> "" 62 end 63 64 mod_info = mod.module_info() 65 66 generic_info = [ 67 {"Module bytecode", module_object_file(mod)}, 68 {"Source", module_source_file(mod_info)}, 69 {"Version", module_version(mod_info)}, 70 {"Compile options", module_compile_options(mod_info)}, 71 {"Description", "#{extra}Call #{inspect(mod)}.module_info() to access metadata."} 72 ] 73 74 final_info = [ 75 {"Raw representation", ":" <> inspect(Atom.to_string(mod))}, 76 {"Reference modules", "Module, Atom"} 77 ] 78 79 generic_info ++ protocol_info(mod) ++ final_info 80 end 81 82 defp protocol_info(mod) do 83 if function_exported?(mod, :__protocol__, 1) do 84 impls = 85 mod 86 |> Protocol.extract_impls(:code.get_path()) 87 |> Enum.map_join(", ", &inspect/1) 88 89 [{"Protocol", "This module is a protocol. These data structures implement it:\n #{impls}"}] 90 else 91 [] 92 end 93 end 94 95 defp info_module_like_atom(atom) do 96 [ 97 {"Raw representation", ":" <> inspect(Atom.to_string(atom))}, 98 {"Reference modules", "Atom"} 99 ] 100 end 101 102 defp info_atom(_atom) do 103 [{"Reference modules", "Atom"}] 104 end 105 106 defp module_object_file(mod) do 107 default_or_apply(:code.which(mod), fn 108 [_ | _] = path -> Path.relative_to_cwd(path) 109 atom -> inspect(atom) 110 end) 111 end 112 113 defp module_version(mod_info) do 114 default_or_apply(mod_info[:attributes][:vsn], &inspect/1) 115 end 116 117 defp module_source_file(mod_info) do 118 default_or_apply(mod_info[:compile][:source], &Path.relative_to_cwd/1) 119 end 120 121 defp module_compile_options(mod_info) do 122 default_or_apply(mod_info[:compile][:options], &inspect/1) 123 end 124 125 defp default_or_apply(nil, _), do: "no value found" 126 defp default_or_apply(data, fun), do: fun.(data) 127end 128 129defimpl IEx.Info, for: List do 130 def info(list) do 131 specific_info = 132 cond do 133 list == [] -> info_list(list) 134 List.ascii_printable?(list) -> info_printable_charlist(list) 135 Keyword.keyword?(list) -> info_kw_list(list) 136 List.improper?(list) -> info_improper_list(list) 137 true -> info_list(list) 138 end 139 140 [{"Data type", "List"}] ++ specific_info 141 end 142 143 defp info_printable_charlist(charlist) do 144 description = """ 145 This is a list of integers that is printed as a sequence of characters 146 delimited by single quotes because all the integers in it represent printable 147 ASCII characters. Conventionally, a list of Unicode code points is known as a 148 charlist and a list of ASCII characters is a subset of it. 149 """ 150 151 [ 152 {"Description", description}, 153 {"Raw representation", inspect(charlist, charlists: :as_lists)}, 154 {"Reference modules", "List"} 155 ] 156 end 157 158 defp info_kw_list(_kw_list) do 159 description = """ 160 This is what is referred to as a "keyword list". A keyword list is a list 161 of two-element tuples where the first element of each tuple is an atom. 162 """ 163 164 [{"Description", description}, {"Reference modules", "Keyword, List"}] 165 end 166 167 defp info_improper_list(_improper_list) do 168 description = """ 169 This is what is referred to as an "improper list". An improper list is a 170 list which its last tail is not to an empty list. For example: [1, 2, 3] 171 is a proper list, as it is equivalent to [1, 2, 3 | []], as opposed to 172 [1, 2 | 3] which is an improper list since its last tail returns 3. 173 """ 174 175 [ 176 {"Description", description}, 177 {"Reference modules", "List"} 178 ] 179 end 180 181 defp info_list(_list) do 182 [{"Reference modules", "List"}] 183 end 184end 185 186defimpl IEx.Info, for: BitString do 187 def info(bitstring) do 188 specific_info = 189 cond do 190 is_binary(bitstring) and String.printable?(bitstring) -> info_string(bitstring) 191 is_binary(bitstring) and String.valid?(bitstring) -> info_non_printable_string(bitstring) 192 is_binary(bitstring) -> info_binary(bitstring) 193 is_bitstring(bitstring) -> info_bitstring(bitstring) 194 end 195 196 [{"Data type", "BitString"}] ++ specific_info 197 end 198 199 defp info_string(bitstring) do 200 description = """ 201 This is a string: a UTF-8 encoded binary. It's printed surrounded by 202 "double quotes" because all UTF-8 encoded code points in it are printable. 203 """ 204 205 [ 206 {"Byte size", byte_size(bitstring)}, 207 {"Description", description}, 208 {"Raw representation", inspect(bitstring, binaries: :as_binaries)}, 209 {"Reference modules", "String, :binary"} 210 ] 211 end 212 213 defp info_non_printable_string(bitstring) do 214 first_non_printable = find_first_codepoint(bitstring, &(not String.printable?(&1))) 215 216 desc = """ 217 This is a string: a UTF-8 encoded binary. It's printed with the `<<>>` 218 syntax (as opposed to double quotes) because it contains non-printable 219 UTF-8 encoded code points (the first non-printable code point being 220 `#{inspect(first_non_printable)}`). 221 """ 222 223 [ 224 {"Byte size", byte_size(bitstring)}, 225 {"Description", desc}, 226 {"Reference modules", "String, :binary"} 227 ] 228 end 229 230 defp info_binary(bitstring) do 231 first_non_valid = find_first_codepoint(bitstring, &(not String.valid?(&1))) 232 233 description = """ 234 This is a binary: a collection of bytes. It's printed with the `<<>>` 235 syntax (as opposed to double quotes) because it is not a UTF-8 encoded 236 binary (the first invalid byte being `#{inspect(first_non_valid)}`) 237 """ 238 239 [ 240 {"Byte size", byte_size(bitstring)}, 241 {"Description", description}, 242 {"Reference modules", ":binary"} 243 ] 244 end 245 246 defp info_bitstring(bitstring) do 247 description = """ 248 This is a bitstring. It's a chunk of bits that are not divisible by 8 249 (the number of bytes isn't whole). 250 """ 251 252 [{"Bits size", bit_size(bitstring)}, {"Description", description}] 253 end 254 255 defp find_first_codepoint(binary, fun) do 256 binary 257 |> String.codepoints() 258 |> Enum.find(fun) 259 end 260end 261 262defimpl IEx.Info, for: Integer do 263 def info(_integer) do 264 [{"Data type", "Integer"}, {"Reference modules", "Integer"}] 265 end 266end 267 268defimpl IEx.Info, for: Float do 269 def info(_float) do 270 [{"Data type", "Float"}, {"Reference modules", "Float"}] 271 end 272end 273 274defimpl IEx.Info, for: Function do 275 def info(fun) do 276 fun_info = Function.info(fun) 277 278 specific_info = 279 if fun_info[:type] == :external and fun_info[:env] == [] do 280 info_named_fun(fun_info) 281 else 282 info_anon_fun(fun_info) 283 end 284 285 [{"Data type", "Function"}] ++ specific_info 286 end 287 288 defp info_anon_fun(fun_info) do 289 [ 290 {"Type", to_string(fun_info[:type])}, 291 {"Arity", fun_info[:arity]}, 292 {"Description", "This is an anonymous function."} 293 ] 294 end 295 296 defp info_named_fun(fun_info) do 297 [ 298 {"Type", to_string(fun_info[:type])}, 299 {"Arity", fun_info[:arity]} 300 ] 301 end 302end 303 304defimpl IEx.Info, for: PID do 305 @keys [:registered_name, :links, :message_queue_len] 306 307 def info(pid) do 308 extra_info = 309 case :rpc.pinfo(pid, @keys) do 310 [_ | _] = info -> 311 [ 312 {"Alive", true}, 313 {"Name", process_name(info[:registered_name])}, 314 {"Links", links(info[:links])}, 315 {"Message queue length", info[:message_queue_len]} 316 ] 317 318 _ -> 319 [{"Alive", false}] 320 end 321 322 final_info = [ 323 {"Description", "Use Process.info/1 to get more info about this process"}, 324 {"Reference modules", "Process, Node"} 325 ] 326 327 [{"Data type", "PID"}] ++ extra_info ++ final_info 328 end 329 330 defp process_name([]), do: "not registered" 331 defp process_name(name), do: inspect(name) 332 333 defp links([]), do: "none" 334 defp links(links), do: Enum.map_join(links, ", ", &inspect/1) 335end 336 337defimpl IEx.Info, for: Map do 338 def info(_map) do 339 [{"Data type", "Map"}, {"Reference modules", "Map"}] 340 end 341end 342 343defimpl IEx.Info, for: Port do 344 def info(port) do 345 connected = :rpc.call(node(port), :erlang, :port_info, [port, :connected]) 346 347 [ 348 {"Data type", "Port"}, 349 {"Open", match?({:connected, _}, connected)}, 350 {"Reference modules", "Port"} 351 ] 352 end 353end 354 355defimpl IEx.Info, for: Reference do 356 def info(_ref) do 357 [{"Data type", "Reference"}] 358 end 359end 360 361defimpl IEx.Info, for: [Date, Time, NaiveDateTime] do 362 {sigil, repr} = 363 case @for do 364 Date -> {"D", "date"} 365 Time -> {"T", "time"} 366 NaiveDateTime -> {"N", ~S{"naive" datetime (that is, a datetime without a time zone)}} 367 end 368 369 def info(value) do 370 description = """ 371 This is a struct representing a #{unquote(repr)}. It is commonly 372 represented using the `~#{unquote(sigil)}` sigil syntax, that is 373 defined in the `Kernel.sigil_#{unquote(sigil)}/2` macro. 374 """ 375 376 [ 377 {"Data type", inspect(@for)}, 378 {"Description", description}, 379 {"Raw representation", raw_inspect(value)}, 380 {"Reference modules", inspect(@for) <> ", Calendar, Map"} 381 ] 382 end 383 384 defp raw_inspect(value) do 385 value 386 |> Inspect.Any.inspect(%Inspect.Opts{}) 387 |> Inspect.Algebra.format(:infinity) 388 |> IO.iodata_to_binary() 389 end 390end 391 392defimpl IEx.Info, for: Any do 393 def info(%module{}) do 394 [ 395 {"Data type", inspect(module)}, 396 {"Description", "This is a struct. Structs are maps with a __struct__ key."}, 397 {"Reference modules", inspect(module) <> ", Map"} 398 ] 399 end 400end 401