# `Agenda.Place`
[🔗](https://github.com/elixir-tempo/agenda/blob/v0.3.0/lib/agenda/place.ex#L1)

A place — a named location that contains resources and other places.

Places form a tree of arbitrary depth. A campus contains buildings,
a building contains floors, a floor contains rooms; the library never
interprets what a level *means*, only how the levels nest.

The tree exists so that travel between two resources can be
**derived** rather than configured. A flat `location: :sydney`
attribute can answer *"is this room in Sydney?"*, but not *"can
someone get from here to there in the ten-minute break?"* — and only
the second question decides whether a programme is workable.
`separation/2` is the primitive that answers it.

### As an iCalendar `VLOCATION`

RFC 9073's `VLOCATION` is the nearest iCalendar has to a place, and
the mapping loses the very thing the tree is for:

* **`name`** is `VLOCATION`'s `NAME`.

* **`location_type`** is its `LOCATION-TYPE`, the RFC 4589 location
  types (`:office`, `:hotel`, `:classroom`, …) written as atoms. It
  is no part of a place's identity, which is its chain of names.

* **`within` has no equivalent.** A `VLOCATION` does not nest, so an
  export writes a place alone, or the places on its `path/1` as
  separate `VLOCATION`s with nothing to say which holds which.
  `contains?/2`, `common_ancestor/2`, `separation/2` and
  `Agenda.travel_time/3`, all derived from the nesting, do not
  survive a round trip: a place read back from iCalendar is a root.

# `separation`

```elixir
@type separation() :: non_neg_integer() | :disjoint
```

How far apart two places are: `0` when they are the same place, `n`
when the deeper of the two is `n` levels below their nearest common
ancestor, and `:disjoint` when they share no ancestor at all.

# `t`

```elixir
@type t() :: %Agenda.Place{
  location_type: [atom()],
  name: String.t(),
  within: t() | nil
}
```

A place in the containment tree.

# `common_ancestor`

```elixir
@spec common_ancestor(t(), t()) :: t() | nil
```

The nearest place enclosing both `a` and `b`, or `nil` when they
share no ancestor.

### Arguments

* `a` and `b` are each a `t:t/0`.

### Returns

* the innermost common `t:t/0`, or

* `nil` when the two are in unrelated trees.

### Examples

    iex> sydney = Agenda.Place.new("Sydney Convention Centre")
    iex> level_2 = Agenda.Place.new("Level 2", within: sydney)
    iex> level_3 = Agenda.Place.new("Level 3", within: sydney)
    iex> Agenda.Place.common_ancestor(level_2, level_3).name
    "Sydney Convention Centre"

# `contains?`

```elixir
@spec contains?(t(), t()) :: boolean()
```

`true` when `outer` encloses `inner`, at any depth. A place contains
itself.

### Arguments

* `outer` is the enclosing `t:t/0`.

* `inner` is the enclosed `t:t/0`.

### Returns

* `true` or `false`.

### Examples

    iex> sydney = Agenda.Place.new("Sydney Convention Centre")
    iex> level_2 = Agenda.Place.new("Level 2", within: sydney)
    iex> Agenda.Place.contains?(sydney, level_2)
    true

    iex> sydney = Agenda.Place.new("Sydney Convention Centre")
    iex> level_2 = Agenda.Place.new("Level 2", within: sydney)
    iex> Agenda.Place.contains?(level_2, sydney)
    false

# `new`

```elixir
@spec new(String.t(), keyword()) :: t()
```

Build a place, optionally inside another.

### Arguments

* `name` is the place's name.

* `options` is a keyword list of options.

### Options

* `:within` is the enclosing `t:t/0`. The default is `nil`, making
  this place a root.

* `:location_type` is what kind of place it is, RFC 9073's
  `LOCATION-TYPE`: a list of RFC 4589 location types written as
  atoms, such as `[:office]` or `[:hotel, :restaurant]`. One atom is
  a list of one. The default is `[]`.

### Returns

* a `t:t/0`.

### Examples

    iex> sydney = Agenda.Place.new("Sydney Convention Centre")
    iex> level_2 = Agenda.Place.new("Level 2", within: sydney)
    iex> level_2.within.name
    "Sydney Convention Centre"

    iex> Agenda.Place.new("Head office", location_type: :office).location_type
    [:office]

# `path`

```elixir
@spec path(t()) :: [t()]
```

The chain of places from the root down to `place`, inclusive.

### Arguments

* `place` is a `t:t/0`.

### Returns

* a list of `t:t/0` ordered outermost first.

### Examples

    iex> sydney = Agenda.Place.new("Sydney Convention Centre")
    iex> level_2 = Agenda.Place.new("Level 2", within: sydney)
    iex> Enum.map(Agenda.Place.path(level_2), & &1.name)
    ["Sydney Convention Centre", "Level 2"]

# `root`

```elixir
@spec root(t()) :: t()
```

The outermost place enclosing `place`, or `place` itself when it is
already a root.

### Arguments

* `place` is a `t:t/0`.

### Returns

* a `t:t/0`.

### Examples

    iex> sydney = Agenda.Place.new("Sydney Convention Centre")
    iex> level_2 = Agenda.Place.new("Level 2", within: sydney)
    iex> Agenda.Place.root(level_2).name
    "Sydney Convention Centre"

# `separation`

```elixir
@spec separation(t(), t()) :: separation()
```

How far apart two places are, measured in levels of the containment
tree.

This is the primitive travel time is derived from: the further up the
tree you must climb to get from one place to the other, the longer
the journey.

### Arguments

* `a` and `b` are each a `t:t/0`.

### Returns

* `0` when `a` and `b` are the same place,

* a positive integer — the number of levels from the deeper place up
  to the nearest common ancestor, or

* `:disjoint` when the two share no ancestor.

### Examples

    iex> sydney = Agenda.Place.new("Sydney Convention Centre")
    iex> level_2 = Agenda.Place.new("Level 2", within: sydney)
    iex> level_3 = Agenda.Place.new("Level 3", within: sydney)
    iex> {Agenda.Place.separation(level_2, level_2),
    ...>  Agenda.Place.separation(level_2, level_3)}
    {0, 1}

    iex> sydney = Agenda.Place.new("Sydney Convention Centre")
    iex> darling = Agenda.Place.new("Darling Harbour Theatre")
    iex> Agenda.Place.separation(sydney, darling)
    :disjoint

---

*Consult [api-reference.md](api-reference.md) for complete listing*
