README.md

# PhotoShuffle

[![Hex.pm](https://img.shields.io/hexpm/v/photo_shuffle.svg)](https://hex.pm/packages/photo_shuffle)
[![Documentation](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/photo_shuffle/)
[![License](https://img.shields.io/hexpm/l/photo_shuffle.svg)](https://github.com/unaffiliatedstudios/photo_shuffle/blob/main/LICENSE)

A lightweight Elixir library for managing and shuffling image collections with intelligent metadata generation. Perfect for portfolio sites, galleries, and any application that needs randomized image displays.

## Features

- 🎲 **Random Shuffling** - Intelligently shuffle and select N images from collections
- 📝 **Smart Titles** - Automatically generate human-readable titles from filenames
- 📍 **Deterministic Locations** - Consistent location mapping via path hashing  
- 🔌 **Pluggable File Systems** - Behaviour-based design for easy mocking and testing
- ⚡ **Zero Runtime Dependencies** - Lightweight with no external runtime deps
- 📚 **Comprehensive Documentation** - Full ExDoc coverage with examples

## Installation

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

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

## Quick Start

```elixir
# Process images from a directory
{:ok, images} = PhotoShuffle.process_images("/path/to/images", "Windows")

# Shuffle and select 6 random images  
selected = PhotoShuffle.shuffle_images(images, 6)

# Generate a title from a filename
title = PhotoShuffle.generate_title("IMG_5366.jpg")
# => "Professional Installation Project"

# Get deterministic location for an image
location = PhotoShuffle.smart_location("/path/to/IMG_5366.jpg", "Windows")
# => "Denver"
```

## Usage in Phoenix LiveView

```elixir
defmodule MyAppWeb.GalleryLive do
  use MyAppWeb, :live_view

  def mount(_params, _session, socket) do
    # Load and shuffle images
    path = Application.app_dir(:my_app, "priv/static/images/gallery")
    {:ok, all_images} = PhotoShuffle.process_images(path, "Gallery")
    displayed_images = PhotoShuffle.shuffle_images(all_images, 8)
    
    {:ok, assign(socket, images: displayed_images, all_images: all_images)}
  end

  def handle_event("refresh_gallery", _params, socket) do
    # Shuffle again on button click
    new_images = PhotoShuffle.shuffle_images(socket.assigns.all_images, 8)
    {:noreply, assign(socket, images: new_images)}
  end

  def render(assigns) do
    ~H"""
    <div class="grid grid-cols-3 gap-4">
      <%= for image <- @images do %>
        <div class="relative">
          <img src={image.src} alt={image.title} />
          <p>{image.title} - {image.location}</p>
        </div>
      <% end %>
    </div>
    <button phx-click="refresh_gallery">Shuffle Gallery</button>
    """
  end
end
```

## Configuration

```elixir
# config/config.exs
config :photo_shuffle,
  file_system: PhotoShuffle.FileSystem.Local,
  locations: ["Denver", "Aurora", "Boulder", "Fort Collins"]

# config/test.exs  
config :photo_shuffle,
  file_system: MyApp.PhotoShuffleFilesystemMock
```

## Custom URL Builder

For custom path transformations:

```elixir
# Custom URL builder that strips "priv/static/" prefix
url_builder = fn path, _base_url ->
  case String.split(path, "priv/static/", parts: 2) do
    [_prefix, relative] -> "/" <> relative
    _ -> "/" <> Path.basename(path)
  end
end

{:ok, images} = PhotoShuffle.process_images(
  "/app/priv/static/images",  
  "Portfolio",
  url_builder: url_builder
)
```

## Testing with Mox

```elixir
# test/test_helper.exs
Mox.defmock(MyApp.FileSystemMock, for: PhotoShuffle.FileSystemBehaviour)

# test/my_module_test.exs
test "processes images" do
  Mox.expect(MyApp.FileSystemMock, :list_images, fn _path ->
    {:ok, ["/path/img1.jpg", "/path/img2.jpg"]}
  end)

  Mox.expect(MyApp.FileSystemMock, :basename, 2, fn path ->
    Path.basename(path)
  end)

  {:ok, images} = MyModule.load_images()
  assert length(images) == 2
end
```

## API Documentation

### Core Functions

- `PhotoShuffle.process_images/3` - Load and process images from a directory
- `PhotoShuffle.shuffle_images/2` - Randomly shuffle and select N images
- `PhotoShuffle.generate_title/1` - Generate human-readable titles
- `PhotoShuffle.smart_location/2` - Get deterministic locations
- `PhotoShuffle.hash_path/1` - Create consistent hashes for paths

See [HexDocs](https://hexdocs.pm/photo_shuffle) for full API documentation.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Run tests (`mix test`)
4. Run code quality checks (`mix credo`, `mix dialyzer`)
5. Commit your changes (`git commit -m 'Add amazing feature'`)
6. Push to the branch (`git push origin feature/amazing-feature`)
7. Open a Pull Request

## License

This project is licensed under the MIT License - see the LICENSE file for details.

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for version history.