diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7bbad67 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI +on: [push, pull_request] +jobs: + build: + strategy: + matrix: + # https://hexdocs.pm/elixir/compatibility-and-deprecations.html + # https://github.com/erlef/setup-beam#compatibility-between-operating-system-and-erlangotp + elixir: ["1.13"] + otp: ["24", "23", "22"] + os: ["ubuntu-20.04"] + include: + - { elixir: "1.12", otp: "24", os: "ubuntu-20.04" } + - { elixir: "1.11", otp: "24", os: "ubuntu-20.04" } + - { elixir: "1.10", otp: "23", os: "ubuntu-20.04" } + - { elixir: "1.9", otp: "22", os: "ubuntu-20.04" } + - { elixir: "1.8", otp: "22", os: "ubuntu-20.04" } + - { elixir: "1.7", otp: "22", os: "ubuntu-20.04" } + - { elixir: "1.6", otp: "21", os: "ubuntu-20.04" } + - { elixir: "1.5", otp: "20", os: "ubuntu-20.04" } + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: erlef/setup-beam@v1.9.0 + with: + otp-version: ${{ matrix.otp }} + elixir-version: ${{ matrix.elixir }} + - run: mix compile --warnings-as-errors + - run: mix test diff --git a/README.livemd b/README.livemd new file mode 100644 index 0000000..4cf2d9d --- /dev/null +++ b/README.livemd @@ -0,0 +1,257 @@ + + + + +# ExSamples Guide + +## Setup + +```elixir +Mix.install([ + :exsamples +]) +``` + +```output +:ok +``` + +## Usage + +Initializes lists of maps, structs or keyword lists using tabular data in Elixir. + +ExSamples helps you to describe data of the same type in a more **compact** and **readable** way. Specially useful when defining sample data (e.g. for tests). Here is an example: + + + +```elixir +import ExSamples + +countries = samples do + :id | :name | :currency | :language | :population + 1 | "Brazil" | "Real (BRL)" | "Portuguese" | 204_451_000 + 2 | "United States" | "United States Dollar (USD)" | "English" | 321_605_012 + 3 | "Austria" | "Euro (EUR)" | "German" | 8_623_073 + 4 | "Sweden" | "Swedish krona (SEK)" | "Swedish" | 9_801_616 +end + +countries |> Enum.at(1) +``` + +```output +%{ + currency: "United States Dollar (USD)", + id: 2, + language: "English", + name: "United States", + population: 321605012 +} +``` + + + +```elixir +import ExSamples + +users = + samples do + :name | :country | :city | :admin + "Christian" | "United States" | "New York City" | false + "Peter" | "Germany" | "Berlin" | true + "José" | "Brazil" | "São Paulo" | false + "Ingrid" | "Austria" | "Salzburg" | false + "Lucas" | "Brazil" | "Fortaleza" | true + end +``` + +```output +[ + %{admin: false, city: "New York City", country: "United States", name: "Christian"}, + %{admin: true, city: "Berlin", country: "Germany", name: "Peter"}, + %{admin: false, city: "São Paulo", country: "Brazil", name: "José"}, + %{admin: false, city: "Salzburg", country: "Austria", name: "Ingrid"}, + %{admin: true, city: "Fortaleza", country: "Brazil", name: "Lucas"} +] +``` + +As you can see, after macro expansion you get a regular list. + +You can use `for` comprehensions for mapping and filtering your data just like with any other Enumerable. + +```elixir +for %{name: name, country: country, city: city} <- users, country == "Brazil" do + {name, city} +end +``` + +```output +[{"José", "São Paulo"}, {"Lucas", "Fortaleza"}] +``` + +## Data Types + +By default `samples` initializes a list of maps. But you can also define structs and keyword lists. + +### Initializing structs + + + +```elixir +import ExSamples + +defmodule Country do + defstruct [:id, :name, :currency, :language, :population] +end +``` + +```output +{:module, Country, <<70, 79, 82, 49, 0, 0, 7, ...>>, + %Country{currency: nil, id: nil, language: nil, name: nil, population: nil}} +``` + +```elixir +samples as: Country do + :id | :name | :currency | :language | :population + 1 | "Brazil" | "Real (BRL)" | "Portuguese" | 204_451_000 + 2 | "United States" | "United States Dollar (USD)" | "English" | 321_605_012 +end +``` + +```output +[ + %Country{ + currency: "Real (BRL)", + id: 1, + language: "Portuguese", + name: "Brazil", + population: 204451000 + }, + %Country{ + currency: "United States Dollar (USD)", + id: 2, + language: "English", + name: "United States", + population: 321605012 + } +] +``` + +### Initializing keyword lists + + + +```elixir +import ExSamples + +samples as: [] do + :id | :name | :currency | :language | :population + 3 | "Austria" | "Euro (EUR)" | "German" | 8_623_073 + 4 | "Sweden" | "Swedish krona (SEK)" | "Swedish" | 9_801_616 +end +``` + +```output +[ + [id: 3, name: "Austria", currency: "Euro (EUR)", language: "German", population: 8623073], + [id: 4, name: "Sweden", currency: "Swedish krona (SEK)", language: "Swedish", population: 9801616] +] +``` + +### Assigning variables as structs + + + +```elixir +import ExSamples + +defmodule Country do + defstruct [:name, :currency, :language] +end + +defmodule User do + defstruct [:id, :name, :country, :admin, :last_login] +end +``` + +```output +{:module, User, <<70, 79, 82, 49, 0, 0, 7, ...>>, + %User{admin: nil, country: nil, id: nil, last_login: nil, name: nil}} +``` + +```elixir +samples do + Country | :name | :currency | :language + country1 | "Brazil" | "Real (BRL)" | "Portuguese" + country2 | "United States" | "United States Dollar (USD)" | "English" + country3 | "Austria" | "Euro (EUR)" | "German" +end + +samples do + User | :id | :name | :country | :admin | :last_login + user1 | 16 | "Lucas" | country1 | false | {2015, 10, 08} + user2 | 327 | "Ingrid" | country3 | true | {2014, 09, 12} + user3 | 34 | "Christian" | country2 | false | {2015, 01, 24} +end + +user1 +``` + +```output +%User{ + admin: false, + country: %Country{currency: "Real (BRL)", language: "Portuguese", name: "Brazil"}, + id: 16, + last_login: {2015, 10, 8}, + name: "Lucas" +} +``` + +```elixir +IO.puts("Name: #{user1.name}, Country: #{user1.country.name}") +``` + +```output +Name: Lucas, Country: Brazil +``` + +```output +:ok +``` + +### Assigning variables as maps + + + +```elixir +import ExSamples + +samples do + %{} | :name | :country | :city + user1 | "Christian" | "United States" | "New York City" + user2 | "Ingrid" | "Austria" | "Salzburg" +end + +user1 +``` + +```output +%{city: "New York City", country: "United States", name: "Christian"} +``` + +### Assigning variables as keyword lists + + + +```elixir +samples do + [] | :name | :country | :city + user1 | "Christian" | "United States" | "New York City" + user2 | "Ingrid" | "Austria" | "Salzburg" +end + +user1 +``` + +```output +[name: "Christian", country: "United States", city: "New York City"] +``` diff --git a/README.md b/README.md index 88bdcd1..72c2ce8 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,10 @@ Run `mix deps.get`. ## Usage +You can see it in action with [livebook](https://livebook.dev/) with [README.livemd](README.livemd) + +[![Run in Livebook](https://livebook.dev/badge/v1/blue.svg)](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fmsaraiva%2Fexsamples%2Fblob%2Fmaster%2FREADME.livemd) + ```Elixir import ExSamples diff --git a/lib/samples.ex b/lib/samples.ex index f976153..e80e1e0 100644 --- a/lib/samples.ex +++ b/lib/samples.ex @@ -33,7 +33,7 @@ defmodule Samples do end defp slice_table(table) do - [header|rows] = table + [header|rows] = extract_header_rows(table) {type, fields} = extract_type_and_fields(header) {vars, fields_values} = extract_vars_and_fields_values(type, rows) @@ -61,10 +61,17 @@ defmodule Samples do end end + defp extract_header_rows([]), do: [[nil]] + defp extract_header_rows(table), do: table + def extract_type_and_fields([type = {atom, _, []}|fields]) when atom == :%{} do {type, fields} end + def extract_type_and_fields([{:__aliases__, _, [_]} = type | fields]) do + {type, fields} + end + def extract_type_and_fields(fields = [{field, [_], _}|_]) when is_atom(field) do {nil, Enum.map(fields, fn {field, [_], _} -> field end)} end @@ -98,6 +105,10 @@ defmodule Samples do {:%, [], [{:__aliases__, [], [module]}, {:%{}, [], value}]} end + defp replace_value({:__aliases__, [line: _], [module]}, value) do + {:%, [], [{:__aliases__, [], [module]}, {:%{}, [], value}]} + end + # As structs defp replace_value({:%, meta, [lhs, {:%{}, _, _value}]}, value) do {:%, meta, [lhs, {:%{}, [], value}]} diff --git a/mix.exs b/mix.exs index 3e76001..98159c7 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule ExSamples.Mixfile do def project do [app: :exsamples, version: "0.1.0", - elixir: "~> 1.1", + elixir: "~> 1.5", description: description(), package: package(), deps: deps()]