Full Code of wojtekmach/oop for AI

master b1ce8bf1dff7 cached
7 files
14.0 KB
4.1k tokens
20 symbols
1 requests
Download .txt
Repository: wojtekmach/oop
Branch: master
Commit: b1ce8bf1dff7
Files: 7
Total size: 14.0 KB

Directory structure:
gitextract_t24ek6r3/

├── .gitignore
├── .travis.yml
├── README.md
├── lib/
│   └── oop.ex
├── mix.exs
└── test/
    ├── oop_test.exs
    └── test_helper.exs

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/_build
/cover
/deps
erl_crash.dump
*.ez


================================================
FILE: .travis.yml
================================================
language: elixir
sudo: false
elixir:
- 1.3.4
otp_release:
- 18.2


================================================
FILE: README.md
================================================
<h1 align="center"> <br><img src="logo/logotype_horizontal.png?raw=true" alt="oop" width="256"> <br>

# OOP

[![Build Status](https://travis-ci.org/wojtekmach/oop.svg?branch=master)](https://travis-ci.org/wojtekmach/oop)

Are you tired of all of that modules, processes and functions nonsense? Do you want to just use classes, objects and methods? If so, use OOP [1] library in Elixir [2]!

## Demo

[![Lightning Talks - Wojtek Mach (ElixirConfEU 2016)](https://img.youtube.com/vi/5EtV2JUU0Z4/0.jpg)](https://www.youtube.com/watch?v=5EtV2JUU0Z4)

## Example

```elixir
import OOP

class Person do
  var :name

  def say_hello_to(who) do
    what = "Hello #{who.name}"
    IO.puts("#{this.name}: #{what}")
  end
end

joe = Person.new(name: "Joe")
mike = Person.new(name: "Mike")
robert = Person.new(name: "Robert")

joe.say_hello_to(mike)    # Joe: Hello Mike
mike.say_hello_to(joe)    # Mike: Hello Joe
mike.say_hello_to(robert) # Mike: Hello Robert
robert.say_hello_to(mike) # Robert: Hello Mike

joe.set_name("Hipster Joe")
joe.name # => Hipster Joe
```

An OOP library wouldn't be complete without inheritance:

```elixir
class Animal do
  var :name
end

class Dog < Animal do
  var :breed
end

snuffles = Dog.new(name: "Snuffles", breed: "Shih Tzu")
snuffles.name # => "Snuffles"
snuffles.breed # => "Shih Tzu"
```

... or multiple inheritance:

```elixir
class Human do
  var :name
end

class Horse do
  var :horseshoes_on?
end

class Centaur < [Human, Horse] do
end

john = Centaur.new(name: "John", horseshoes_on?: true)
john.name # => "John"
john.horseshoes_on? # => true
```

See more usage in the [test suite](test/oop_test.exs).

## Installation

Add `oop` to your list of dependencies in `mix.exs`:

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

[1] According to Alan Kay, the inventor of OOP, "objects" is the lesser idea; the big idea is "messaging". In that sense, I can't agree more with Joe Armstrong's quote that Erlang is "possibly the only object-oriented language".

[2] Please don't. You've been warned.

## License

The MIT License (MIT)

Copyright (c) 2015 Wojciech Mach

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: lib/oop.ex
================================================
defmodule OOP.Registry do
  def start_link do
    Agent.start_link(fn -> %{} end, name: __MODULE__)
  end

  def register(pid, class) do
    Agent.update(__MODULE__, &Map.put(&1, pid, class))
  end

  def get(pid) do
    Agent.get(__MODULE__, &Map.get(&1, pid, nil))
  end
end

defmodule OOP.Application do
  use Application

  def start(_type, _args) do
    OOP.Registry.start_link()
  end
end

defmodule OOP.Builder do
  def create_class(class, superclasses, block, opts) do
    quote do
      defmodule unquote(class) do
        OOP.Builder.ensure_can_be_subclassed(unquote(superclasses))

        @final Keyword.get(unquote(opts), :final, false)

        def __final__? do
          @final
        end

        def new(data \\ [], descendant? \\ false) do
          OOP.Builder.ensure_can_be_instantiated(unquote(class), descendant?, unquote(opts))


          object = :"#{unquote(class)}#{:erlang.unique_integer()}"

          defmodule object do
            use GenServer

            def start_link(data) do
              GenServer.start_link(__MODULE__, data, name: __MODULE__)
            end

            def class do
              unquote(class)
            end

            def methods do
              built_ins = [
                code_change: 3, handle_call: 3, handle_cast: 2, handle_info: 2,
                init: 1, start_link: 1, terminate: 2,
                class: 0, methods: 0,
              ]

              __MODULE__.__info__(:functions) -- built_ins
            end

            import Kernel, except: [def: 2]

            Module.register_attribute(__MODULE__, :friends, accumulate: true)

            unquote(block)

            Enum.each(unquote(superclasses), fn superclass ->
              parent = superclass.new(data, true)

              for {method, arity} <- parent.methods do
                Code.eval_quoted(OOP.Builder.inherit_method(method, arity, parent), [], __ENV__)
              end
            end)
          end

          {:ok, pid} = object.start_link(Enum.into(data, %{}))
          OOP.Registry.register(pid, unquote(class))

          object
        end
      end
    end
  end

  def ensure_can_be_subclassed(superclasses) do
    Enum.each(superclasses, fn s ->
      if s.__final__?, do: raise "cannot subclass final class #{s}"
    end)
  end

  def ensure_can_be_instantiated(class, descendant?, opts) do
    abstract? = Keyword.get(opts, :abstract, false)

    if !descendant? and abstract? do
      raise "cannot instantiate abstract class #{class}"
    end
  end

  def create_method(call, expr) do
    # HACK: this is a really gross way of checking if the function is using `this`.
    #       if so, we let it leak: `var!(this) = data`.
    #       We do this so that we don't get the "unused variable this" warning when
    #       we don't use `this`.
    using_this? = String.match?(Macro.to_string(expr), ~r"\bthis\.")

    {method, args} = Macro.decompose_call(call)

    handle_call_quoted =
      quote do
        try do
          [do: value] = unquote(expr)
          {:reply, {:ok, value}, data}
        rescue
          e in [RuntimeError] ->
            {:reply, {:error, e}, data}
        end
      end

    quote do
      def unquote(call) do
        case GenServer.call(__MODULE__, {:call, unquote(method), unquote(args)}) do
          {:ok, value} -> value
          {:error, e} -> raise e
        end
      end

      if unquote(using_this?) do
        def handle_call({:call, unquote(method), unquote(args)}, _from, data) do
          var!(this) = data
          unquote(handle_call_quoted)
        end
      else
        def handle_call({:call, unquote(method), unquote(args)}, _from, data) do
          unquote(handle_call_quoted)
        end
      end
    end
  end

  def inherit_method(method, arity, parent) do
    args = (0..arity) |> Enum.drop(1) |> Enum.map(fn i -> {:"arg#{i}", [], OOP} end)

    {:defdelegate, [context: OOP, import: Kernel],
      [{method, [], args}, [to: parent]]}
  end

  def create_var(field, opts) do
    private? = Keyword.get(opts, :private, false)

    quote do
      def unquote(field)() do
        case GenServer.call(__MODULE__, {:get, unquote(field)}) do
          {:ok, value} -> value
          {:error, :private} -> raise "Cannot access private var #{unquote(field)}"
        end
      end

      def unquote(:"set_#{field}")(value) do
        GenServer.call(__MODULE__, {:set, unquote(field), value})
      end

      def handle_call({:get, unquote(field)}, {pid, _ref}, data) do
        classes = [class() | @friends]
        if unquote(private?) and ! OOP.Registry.get(pid) in classes do
          {:reply, {:error, :private}, data}
        else
          {:reply, {:ok, Map.get(data, unquote(field))}, data}
        end
      end

      def handle_call({:set, unquote(field), value}, _from, data) do
        {:reply, value, Map.put(data, unquote(field), value)}
      end
    end
  end
end

defmodule OOP do
  defmacro class(class_expr, block, opts \\ []) do
    {class, superclasses} =
      case class_expr do
        {:<, _, [class, superclasses]} when is_list(superclasses) ->
          {class, superclasses}

        {:<, _, [class, superclass]} ->
          {class, [superclass]}

        class ->
          {class, []}
      end

    OOP.Builder.create_class(class, superclasses, block, opts)
  end

  defmacro abstract(class_expr, block) do
    {:class, _, [class]} = class_expr

    quote do
      OOP.class(unquote(class), unquote(block), abstract: true)
    end
  end

  defmacro final(class_expr, block) do
    {:class, _, [class]} = class_expr

    quote do
      OOP.class(unquote(class), unquote(block), final: true)
    end
  end

  defmacro def(call, expr \\ nil) do
    OOP.Builder.create_method(call, expr)
  end

  defmacro var(field, opts \\ []) do
    OOP.Builder.create_var(field, opts)
  end

  defmacro private_var(field) do
    quote do
      var(unquote(field), private: true)
    end
  end

  defmacro friend(class) do
    quote do
      @friends unquote(class)
    end
  end
end


================================================
FILE: mix.exs
================================================
defmodule OOP.Mixfile do
  use Mix.Project

  def project do
    [app: :oop,
     version: "0.1.1",
     description: "OOP in Elixir!",
     package: package(),
     elixir: "~> 1.0",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps()]
  end

  def application do
    [mod: {OOP.Application, []}]
  end

  defp deps do
    []
  end

  defp package do
    [
      maintainers: ["Wojtek Mach"],
      licenses: ["MIT"],
      links: %{"GitHub" => "https://github.com/wojtekmach/oop"},
    ]
  end
end


================================================
FILE: test/oop_test.exs
================================================
defmodule OOPTest do
  use ExUnit.Case
  import OOP

  test "define empty class" do
    c = class Person do
    end

    assert c
    purge Person
  end

  test "instantiate empty object" do
    class Person do
    end

    alice = Person.new
    assert alice.class == Person
    purge Person
  end

  test "define methods on objects" do
    class Person do
      def zero do
        0
      end

      def sum1(a) do
        a
      end

      def sum2(a, b) do
        a + b
      end
    end

    alice = Person.new
    assert alice.zero() == 0
    assert alice.sum1(1) == 1
    assert alice.sum2(1, 2) == 3
    purge Person
  end

  test "define fields" do
    class Person do
      var :name

      def title(prefix) do
        "#{prefix} #{this.name}"
      end
    end

    alice = Person.new
    assert alice.name == nil

    bob = Person.new(name: "Bob")
    assert bob.name == "Bob"
    bob.set_name("Hipster Bob")
    assert bob.name == "Hipster Bob"
    assert bob.title("Mr.") == "Mr. Hipster Bob"

    assert alice.name == nil

    purge Person
  end

  test "define private fields" do
    class AppleInc do
      private_var :registered_devices

      def registered_devices_count do
        length(this.registered_devices)
      end
    end

    apple = AppleInc.new(registered_devices: ["Alice's iPhone", "Bob's iPhone"])

    assert_raise RuntimeError, "Cannot access private var registered_devices", fn ->
      apple.registered_devices
    end

    assert apple.registered_devices_count == 2

    purge AppleInc
  end

  test "define friend class" do
    class NSA do
      def get_data(company) do
        company.registered_devices
      end
    end

    class Thief do
      def get_data(company) do
        company.registered_devices
      end
    end

    class AppleInc do
      friend NSA
      private_var :registered_devices
    end

    apple = AppleInc.new(registered_devices: ["Alice's iPhone", "Bob's iPhone"])
    thief = Thief.new
    nsa = NSA.new

    assert_raise RuntimeError, "Cannot access private var registered_devices", fn ->
      thief.get_data(apple)
    end

    assert nsa.get_data(apple) == ["Alice's iPhone", "Bob's iPhone"]

    purge [AppleInc, Thief, NSA]
  end

  test "inheritance" do
    class Animal do
      var :name

      def title(prefix) do
        "#{prefix} #{this.name}"
      end
    end

    class Dog < Animal do
      var :breed
    end

    snuffles = Dog.new(name: "Snuffles", breed: "Shih Tzu")
    assert snuffles.name == "Snuffles"
    assert snuffles.breed == "Shih Tzu"
    assert snuffles.title("Mr.") == "Mr. Snuffles"

    purge [Animal, Dog]
  end

  test "multiple inheritance" do
    class Human do
      var :name
    end

    class Horse do
      var :horseshoes_on?
    end

    class Centaur < [Human, Horse] do
    end

    john = Centaur.new(name: "John", horseshoes_on?: true)
    assert john.name == "John"
    assert john.horseshoes_on? == true

    purge [Human, Horse, Centaur]
  end

  test "define abstract class" do
    abstract class ActiveRecord.Base do
    end

    assert_raise RuntimeError, "cannot instantiate abstract class #{ActiveRecord.Base}", fn ->
      ActiveRecord.Base.new
    end

    class Post < ActiveRecord.Base do
      var :title
    end

    assert Post.new(title: "Post 1").title == "Post 1"
    purge [ActiveRecord.Base, Post]
  end

  test "abstract class inheriting from abstract class" do
    abstract class ActiveRecord.Base do
    end

    abstract class ApplicationRecord < ActiveRecord.Base do
    end

    assert_raise RuntimeError, "cannot instantiate abstract class #{ActiveRecord.Base}", fn ->
      ActiveRecord.Base.new
    end

    assert_raise RuntimeError, "cannot instantiate abstract class #{ApplicationRecord}", fn ->
      ApplicationRecord.new
    end

    class Post < ApplicationRecord do
      var :title
    end

    assert Post.new(title: "Post 1").title == "Post 1"
    purge [ActiveRecord.Base, ApplicationRecord, Post]
  end

  test "define final class" do
    final class FriezaFourthForm do
    end

    assert FriezaFourthForm.new

    assert_raise RuntimeError, "cannot subclass final class #{FriezaFourthForm}", fn ->
      class FriezaFifthForm < FriezaFourthForm do
      end
    end

    purge [FriezaFourthForm, FriezaFifthForm]
  end

  defp purge(module) when is_atom(module) do
    :code.delete(module)
    :code.purge(module)
  end
  defp purge(modules) when is_list(modules) do
    Enum.each(modules, &purge/1)
  end
end


================================================
FILE: test/test_helper.exs
================================================
ExUnit.start()
Download .txt
gitextract_t24ek6r3/

├── .gitignore
├── .travis.yml
├── README.md
├── lib/
│   └── oop.ex
├── mix.exs
└── test/
    ├── oop_test.exs
    └── test_helper.exs
Download .txt
SYMBOL INDEX (20 symbols across 3 files)

FILE: lib/oop.ex
  class OOP (line 179) | defmodule OOP
    class OOP.Registry (line 1) | defmodule OOP.Registry
      method start_link (line 2) | def start_link do
      method register (line 6) | def register(pid, class) do
      method get (line 10) | def get(pid) do
    class OOP.Application (line 15) | defmodule OOP.Application
      method start (line 18) | def start(_type, _args) do
    class OOP.Builder (line 23) | defmodule OOP.Builder
      method create_class (line 24) | def create_class(class, superclasses, block, opts) do
      method ensure_can_be_subclassed (line 86) | def ensure_can_be_subclassed(superclasses) do
      method ensure_can_be_instantiated (line 92) | def ensure_can_be_instantiated(class, descendant?, opts) do
      method create_method (line 100) | def create_method(call, expr) do
      method inherit_method (line 141) | def inherit_method(method, arity, parent) do
      method create_var (line 148) | def create_var(field, opts) do

FILE: mix.exs
  class OOP.Mixfile (line 1) | defmodule OOP.Mixfile
    method project (line 4) | def project do
    method application (line 15) | def application do
    method deps (line 19) | defp deps do
    method package (line 23) | defp package do

FILE: test/oop_test.exs
  class OOPTest (line 1) | defmodule OOPTest
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (15K chars).
[
  {
    "path": ".gitignore",
    "chars": 41,
    "preview": "/_build\n/cover\n/deps\nerl_crash.dump\n*.ez\n"
  },
  {
    "path": ".travis.yml",
    "chars": 65,
    "preview": "language: elixir\nsudo: false\nelixir:\n- 1.3.4\notp_release:\n- 18.2\n"
  },
  {
    "path": "README.md",
    "chars": 3127,
    "preview": "<h1 align=\"center\"> <br><img src=\"logo/logotype_horizontal.png?raw=true\" alt=\"oop\" width=\"256\"> <br>\n\n# OOP\n\n[![Build St"
  },
  {
    "path": "lib/oop.ex",
    "chars": 6037,
    "preview": "defmodule OOP.Registry do\n  def start_link do\n    Agent.start_link(fn -> %{} end, name: __MODULE__)\n  end\n\n  def registe"
  },
  {
    "path": "mix.exs",
    "chars": 545,
    "preview": "defmodule OOP.Mixfile do\n  use Mix.Project\n\n  def project do\n    [app: :oop,\n     version: \"0.1.1\",\n     description: \"O"
  },
  {
    "path": "test/oop_test.exs",
    "chars": 4487,
    "preview": "defmodule OOPTest do\n  use ExUnit.Case\n  import OOP\n\n  test \"define empty class\" do\n    c = class Person do\n    end\n\n   "
  },
  {
    "path": "test/test_helper.exs",
    "chars": 15,
    "preview": "ExUnit.start()\n"
  }
]

About this extraction

This page contains the full source code of the wojtekmach/oop GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (14.0 KB), approximately 4.1k tokens, and a symbol index with 20 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!