Packages
pages
0.2.6
4.1.0
4.0.0
3.5.0
3.4.2
3.4.1
3.4.0
3.3.0
3.2.0
3.1.0
3.0.1
3.0.0
2.3.0
2.2.1
2.2.0
2.1.1
2.1.0
2.0.0
1.3.1
1.3.0
1.2.0
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.14.0
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.1
0.11.0
0.10.1
0.10.0
0.9.0
0.8.0
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.1
0.3.0
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Page pattern for interacting with web pages
Current section
Files
Jump to
Current section
Files
lib/pages/css.ex
defmodule Pages.Css do
# @related [test](/test/pages/css_test.exs)
@moduledoc """
Constructs CSS selectors via Elixir data structures. See `selector/1` for details.
"""
@type selector() :: binary() | atom() | list()
@doc ~S"""
Accepts a string, atom, or list and returns a CSS string.
## String syntax
When given a string, returns the string. This is useful when you don't know if a selector is already a string.
```elixir
iex> Pages.Css.selector(".profile[test-role='new-members']")
".profile[test-role='new-members']"
```
## Keyword list syntax
_The keyword list syntax is intentionally limited; complex selectors are more
easily written as strings._
The keyword list syntax makes it a bit eaiser to write simple selectors or selectors that use variables:
e.g.:
```elixir
Pages.Css.selector(test_role: "new-members")
Pages.Css.selector(id: some_variable)
```
Keys are expected to be atoms and will be dasherized (`foo_bar` -> `foo-bar`).
Values are expected to be strings or another keyword list.
A key/value pair will be converted to an attribute selector:
```elixir
iex> Pages.Css.selector(test_role: "new-members")
"[test-role='new-members']"
```
A keyword list will be converted to a list of attribute selectors:
```elixir
iex> Pages.Css.selector(class: "profile", test_role: "new-members")
"[class='profile'][test-role='new-members']"
```
(Note that the CSS selector `.profile` expands to `[class~='profile']` which is not equivalent to
`[class='profile']`. The keyword list syntax will not generate `~=` selectors so you should use a
string selector such as `".profile[test-role='new-members']"` instead if you want `~=` semantics. See the
[CSS spec](https://www.w3.org/TR/CSS21/selector.html#attribute-selectors) for details.)
When the value is a keyword list, the key is converted to an element selector:
```elixir
iex> Pages.Css.selector(p: [class: "profile", test_role: "new-members"])
"p[class='profile'][test-role='new-members']"
```
## Regular (non-keyword) list syntax
_The list syntax is intentionally limited; complex selectors are more
easily written as strings._
When the value is a regular (non-keyword) list, atoms are converted to element selectors and
keyword lists are converted as described above.
```elixir
iex> Pages.Css.selector([[p: [class: "profile", test_role: "new-members"]], :div, [class: "tag"]])
"p[class='profile'][test-role='new-members'] div [class='tag']"
```
"""
@spec selector(selector()) :: binary()
def selector(input) when is_binary(input), do: input
def selector(input) when is_atom(input), do: input |> to_string() |> selector()
def selector(input) when is_list(input), do: reduce(input) |> Moar.String.squish()
@deprecated "Use `selector/1` instead"
@spec query(selector()) :: binary()
def query(input), do: selector(input)
defp reduce(input, result \\ "")
defp reduce([head | tail], result), do: result <> reduce(head) <> reduce(tail)
defp reduce({k, v}, result) when is_atom(k), do: reduce({k |> to_string() |> Moar.String.dasherize(), v}, result)
defp reduce({k, v}, result) when is_list(v), do: "#{result} #{k}#{reduce(v)}"
defp reduce({_k, false}, result), do: result
defp reduce({k, true}, result), do: "#{result}[#{k}]"
defp reduce({k, v}, result), do: "#{result}[#{k}='#{v}']"
defp reduce(term, result), do: "#{result} #{term} "
end