utils.ex (3040B)
1 defmodule ExDoc.Utils do 2 @moduledoc false 3 4 @doc """ 5 HTML escapes the given string. 6 7 iex> ExDoc.Utils.h("<foo>") 8 "<foo>" 9 10 """ 11 def h(string) do 12 String.replace(string, ~w|& < > "|, fn 13 "&" -> "&" 14 "<" -> "<" 15 ">" -> ">" 16 ~S(") -> """ 17 end) 18 end 19 20 @doc """ 21 Sorts mapped strings by natural order. 22 """ 23 def natural_sort_by(enumerable, mapper) when is_function(mapper, 1) do 24 Enum.sort_by(enumerable, fn elem -> 25 elem 26 |> mapper.() 27 |> to_sortable_charlist() 28 end) 29 end 30 31 defp to_sortable_charlist(string) do 32 string 33 |> :unicode.characters_to_nfkd_list() 34 |> make_sortable() 35 end 36 37 @offset -1_000_000_000 38 39 # Numbers come first, so group and pad them with offset 40 defp make_sortable([digit | chars]) when digit in ?0..?9 do 41 {digits, chars} = Enum.split_while(chars, &(&1 in ?0..?9)) 42 [@offset + List.to_integer([digit | digits]) | make_sortable(chars)] 43 end 44 45 # Then Elixir special punctuation - trailing bang `!` 46 defp make_sortable([?! | chars]), do: [?0 | make_sortable(chars)] 47 48 # Then Elixir special punctuation - question mark `?` 49 defp make_sortable([?? | chars]), do: [?1 | make_sortable(chars)] 50 51 # Then underscore 52 defp make_sortable([?_ | chars]), do: [?2 | make_sortable(chars)] 53 54 # Then uppercased letters and lowercased letters 55 defp make_sortable([char | chars]) when char in ?a..?z do 56 [char - 31.5 | make_sortable(chars)] 57 end 58 59 defp make_sortable([char | chars]), do: [char | make_sortable(chars)] 60 defp make_sortable([]), do: [] 61 62 @doc """ 63 A very simple JSON encoder. 64 65 We want to minimize the number of dependencies ExDoc has, 66 because we don't want someone to be allowed to not upgrade 67 their app due to an ExDoc restriction, so we ship with a 68 simple JSON implementation. 69 """ 70 def to_json(nil), do: "null" 71 def to_json(true), do: "true" 72 def to_json(false), do: "false" 73 74 def to_json(map) when is_map(map) do 75 mapped = 76 Enum.map_intersperse(Map.to_list(map), ?,, fn {key, value} -> 77 [key |> Atom.to_string() |> inspect(), ?:, to_json(value)] 78 end) 79 80 [?{, mapped, ?}] 81 end 82 83 def to_json(list) when is_list(list) do 84 mapped = Enum.map_intersperse(list, ?,, &to_json/1) 85 [?[, mapped, ?]] 86 end 87 88 def to_json(atom) when is_atom(atom) do 89 atom |> Atom.to_string() |> inspect() 90 end 91 92 def to_json(binary) when is_binary(binary) do 93 inspect(binary, printable_limit: :infinity) 94 end 95 96 def to_json(integer) when is_integer(integer) do 97 Integer.to_string(integer) 98 end 99 100 @doc """ 101 Generates a url based on the given pattern. 102 """ 103 def source_url_pattern(source_url_pattern, path, line) 104 when is_binary(path) and is_integer(line) do 105 cond do 106 is_function(source_url_pattern) -> 107 source_url_pattern.(path, line) 108 109 source_url_pattern -> 110 source_url_pattern 111 |> String.replace("%{path}", path) 112 |> String.replace("%{line}", Integer.to_string(line)) 113 114 true -> 115 nil 116 end 117 end 118 end