Skip to content

Commit

Permalink
POWER of the PRISM
Browse files Browse the repository at this point in the history
  • Loading branch information
Coburn Berry committed Jan 11, 2018
0 parents commit d1b4e0c
Show file tree
Hide file tree
Showing 66 changed files with 2,044 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Prismic

**TODO: Add description**

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `prismic` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:prismic, "~> 0.1.0"}
]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/prismic](https://hexdocs.pm/prismic).

32 changes: 32 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure your application as:
#
# config :prismic, key: :value
#
# and access this configuration in your application as:
#
# Application.get_env(:prismic, :key)
#
# You can also configure a 3rd-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
config :prismic,
repo_url: "https://micro.cdn.prismic.io/api"
5 changes: 5 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use Mix.Config

config :prismic,
http_client_module: Prismic.HTTPClient.Echo,
cache_module: Prismic.Cache.Echo
5 changes: 5 additions & 0 deletions lib/alternate_language.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Prismic.AlternateLanguage do
defstruct [:id, :uid, :type, :lang]

@type t :: %__MODULE__{id: String.t(), uid: String.t(), type: String.t(), lang: String.t()}
end
35 changes: 35 additions & 0 deletions lib/api.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule Prismic.API do
@moduledoc """
Entry point for the prismic api. The overall flow is to retrieve a fresh copy of the api, select a form ( default "Everything" ), add queries, choose a ref ( default "Master" ), and submit
"""
alias Prismic.{API, Form, Ref}

defstruct [:repository_url, :access_token, :refs, :forms, :bookmarks]

@type t :: %__MODULE__{
repository_url: String.t(),
access_token: String.t(),
refs: [%Ref{}],
forms: Map.t(),
bookmarks: Map.t()
}

#TODO: this should take a token also
@doc """
Retrieve api entrypoint from a given url and authentication token
"""
@spec new(String.t, String.t) :: {:ok, %API{}} | {:error, any}
def new(json, repo_url) do
json
|> Poison.decode!(as: %API{repository_url: repo_url, refs: [%Ref{}]}, keys: :atoms)
|> Map.update!(:forms, fn form_map ->
for {form_name, form} <- form_map, do: {form_name, struct(Form, form)}
end)
end

# TODO: this should be a function in the Ref module
@spec find_ref(%API{}, String.t()) :: %Ref{} | nil
def find_ref(%API{refs: refs}, label) do
Enum.find(refs, &(&1.label == label))
end
end
20 changes: 20 additions & 0 deletions lib/application.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule Prismic.Application do
use Application

# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Supervisor.Spec

cache_module = Application.get_env(:prismic, :cache_module)
children = if is_nil(cache_module) do
#start default cache if not provided
[worker(Prismic.Cache.Default, [], id: :prismic_cache) ]
else
[]
end

opts = [strategy: :one_for_one, name: Prismic.Supervisor]
Supervisor.start_link(children, opts)
end
end
78 changes: 78 additions & 0 deletions lib/cache.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
defmodule Prismic.Cache do
@moduledoc """
Behavior to be implemented by injectable caches
"""

@callback get(String.t) :: any
@callback set(String.t, any) :: any
@callback get_or_store(String.t, any) :: any

def cache_module do
Application.get_env(:prismic, :cache_module) || Prismic.Cache.Default
end

def get(key), do: cache_module().get(key)

def set(key, value), do: cache_module().set(key, value)

def get_or_store(key, fun) when is_function(fun) do
cache_module().get_or_store(key, fun)
end
end

defmodule Prismic.Cache.Default do
@moduledoc """
Default cache using an agent
"""
use Agent

@behaviour Prismic.Cache
defmodule Item do
defstruct [:value, :expiration]
end
@ttl_seconds 60

def start_link() do
Agent.start_link(fn -> Map.new end, name: __MODULE__)
end

def get_or_store(key, fun) do
if hit = get(key) do
hit
else
case fun.() do
{:commit, result} ->
set(key, result)
result
{:ignore, result} ->
#return function result, but don't cache failures
result
result ->
set(key, result)
result
end
end
end

def get(key) do
Agent.get_and_update(__MODULE__, fn map ->
now = now()
with %Item{expiration: expiration, value: value} when expiration > now <- Map.get(map, key) do
{value, map}
else
_ ->
# if item not found, delete is noop
# if it is expired, remove it
{nil, Map.delete(map, key)}
end
end)
end

def set(key, value, ttl \\ @ttl_seconds) do
Agent.update(__MODULE__, fn map ->
Map.put(map, key, %Item{value: value, expiration: now() + ttl})
end)
end

defp now, do: System.os_time(:seconds)
end
36 changes: 36 additions & 0 deletions lib/document.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
defmodule Prismic.Document do
alias Prismic.Fragment

defstruct [
:id,
:uid,
:type,
:href,
:tags,
:slugs,
:first_publication_date,
:last_publication_date,
:lang,
:alternate_languages,
:fragments
]

@type fragment :: any()
@type t :: %__MODULE__{
id: String.t(),
uid: String.t(),
type: String.t(),
href: String.t(),
tags: [String.t()],
slugs: [String.t()],
first_publication_date: DateTime.t() | nil,
last_publication_date: DateTime.t() | nil,
lang: String.t(),
alternate_languages: [String.t()],
fragments: [fragment]
}

def as_html(%{fragments: fragments}, link_resolver \\ nil) do
Enum.map(fragments, &Fragment.as_html(&1, link_resolver, nil))
end
end
12 changes: 12 additions & 0 deletions lib/form.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule Prismic.Form do
defstruct [:name, :method, :rel, :enctype, :action, :fields]

@type t :: %__MODULE__{
name: String.t(),
method: String.t(),
rel: String.t(),
enctype: String.t(),
action: String.t(),
fields: Map.t()
}
end
8 changes: 8 additions & 0 deletions lib/fragment.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defprotocol Prismic.Fragment do
@doc """
Generate usable HTML markup
- You need to pass a proper link_resolver so that internal links are turned into the proper URL
"""
def as_html(fragment, link_resolver, html_serializer \\ nil)
end
26 changes: 26 additions & 0 deletions lib/fragment/color.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Prismic.Fragment.Color do
@type hexidecimal :: String.t()
@type t :: %__MODULE__{value: hexidecimal}

import String, only: [slice: 2, to_integer: 2]

defmodule RGB do
@type t :: %__MODULE__{red: String.t(), green: String.t(), blue: String.t()}

defstruct [:red, :green, :blue]
end

defstruct [:value]

def to_rgb(%{value: value}) do
%RGB{
red: value |> slice(0..1) |> to_integer(16),
green: value |> slice(2..3) |> to_integer(16),
blue: value |> slice(4..5) |> to_integer(16)
}
end

def valid?(%{value: value}) do
value =~ ~r/(\h{2})(\h{2})(\h{2})/
end
end
11 changes: 11 additions & 0 deletions lib/fragment/composite_slice.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Prismic.Fragment.CompositeSlice do
alias Prismic.Group
@type t :: %__MODULE__{
slice_type: String.t(),
slice_label: String.t(),
non_repeat: map,
repeat: Group.t()
}

defstruct [:slice_type, :slice_label, :non_repeat, :repeat]
end
5 changes: 5 additions & 0 deletions lib/fragment/date.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Prismic.Fragment.Date do
@type t :: %__MODULE__{value: DateTime.t()}

defstruct [:value]
end
16 changes: 16 additions & 0 deletions lib/fragment/document_link.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defmodule Prismic.Fragment.DocumentLink do
defstruct [:id, :uid, :type, :tags, :slug, :lang, :fragments, :broken, :target]

@type fragments :: [any()]
@type t :: %__MODULE__{
id: String.t(),
uid: String.t(),
type: String.t(),
tags: [String.t()],
slug: String.t(),
lang: String.t(),
fragments: fragments,
broken: true | false,
target: String.t()
}
end
11 changes: 11 additions & 0 deletions lib/fragment/embed.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Prismic.Fragment.Embed do
defstruct [:embed_type, :provider, :url, :html, :o_embed_json]

@type t :: %__MODULE__{
embed_type: String.t(),
provider: any(),
url: String.t(),
html: String.t(),
o_embed_json: String.t()
}
end
11 changes: 11 additions & 0 deletions lib/fragment/file_link.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Prismic.Fragment.FileLink do
defstruct [:url, :name, :kind, :size, :target]

@type t :: %__MODULE__{
url: String.t(),
name: String.t(),
kind: String.t(),
size: Integer.t(),
target: String.t()
}
end
5 changes: 5 additions & 0 deletions lib/fragment/geopoint.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Prismic.Fragment.Geopoint do
@type t :: %__MODULE__{latitude: String.t(), longitude: String.t()}

defstruct [:latitude, :longitude]
end
13 changes: 13 additions & 0 deletions lib/fragment/image.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule Prismic.Fragment.Image do
alias Prismic.Fragment.View

@type views :: %{String.t() => View.t()}
@type t :: %__MODULE__{main: View.t(), views: views}

defstruct [:main, :views]
end

defimpl Prismic.Fragment, for: Prismic.Fragment.Image do
# TODO
def as_html(_, _, _), do: ""
end
8 changes: 8 additions & 0 deletions lib/fragment/image_link.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defmodule Prismic.Fragment.ImageLink do
defstruct [:url, :target]

@type t :: %__MODULE__{
url: String.t(),
target: String.t()
}
end
Loading

0 comments on commit d1b4e0c

Please sign in to comment.