text:
"The Adventures of Sherlock Holmes" by Sir Arthur Conan Doyle

To Sherlock Holmes she is always THE woman. I have seldom heard him mention her under any other name. In his eyes she eclipses and predominates the whole of her sex. It was not that he felt any emotion akin to love for Irene Adler. All emotions, and that one particularly, were abhorrent to his cold, precise but admirably balanced mind. He was, I take it, the most perfect reasoning and observing machine that the world has seen, but as a lover he would have placed himself in a false position. He never spoke of the softer passions, save with a gibe and a sneer. They were admirable things for the observer--excellent for drawing the veil from men's motives and actions. But for the trained reasoner to admit such intrusions into his own delicate and finely adjusted temperament was to introduce a distracting factor which might throw a doubt upon all his mental results. Grit in a sensitive instrument, or a crack in one of his own high-power lenses, would not be more disturbing than a strong emotion in a nature such as his. And yet there was but one woman to him, and that woman was the late Irene Adler, of dubious and questionable memory.

I had seen little of Holmes lately. My marriage had drifted us away from each other. My own complete happiness, and the home-centred interests which rise up around the man who first finds himself master of his own establishment, were sufficient to absorb all my attention, while Holmes, who loathed every form of society with his whole Bohemian soul, remained in our lodgings in Baker Street, buried among his old books, and alternating from week to week between cocaine and ambition, the drowsiness of the drug, and the fierce energy of his own keen nature. He was still, as ever, deeply attracted by the study of crime, and occupied his immense faculties and extraordinary powers of observation in following out those clues, and clearing up those mysteries which had been abandoned as hopeless by the official police. From time to time I heard some vague account of his doings: of his summons to Odessa in the case of the Trepoff murder, of his clearing up of the singular tragedy of the Atkinson brothers at Trincomalee, and finally of the mission which he had accomplished so delicately and successfully for the reigning family of Holland. Beyond these signs of his activity, however, which I merely shared with all the readers of the daily press, I knew little of my former friend and companion.

One night--it was on the twentieth of March, 1888--I was returning from a journey to a patient (for I had now returned to civil practice), when my way led me through Baker Street. As I passed the well-remembered door, which must always be associated in my mind with my wooing, and with the dark incidents of the Study in Scarlet, I was seized with a keen desire to see Holmes again, and to know how he was employing his extraordinary powers. His rooms were brilliantly lit, and, even as I looked up, I saw his tall, spare figure pass twice in a dark silhouette against the blind. He was pacing the room swiftly, eagerly, with his head sunk upon his chest and his hands clasped behind him. To me, who knew his every mood and habit, his attitude and manner told their own story. He was at work again. He had risen out of his drug-created dreams and was hot upon the scent of some new problem. I rang the bell and was shown up to the chamber which had formerly been in part my own.

His manner was not effusive. It seldom was; but he was glad, I think, to see me. With hardly a word spoken, but with a kindly eye, he waved me to an armchair, threw across his case of cigars, and indicated a spirit case and a gasogene in the corner. Then he stood before the fire and looked me over in his singular introspective fashion.

"Wedlock suits you," he remarked. "I think, Watson, that you have put on seven and a half pounds since I saw you."

"Seven!" I answered.

"Indeed, I should have thought a little more. Just a trifle more, I fancy, Watson. And in practice again, I observe. You did not tell me that you intended to go into harness."

"Then, how do you know?"

"I see it, I deduce it. How do I know that you have been getting yourself very wet lately, and that you have a most clumsy and careless servant girl?"

"My dear Holmes," said I, "this is too much. You would certainly have been burned, had you lived a few centuries ago. It is true that I had a country walk on Thursday and came home in a dreadful mess, but as I have changed my clothes I can't imagine how you deduce it. As to Mary Jane, she is incorrigible, and my wife has given her notice, but there, again, I fail to see how you work it out."

He chuckled to himself and rubbed his long, nervous hands together.

"It is simplicity itself," said he; "my eyes tell me that on the inside of your left shoe, just where the firelight strikes it, the leather is scored by six almost parallel cuts. Obviously they have been caused by someone who has very carelessly scraped round the edges of the sole in order to remove crusted mud from it. Hence, you see, my double deduction that you had been out in vile weather, and that you had a particularly malignant boot-slitting specimen of the London slavey. As to your practice, if a gentleman walks into my rooms smelling of iodoform, with a black mark of nitrate of silver upon his right forefinger, and a bulge on the right side of his top-hat to show where he has secreted his stethoscope, I must be dull, indeed, if I do not pronounce him to be an active member of the medical profession."

I could not help laughing at the ease with which he explained his process of deduction. "When I hear you give your reasons," I remarked, "the thing always appears to me to be so ridiculously simple that I could easily do it myself, though at each successive instance of your reasoning I am baffled until you explain your process. And yet I believe that my eyes are as good as yours."

"Quite so," he answered, lighting a cigarette, and throwing himself down into an armchair. "You see, but you do not observe. The distinction is clear. For example, you have frequently seen the steps which lead up from the hall to this room."

"Frequently."

"How often?"

"Well, some hundreds of times."

"Then how many are there?"

"How many? I don't know."

"Quite so! You have not observed. And yet you have seen. That is just my point. Now, I know that there are seventeen steps, because I have both seen and observed. By-the-way, since you are interested in these little problems, and since you are good enough to chronicle one or two of my trifling experiences, you may be interested in this." He threw over a sheet of thick, pink-tinted note-paper which had been lying open upon the table. "It came by the last post," said he. "Read it aloud."

The note was undated, and without either signature or address.

"There will call upon you to-night, at a quarter to eight o'clock," it said, "a gentleman who desires to consult you upon a matter of the very deepest moment. Your recent services to one of the royal houses of Europe have shown that you are one who may safely be trusted with matters which are of an importance which can hardly be exaggerated. This account of you we have from all quarters received. Be in your chamber then at that hour, and do not take it amiss if your visitor wear a mask."

"This is indeed a mystery," I remarked. "What do you imagine that it means?"

"I have no data yet. It is a capital mistake to theorize before one has data. Insensibly one begins to twist facts to suit theories, instead of theories to suit facts. But the note itself. What do you deduce from it?"

I carefully examined the writing, and the paper upon which it was written.

"The man who wrote it was presumably well to do," I remarked, endeavouring to imitate my companion's processes. "Such paper could not be bought under half a crown a packet. It is peculiarly strong and stiff."

"Peculiar--that is the very word," said Holmes. "It is not an English paper at all. Hold it up to the light."

I did so, and saw a large "E" with a small "g," a "P," and a large "G" with a small "t" woven into the texture of the paper.

"What do you make of that?" asked Holmes.

"The name of the maker, no doubt; or his monogram, rather."

"Not at all. The 'G' with the small 't' stands for 'Gesellschaft,' which is the German for 'Company.' It is a customary contraction like our 'Co.' 'P,' of course, stands for 'Papier.' Now for the 'Eg.' Let us glance at our Continental Gazetteer." He took down a heavy brown volume from his shelves. "Eglow, Eglonitz--here we are, Egria. It is in a German-speaking country--in Bohemia, not far from Carlsbad. 'Remarkable as being the scene of the death of Wallenstein, and for its numerous glass-factories and paper-mills.' Ha, ha, my boy, what do you make of that?" His eyes sparkled, and he sent up a great blue triumphant cloud from his cigarette.

"The paper was made in Bohemia," I said.

"Precisely. And the man who wrote the note is a German. Do you note the peculiar construction of the sentence--'This account of you we have from all quarters received.' A Frenchman or Russian could not have written that. It is the German who is so uncourteous to his verbs. It only remains, therefore, to discover what is wanted by this German who writes upon Bohemian paper and prefers wearing a mask to showing his face. And here he comes, if I am not mistaken, to resolve all our doubts."

As he spoke there was the sharp sound of horses' hoofs and grating wheels against the curb, followed by a sharp pull at the bell. Holmes whistled.

"A pair, by the sound," said he. "Yes," he continued, glancing out of the window. "A nice little brougham and a pair of beauties. A hundred and fifty guineas apiece. There's money in this case, Watson, if there is nothing else."

"I think that I had better go, Holmes."

"Not a bit, Doctor. Stay where you are. I am lost without my Boswell. And this promises to be interesting. It would be a pity to miss it."

"But your client--"

"Never mind him. I may want your help, and so may he. Here he comes. Sit down in that armchair, Doctor, and give us your best attention."

A slow and heavy step, which had been heard upon the stairs and in the passage, paused immediately outside the door. Then there was a loud and authoritative tap.

"Come in!" said Holmes.

A man entered who could hardly have been less than six feet six inches in height, with the chest and limbs of a Hercules. His dress was rich with a richness which would, in England, be looked upon as akin to bad taste. Heavy bands of astrakhan were slashed across the sleeves and fronts of his double-breasted coat, while the deep blue cloak which was thrown over his shoulders was lined with flame-coloured silk and secured at the neck with a brooch which consisted of a single flaming beryl. Boots which extended halfway up his calves, and which were trimmed at the tops with rich brown fur, completed the impression of barbaric opulence which was suggested by his whole appearance. He carried a broad-brimmed hat in his hand, while he wore across the upper part of his face, extending down past the cheekbones, a black vizard mask, which he had apparently adjusted that very moment, for his hand was still raised to it as he entered. From the lower part of the face he appeared to be a man of strong character, with a thick, hanging lip, and a long, straight chin suggestive of resolution pushed to the length of obstinacy.

"You had my note?" he asked with a deep harsh voice and a strongly marked German accent. "I told you that I would call." He looked from one to the other of us, as if uncertain which to address.

"Pray take a seat," said Holmes. "This is my friend and colleague, Dr. Watson, who is occasionally good enough to help me in my cases. Whom have I the honour to address?"

"You may address me as the Count Von Kramm, a Bohemian nobleman. I understand that this gentleman, your friend, is a man of honour and discretion, whom I may trust with a matter of the most extreme importance. If not, I should much prefer to communicate with you alone."

I rose to go, but Holmes caught me by the wrist and pushed me back into my chair. "It is both, or none," said he. "You may say before this gentleman anything which you may say to me."

The Count shrugged his broad shoulders. "Then I must begin," said he, "by binding you both to absolute secrecy for two years; at the end of that time the matter will be of no importance. At present it is not too much to say that it is of such weight it may have an influence upon European history."

"I promise," said Holmes.

"And I."

"You will excuse this mask," continued our strange visitor. "The august person who employs me wishes his agent to be unknown to you, and I may confess at once that the title by which I have just called myself is not exactly my own."

"I was aware of it," said Holmes dryly.

"The circumstances are of great delicacy, and every precaution has to be taken to quench what might grow to be an immense scandal and seriously compromise one of the reigning families of Europe. To speak plainly, the matter implicates the great House of Ormstein, hereditary kings of Bohemia."

"I was also aware of that," murmured Holmes, settling himself down in his armchair and closing his eyes.

Our visitor glanced with some apparent surprise at the languid, lounging figure of the man who had been no doubt depicted to him as the most incisive reasoner and most energetic agent in Europe. Holmes slowly reopened his eyes and looked impatiently at his gigantic client.

"If your Majesty would condescend to state your case," he remarked, "I should be better able to advise you."

The man sprang from his chair and paced up and down the room in uncontrollable agitation. Then, with a gesture of desperation, he tore the mask from his face and hurled it upon the ground. "You are right," he cried; "I am the King. Why should I attempt to conceal it?"

"Why, indeed?" murmured Holmes. "Your Majesty had not spoken before I was aware that I was addressing Wilhelm Gottsreich Sigismond von Ormstein, Grand Duke of Cassel-Felstein, and hereditary King of Bohemia."

"But you can understand," said our strange visitor, sitting down once more and passing his hand over his high white forehead, "you can understand that I am not accustomed to doing such business in my own person. Yet the matter was so delicate that I could not confide it to an agent without putting myself in his power. I have come incognito from Prague for the purpose of consulting you."

"Then, pray consult," said Holmes, shutting his eyes once more.

"The facts are briefly these: Some five years ago, during a lengthy visit to Warsaw, I made the acquaintance of the well-known adventuress, Irene Adler. The name is no doubt familiar to you."

"Kindly look her up in my index, Doctor," murmured Holmes without opening his eyes. For many years he had adopted a system of docketing all paragraphs concerning men and things, so that it was difficult to name a subject or a person on which he could not at once furnish information. In this case I found her biography sandwiched in between that of a Hebrew rabbi and that of a staff-commander who had written a monograph upon the deep-sea fishes.

"Let me see!" said Holmes. "Hum! Born in New Jersey in the year 1858. Contralto--hum! La Scala, hum! Prima donna Imperial Opera of Warsaw--yes! Retired from operatic stage--ha! Living in London--quite so! Your Majesty, as I understand, became entangled with this young person, wrote her some compromising letters, and is now desirous of getting those letters back."

"Precisely so. But how--"

"Was there a secret marriage?"

"None."

"No legal papers or certificates?"

"None."

"Then I fail to follow your Majesty. If this young person should produce her letters for blackmailing or other purposes, how is she to prove their authenticity?"

"There is the writing."

"Pooh, pooh! Forgery."

"My private note-paper."

"Stolen."

"My own seal."

"Imitated."

"My photograph."

"Bought."

"We were both in the photograph."

"Oh, dear! That is very bad! Your Majesty has indeed committed an indiscretion."

"I was mad--insane."

"You have compromised yourself seriously."

"I was only Crown Prince then. I was young. I am but thirty now."

"It must be recovered."

"We have tried and failed."

"Your Majesty must pay. It must be bought."

"She will not sell."

"Stolen, then."

"Five attempts have been made. Twice burglars in my pay ransacked her house. Once we diverted her luggage when she travelled. Twice she has been waylaid. There has been no result."

"No sign of it?"

"Absolutely none."

Holmes laughed. "It is quite a pretty little problem," said he.

"But a very serious one to me," returned the King reproachfully.

"Very, indeed. And what does she propose to do with the photograph?"

"To ruin me."

"But how?"

"I am about to be married."

"So I have heard."

"To Clotilde Lothman von Saxe-Meningen, second daughter of the King of Scandinavia. You may know the strict principles of her family. She is herself the very soul of delicacy. A shadow of a doubt as to my conduct would bring the matter to an end."

"And Irene Adler?"

"Threatens to send them the photograph. And she will do it. I know that she will do it. You do not know her, but she has a soul of steel. She has the face of the most beautiful of women, and the mind of the most resolute of men. Rather than I should marry another woman, there are no lengths to which she would not go--none."

"You are sure that she has not sent it yet?"

"I am sure."

"And why?"

"Because she has said that she would send it on the day when the betrothal was publicly proclaimed. That will be next Monday."

"Oh, then we have three days yet," said Holmes with a yawn. "That is very fortunate, as I have one or two matters of importance to look into just at present. Your Majesty will, of course, stay in London for the present?"

"Certainly. You will find me at the Langham under the name of the Count Von Kramm."

"Then I shall drop you a line to let you know how we progress."

"Pray do so. I shall be all anxiety."

"Then, as to money?"

"You have carte blanche."

"Absolutely?"

"I tell you that I would give one of the provinces of my kingdom to have that photograph."

"And for present expenses?"

The King took a heavy chamois leather bag from under his cloak and laid it on the table.

"There are three hundred pounds in gold and seven hundred in notes," he said.

Holmes scribbled a receipt upon a sheet of his note-book and handed it to him.

"And Mademoiselle's address?" he asked.

"Is Briony Lodge, Serpentine Avenue, St. John's Wood."

Holmes took a note of it. "One other question," said he. "Was the photograph a cabinet?"

"It was."

"Then, good-night, your Majesty, and I trust that we shall soon have some good news for you. And good-night, Watson," he added, as the wheels of the royal brougham rolled down the street. "If you will be good enough to call to-morrow afternoon at three o'clock I should like to chat this little matter over with you."


text:
### 0.8.0

* Allow activation within any subdirectory of a repository
* Allow specifying `prefix`/`suffix` position for the emoji in commit message text

### 0.7.0 ⚙ ongoing work

* Drop support for Python `3.5`
* Delete deprecated `update` command
* Store config and resources in a centralized place
* Add internal logging

## 0.6.1 📦 current package

* Move helper functions to separate side-package
* Update emoji database; add preview page for existing icons
* Sort database alphabetically and remove items not supported by Github

## 0.6.0

* Fix bug with invocation without a subcommand
* Add icons to info messages
* Allow to run `activate` / `deactivate` commands without arguments (`local` installation is a default)
* Proper deactivation for corrupted setup
* Add more unittests
* Deprecate `update` command

## 0.5.1

* Add support for Python `3.10` version
* Add command aliases
* Use Poetry as a build tool

repo_name: bmwant/podmena

repo_language: Python


text: recursive-include podmena/resources *


text:
[tool.poetry]
name = "podmena"
version = "0.6.1"
description = "Enhance your commit messages with emoji"
authors = ["Misha Behersky <bmwant@gmail.com>"]
license = "Unlicense"
readme = "README.md"
repository = "https://github.com/bmwant/podmena"
keywords = ["git", "hook", "fun", "emoji", "commit"]
classifiers = [
    "Development Status :: 4 - Beta",
    "Environment :: Console",
    "Intended Audience :: Developers",
    "Topic :: Utilities",
    "Topic :: Software Development :: Build Tools",
    "Topic :: Software Development :: Version Control :: Git",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.6",
    "Programming Language :: Python :: 3.7",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Unix Shell"
]

[tool.poetry.dependencies]
python = "^3.6.2"
click = "^8.0"

[tool.poetry.dev-dependencies]
black = "^22.1.0"
pytest = "^7.0.0"

[tool.poetry.scripts]
podmena = 'podmena.cli:cli'

[tool.poetry.urls]
"Blog post" = "https://bmwlog.pp.ua/"

[tool.black]
line-length = 88

[build-system]
requires = ["poetry_core>=1.0.0"]
build-backend = "poetry.core.masonry.api"


text:
.PHONY: build
build:
	@rm -rf dist/
	@rm -rf build/
	@poetry build

.PHONY: tests
tests:
	@poetry run pytest -sv tests


text:
### Development

Install [Poetry](https://python-poetry.org/) and project's dependencies

```bash
$ poetry install
```

Add new feature and launch tests

```bash
$ poetry run pytest -sv tests
# or
$ make tests
```

Install git hooks for the automatic linting and code formatting with [pre-commit](https://pre-commit.com/)

```bash
$ pre-commit install
$ pre-commit run --all-files  # to initialize for the first time
$ pre-commit run flake8  # run a hook individually
```

Enable extra logging

```bash
$ export PODMENA_DEBUG=1
```

### Releasing

Bump a version with features you want to include and build a package

```bash
$ poetry version patch  # patch version update
$ poetry version minor
$ poetry version major  # choose one based on semver rules
$ poetry build
```

Upload package to GitHub and PyPI

```bash
$ git tag -a v0.4.2 -m "Version 0.4.2"
$ git push --tags
$ poetry publish
```


text:
podmena

Enhance your commit messages with emoji 🍒

podmena will automatically add random emoji to every commit message for any
git repository installed for.

![emoji](https://github.com/bmwant/podmena/raw/main/podmena.png)

**868** items in database so far!

Credits go to [WebFX](https://www.webfx.com/tools/emoji-cheat-sheet/) for list of emoji!

### Installation

```bash
$ pip install podmena
```

* Activate for current git repository

```bash
$ podmena add local
```

You can also replace `add` with a different alias

`activate` / `enable` / `install` / `on`

e.g. `podmena enable local`

* Activate globally for all repositories (works with git `2.9.1` and above)

```bash
$ git --version
$ podmena add global  # Aliases work here as well
```

* Deactivate it
```bash
$ podmena rm local
$ podmena rm global
```

You can replace `rm` with any of these available aliases

`remove` / `delete` / `deactivate` / `disable` / `off` / `uninstall`

e.g. `podmena deactivate local`

* Check current status if you not sure

```bash
$ podmena status
```

* And finally `podmena --version` and `podmena --help` in case you need more
details.

> **NOTE:** uninstalling globally will not remove hooks from repositories where
it was installed locally. You need to switch to that directory manually and uninstall it locally as well.

### Preview

See [PREVIEW.md](https://github.com/bmwant/podmena/blob/main/PREVIEW.md) for the full list of icons in the database and to check how GitHub renders them.
### Contribute

See [DEVELOP.md](https://github.com/bmwant/podmena/blob/main/DEVELOP.md) to setup your local development environment and feel free to create a pull request with a new feature.

### Releases

See [CHANGELOG.md](https://github.com/bmwant/podmena/blob/main/CHANGELOG.md) for the new features included within each release.

### See also

* [GitHooks](https://githooks.com/)
* [Atlassian tutorial for git hooks](https://www.atlassian.com/git/tutorials/git-hooks)
Thanks [@kakovskyi](https://github.com/kakovskyi) working for Atlassian!
* It's a wrong place to search if you are looking for 🍋 lemonparty.fun 🍋 club

### Say thanks!

🐶 `D7DA74qzZUyh9cctCxWovPTEovUSjGzL2S` this is [Dogecoin](https://dogecoin.com/) wallet to support the project.

🇺🇦 We would also thank the Armed Forces of Ukraine for providing security to perform this work. This work has become possible only because of resilience and courage of the Ukrainian Army.


text:
name: Tests

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  test:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v2
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Poetry
        uses: snok/install-poetry@v1

      - name: Install dependencies
        run: |
          poetry install

      - name: Run unittests
        run: |
          poetry run pytest -sv tests


text:
import click

position = click.option(
    "-p",
    "--position",
    required=True,
    default="prefix",
)
template = click.option(
    "-t",
    "--template",
    required=True,
    default="random",
)


text:
from .utils import initialize

initialize()


text:
import os
import sys
import logging
import shutil

import click

from podmena import config, options
from podmena.group import AliasedGroup
from podmena.utils import Filetype
from podmena.utils import (
    warn,
    note,
    info,
    check_exists,
    force_symlink,
    safe_delete,
    get_db_path,
    get_hook_path,
    get_git_root_dir,
    set_git_global_hooks_path,
    get_git_config_hooks_value,
    unset_git_global_hooks_path,
)

@click.group(cls=AliasedGroup)
@click.version_option(message="🍒 podmena, version %(version)s")
def cli():
    pass

@cli.group(
    name=("on", "add", "activate", "enable", "install"),
    help="Activate podmena",
    cls=AliasedGroup,
    invoke_without_command=True,
)
@options.template
@options.position
@click.pass_context
def install(ctx, position, template):
    if ctx.invoked_subcommand is None:
        # Install locally by default
        ctx.forward(local_install)

@install.command(
    name="local",
    help="Install podmena for current git repository",
)
@options.template
@options.position
def local_install(position, template):
    # TODO: check is repo via --is-inside-work-tree invocation
    git_local_root = os.path.join(os.getcwd(), ".git")
    local_hooks_path = os.path.join(git_local_root, "hooks")
    if check_exists(git_local_root) and os.path.isdir(local_hooks_path):
        src_file = get_hook_path(Filetype.SHARED)
        dst_file = get_hook_path(Filetype.LOCAL)
        logging.debug(
            f"Copying shared hook file to local destination:\n"
            f"{src_file} >>> {dst_file}"
        )
        shutil.copyfile(src_file, dst_file)
        os.chmod(dst_file, 0o0775)

        db_file = get_db_path(Filetype.SHARED)
        db_link = get_db_path(Filetype.LOCAL)
        logging.debug(
            f"Linking shared db file to local destination:\n" f"{db_file} >>> {db_link}"
        )
        force_symlink(db_file, db_link)

        note("✨ 🍒 ✨ Installed for current repository!", bold=True)
    else:
        warn("🍄 Not a git repository.")
        sys.exit(1)

@install.command(
    name="global",
    help="Install podmena globally for every git repository",
)
@options.position
@options.template
def global_install(position, template):
    confirm_info = (
        "This will set a single global hooks directory for all your repositories.\n"
        "This action may deactivate your previous hooks installed per "
        "repository.\nFor more info see "
        "https://git-scm.com/docs/git-config#git-config-corehooksPath\n"
    )
    info(confirm_info)
    if click.confirm("Do you want to continue?", abort=True):
        src_file = get_hook_path(Filetype.SHARED)
        dst_file = get_hook_path(Filetype.GLOBAL)
        logging.debug(
            f"Copying shared hook file to global destination:\n"
            f"{src_file} >>> {dst_file}"
        )
        shutil.copyfile(src_file, dst_file)
        os.chmod(dst_file, 0o0775)

        db_file = get_db_path(Filetype.SHARED)
        db_link = get_db_path(Filetype.GLOBAL)
        logging.debug(
            f"Linking shared db file to global destination:\n"
            f"{db_file} >>> {db_link}"
        )
        force_symlink(db_file, db_link)

        set_git_global_hooks_path(config.GLOBAL_HOOKS_DIR)
        note("✨ 🍒 ✨ Installed globally for all repositories!", bold=True)

@cli.group(
    name=("off", "rm", "remove", "delete", "deactivate", "disable", "uninstall"),
    help="Deactivate podmena",
    cls=AliasedGroup,
    invoke_without_command=True,
)
@click.pass_context
def remove(ctx):
    if ctx.invoked_subcommand is None:
        # Remove locally by default
        ctx.forward(local_uninstall)

@remove.command(
    name="local",
    help="Uninstall podmena for current git repository",
)
def local_uninstall():
    hook_filepath = get_hook_path(Filetype.LOCAL)
    db_link = get_db_path(Filetype.LOCAL)
    if check_exists(hook_filepath):
        safe_delete(hook_filepath)
        safe_delete(db_link)
        info("💥 🚫 💥 Uninstalled for current repository.", bold=True)
    else:
        warn("🍄 podmena is not installed for current repository.")
        sys.exit(1)

@remove.command(
    name="global",
    help="Uninstall podmena globally",
)
def global_uninstall():
    rc = unset_git_global_hooks_path()
    if rc == 0:
        info("💥 🚫 💥 Deactivated podmena globally.", bold=True)
    elif rc == 5:
        warn("🍄 podmena is not installed globally.")
        sys.exit(1)
    else:
        warn("🍄 Failed to deactivate.")
        sys.exit(rc)

@cli.command(
    name="status",
    help="Shows whether podmena is installed for current repository or globally",
)
def status():
    active = False
    git_root_dir = get_git_root_dir()
    if git_root_dir is not None:
        local_hooks_path = os.path.join(git_root_dir, ".git", "hooks")
        database_path = os.path.join(local_hooks_path, config.DATABASE_FILE)
        hook_path = os.path.join(local_hooks_path, config.HOOK_FILENAME)
        if check_exists(database_path) and check_exists(hook_path):
            note("✨ podmena is activated for current repository.")
            active = True

    global_database_path = get_db_path(Filetype.GLOBAL)
    global_hook_path = get_hook_path(Filetype.GLOBAL)
    git_global_hooks_config = get_git_config_hooks_value()

    if (
        check_exists(global_database_path)
        and check_exists(global_hook_path)
        and git_global_hooks_config == config.GLOBAL_HOOKS_DIR
    ):
        note("✨ podmena is activated globally.")
        active = True

    if not active:
        warn("🍄 podmena is not activated neither for current repository nor globally.")

if __name__ == "__main__":
    cli()


text:
import typing as t

import click

class AliasedGroup(click.Group):
    def __init__(self, name, *args, **kwargs):
        self._aliases = tuple()
        if isinstance(name, tuple):
            self._aliases = name
            name = name[0]

        super().__init__(name, *args, **kwargs)

    def get_command(self, ctx, cmd_name):
        rv = click.Group.get_command(self, ctx, cmd_name)
        if rv is not None:
            return rv

        # Check for the aliases
        for c in self.list_commands(ctx):
            command = click.Group.get_command(self, ctx, c)
            if cmd_name in getattr(command, "_aliases", tuple()):
                return command

        ctx.fail("No such command '{}'".format(cmd_name))

    def list_commands(self, ctx: click.Context) -> t.List[str]:
        return self.commands


text:
import os

DEBUG = bool(os.getenv("PODMENA_DEBUG", default=""))
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
RESOURCES_DIR = os.path.join(CURRENT_DIR, "resources")
CONFIG_DIR = os.path.expanduser("~/.podmena")
GLOBAL_HOOKS_DIR = os.path.join(CONFIG_DIR, "hooks")
HOOK_FILENAME = "commit-msg"
DATABASE_FILE = "emoji-db"


text:
import os
import logging
import shutil
import subprocess
from enum import Enum

import click

from podmena import config

def initialize():
    logging.disable(level=logging.CRITICAL)

    if config.DEBUG:
        logging.disable(logging.NOTSET)
        logging.basicConfig(level=logging.DEBUG)

    if not check_exists(config.GLOBAL_HOOKS_DIR):
        logging.debug("Creating config directory.")
        os.makedirs(config.GLOBAL_HOOKS_DIR)

    logging.debug("Copying resources to the config directory.")
    hook_file = os.path.join(config.RESOURCES_DIR, config.HOOK_FILENAME)
    db_file = os.path.join(config.RESOURCES_DIR, config.DATABASE_FILE)
    shutil.copy(hook_file, config.CONFIG_DIR)
    shutil.copy(db_file, config.CONFIG_DIR)

class Filetype(Enum):
    SHARED = "shared"
    LOCAL = "local"
    GLOBAL = "global"

def warn(message, **kwargs):
    click.secho(message, fg="red", **kwargs)

def note(message, **kwargs):
    click.secho(message, fg="green", **kwargs)

def info(message, **kwargs):
    click.secho(message, fg="yellow", **kwargs)

def set_git_global_hooks_path(hooks_path: str):
    subprocess.call(
        [
            "git",
            "config",
            "--global",
            "core.hooksPath",
            hooks_path,
        ]
    )

def get_git_config_hooks_value():
    try:
        return (
            subprocess.check_output(
                [
                    "git",
                    "config",
                    "--get",
                    "core.hooksPath",
                ]
            )
            .decode()
            .strip()
        )
    except subprocess.CalledProcessError:
        pass

def unset_git_global_hooks_path():
    return subprocess.call(
        [
            "git",
            "config",
            "--global",
            "--unset",
            "core.hooksPath",
        ]
    )

def get_git_root_dir():
    try:
        return (
            subprocess.check_output(
                [
                    "git",
                    "rev-parse",
                    "--show-toplevel",
                ],
                stderr=subprocess.STDOUT,
            )
            .decode()
            .strip()
        )
    except subprocess.CalledProcessError:
        pass

# TODO: merge similar logic into one function
def get_hook_path(filetype: Filetype) -> str:
    if filetype == Filetype.LOCAL:
        return os.path.join(os.getcwd(), ".git", "hooks", config.HOOK_FILENAME)
    elif filetype == Filetype.GLOBAL:
        return os.path.join(config.GLOBAL_HOOKS_DIR, config.HOOK_FILENAME)
    elif filetype == Filetype.SHARED:
        return os.path.join(config.CONFIG_DIR, config.HOOK_FILENAME)
    else:
        raise ValueError("Incorrect file location requested")

def get_db_path(filetype: Filetype) -> str:
    if filetype == Filetype.LOCAL:
        return os.path.join(os.getcwd(), ".git", "hooks", config.DATABASE_FILE)
    elif filetype == Filetype.GLOBAL:
        return os.path.join(config.GLOBAL_HOOKS_DIR, config.DATABASE_FILE)
    elif filetype == Filetype.SHARED:
        return os.path.join(config.CONFIG_DIR, config.DATABASE_FILE)
    else:
        raise ValueError("Incorrect file location requested")

def force_symlink(src: str, dst: str):
    if os.path.exists(dst) and os.path.islink(dst):
        os.remove(dst)

    os.symlink(src, dst)

def safe_delete(path: str):
    try:
        os.remove(path)
    except FileNotFoundError:
        pass

def check_exists(path: str) -> bool:
    """Simple wrapper for easier unit-testing"""
    return os.path.exists(path)


text:
#!/usr/bin/env python3
import os
import sys
import random

curdir = os.path.dirname(os.path.realpath(__file__))
with open(os.path.join(curdir, 'emoji-db')) as f:
    emoji = f.readlines()

with open(sys.argv[1]) as f:
    text = f.read()

with open(sys.argv[1], 'w') as f:
    f.write('{} :{}:'.format(text.strip(), random.choice(emoji).strip()))


text:
100
1234
8ball
a
ab
abc
abcd
accept
aerial_tramway
airplane
alarm_clock
alien
ambulance
anchor
angel
anger
angry
anguished
ant
apple
aquarius
aries
arrow_backward
arrow_double_down
arrow_double_up
arrow_down
arrow_down_small
arrow_forward
arrow_heading_down
arrow_heading_up
arrow_left
arrow_lower_left
arrow_lower_right
arrow_right
arrow_right_hook
arrow_up
arrow_up_down
arrow_up_small
arrow_upper_left
arrow_upper_right
arrows_clockwise
arrows_counterclockwise
art
articulated_lorry
astonished
atm
b
baby
baby_bottle
baby_chick
baby_symbol
back
baggage_claim
balloon
ballot_box_with_check
bamboo
banana
bangbang
bank
bar_chart
barber
baseball
basketball
bath
bathtub
battery
bear
beer
beers
beetle
beginner
bell
bento
bicyclist
bike
bikini
bird
birthday
black_circle
black_joker
black_large_square
black_medium_small_square
black_medium_square
black_nib
black_small_square
black_square_button
blossom
blowfish
blue_book
blue_car
blue_heart
blush
boar
boat
bomb
book
bookmark
bookmark_tabs
books
boom
boot
bouquet
bow
bowling
bowtie
boy
bread
bride_with_veil
bridge_at_night
briefcase
broken_heart
bug
bulb
bullettrain_front
bullettrain_side
bus
busstop
bust_in_silhouette
busts_in_silhouette
cactus
cake
calendar
calling
camel
camera
cancer
candy
capital_abcd
capricorn
car
card_index
carousel_horse
cat
cat2
cd
chart
chart_with_downwards_trend
chart_with_upwards_trend
checkered_flag
cherries
cherry_blossom
chestnut
chicken
children_crossing
chocolate_bar
christmas_tree
church
cinema
circus_tent
city_sunrise
city_sunset
cl
clap
clapper
clipboard
clock1
clock10
clock1030
clock11
clock1130
clock12
clock1230
clock130
clock2
clock230
clock3
clock330
clock4
clock430
clock5
clock530
clock6
clock630
clock7
clock730
clock8
clock830
clock9
clock930
closed_book
closed_lock_with_key
closed_umbrella
cloud
clubs
cn
cocktail
coffee
cold_sweat
collision
computer
confetti_ball
confounded
confused
congratulations
construction
construction_worker
convenience_store
cookie
cool
cop
copyright
corn
couple
couple_with_heart
couplekiss
cow
cow2
credit_card
crescent_moon
crocodile
crossed_flags
crown
cry
crying_cat_face
crystal_ball
cupid
curly_loop
currency_exchange
curry
custard
customs
cyclone
dancer
dancers
dango
dart
dash
date
de
deciduous_tree
department_store
diamond_shape_with_a_dot_inside
diamonds
disappointed
disappointed_relieved
dizzy
dizzy_face
do_not_litter
dog
dog2
dollar
dolls
dolphin
door
doughnut
dragon
dragon_face
dress
dromedary_camel
droplet
dvd
ear
ear_of_rice
earth_africa
earth_americas
earth_asia
egg
eggplant
eight
eight_pointed_black_star
eight_spoked_asterisk
electric_plug
elephant
email
end
envelope
es
euro
european_castle
european_post_office
evergreen_tree
exclamation
expressionless
eyeglasses
eyes
facepunch
factory
fallen_leaf
family
fast_forward
fax
fearful
feelsgood
feet
ferris_wheel
file_folder
finnadie
fire
fire_engine
fireworks
first_quarter_moon
first_quarter_moon_with_face
fish
fish_cake
fishing_pole_and_fish
fist
five
flags
flashlight
floppy_disk
flower_playing_cards
flushed
foggy
football
fork_and_knife
fountain
four
four_leaf_clover
fr
free
fried_shrimp
fries
frog
frowning
fu
fuelpump
full_moon
full_moon_with_face
game_die
gb
gem
gemini
ghost
gift
gift_heart
girl
globe_with_meridians
goat
goberserk
godmode
golf
grapes
green_apple
green_book
green_heart
grey_exclamation
grey_question
grimacing
grin
grinning
guardsman
guitar
gun
haircut
hamburger
hammer
hamster
hand
handbag
hankey
hash
hatched_chick
hatching_chick
headphones
hear_no_evil
heart
heart_decoration
heart_eyes
heart_eyes_cat
heartbeat
heartpulse
hearts
heavy_check_mark
heavy_division_sign
heavy_dollar_sign
heavy_exclamation_mark
heavy_minus_sign
heavy_multiplication_x
heavy_plus_sign
helicopter
herb
hibiscus
high_brightness
high_heel
hocho
honey_pot
honeybee
horse
horse_racing
hospital
hotel
hotsprings
hourglass
hourglass_flowing_sand
house
house_with_garden
hurtrealbad
hushed
ice_cream
icecream
id
ideograph_advantage
imp
inbox_tray
incoming_envelope
information_desk_person
information_source
innocent
interrobang
iphone
it
izakaya_lantern
jack_o_lantern
japan
japanese_castle
japanese_goblin
japanese_ogre
jeans
joy
joy_cat
jp
key
keycap_ten
kimono
kiss
kissing
kissing_cat
kissing_closed_eyes
kissing_heart
kissing_smiling_eyes
koala
koko
kr
large_blue_circle
large_blue_diamond
large_orange_diamond
last_quarter_moon
last_quarter_moon_with_face
laughing
leaves
ledger
left_luggage
left_right_arrow
leftwards_arrow_with_hook
lemon
leo
leopard
libra
light_rail
link
lips
lipstick
lock
lock_with_ink_pen
lollipop
loop
loudspeaker
love_hotel
love_letter
low_brightness
m
mag
mag_right
mahjong
mailbox
mailbox_closed
mailbox_with_mail
mailbox_with_no_mail
man
man_with_gua_pi_mao
man_with_turban
mans_shoe
maple_leaf
mask
massage
meat_on_bone
mega
melon
memo
mens
metal
metro
microphone
microscope
milky_way
minibus
minidisc
mobile_phone_off
money_with_wings
moneybag
monkey
monkey_face
monorail
mortar_board
mount_fuji
mountain_bicyclist
mountain_cableway
mountain_railway
mouse
mouse2
movie_camera
moyai
muscle
mushroom
musical_keyboard
musical_note
musical_score
mute
nail_care
name_badge
neckbeard
necktie
negative_squared_cross_mark
neutral_face
new
new_moon
new_moon_with_face
newspaper
ng
nine
no_bell
no_bicycles
no_entry
no_entry_sign
no_good
no_mobile_phones
no_mouth
no_pedestrians
no_smoking
nose
notebook
notebook_with_decorative_cover
notes
nut_and_bolt
o
o2
ocean
octocat
octopus
oden
office
ok
ok_hand
ok_woman
older_man
older_woman
on
oncoming_automobile
oncoming_bus
oncoming_police_car
oncoming_taxi
one
open_file_folder
open_hands
open_mouth
ophiuchus
orange_book
outbox_tray
ox
package
page_facing_up
page_with_curl
pager
palm_tree
panda_face
paperclip
parking
part_alternation_mark
partly_sunny
passport_control
paw_prints
peach
pear
pencil
pencil2
penguin
pensive
performing_arts
persevere
phone
pig
pig2
pig_nose
pill
pineapple
pisces
pizza
point_down
point_left
point_right
point_up
point_up_2
police_car
poodle
poop
post_office
postal_horn
postbox
potable_water
pouch
poultry_leg
pound
pouting_cat
pray
princess
punch
purple_heart
purse
pushpin
put_litter_in_its_place
question
rabbit
rabbit2
racehorse
radio
radio_button
rage
rage1
rage2
rage3
rage4
railway_car
rainbow
raised_hand
raised_hands
raising_hand
ram
ramen
rat
recycle
red_car
red_circle
registered
relaxed
relieved
repeat
repeat_one
restroom
revolving_hearts
rewind
ribbon
rice
rice_ball
rice_cracker
rice_scene
ring
rocket
roller_coaster
rooster
rose
rotating_light
round_pushpin
rowboat
ru
rugby_football
runner
running
running_shirt_with_sash
sa
sagittarius
sailboat
sake
sandal
santa
satellite
satisfied
saxophone
school
school_satchel
scissors
scorpius
scream
scream_cat
scroll
seat
secret
see_no_evil
seedling
seven
shaved_ice
sheep
shell
ship
shipit
shirt
shit
shoe
shower
signal_strength
six
six_pointed_star
ski
skull
sleeping
sleepy
slot_machine
small_blue_diamond
small_orange_diamond
small_red_triangle
small_red_triangle_down
smile
smile_cat
smiley
smiley_cat
smiling_imp
smirk
smirk_cat
smoking
snail
snake
snowboarder
snowflake
snowman
sob
soccer
soon
sos
sound
space_invader
spades
spaghetti
sparkle
sparkler
sparkles
sparkling_heart
speak_no_evil
speaker
speech_balloon
speedboat
star
star2
stars
station
statue_of_liberty
steam_locomotive
stew
straight_ruler
strawberry
stuck_out_tongue
stuck_out_tongue_closed_eyes
stuck_out_tongue_winking_eye
sun_with_face
sunflower
sunglasses
sunny
sunrise
sunrise_over_mountains
surfer
sushi
suspect
suspension_railway
sweat
sweat_drops
sweat_smile
sweet_potato
swimmer
symbols
syringe
tada
tanabata_tree
tangerine
taurus
taxi
tea
telephone
telephone_receiver
telescope
tennis
tent
thought_balloon
three
thumbsdown
thumbsup
ticket
tiger
tiger2
tired_face
tm
toilet
tokyo_tower
tomato
tongue
top
tophat
tractor
traffic_light
train
train2
tram
triangular_flag_on_post
triangular_ruler
trident
triumph
trolleybus
trollface
trophy
tropical_drink
tropical_fish
truck
trumpet
tshirt
tulip
turtle
tv
twisted_rightwards_arrows
two
two_hearts
two_men_holding_hands
two_women_holding_hands
u5272
u5408
u55b6
u6307
u6708
u6709
u6e80
u7121
u7533
u7981
u7a7a
uk
umbrella
unamused
underage
unlock
up
us
v
vertical_traffic_light
vhs
vibration_mode
video_camera
video_game
violin
virgo
volcano
vs
waning_crescent_moon
waning_gibbous_moon
warning
watch
water_buffalo
watermelon
wave
wavy_dash
waxing_crescent_moon
waxing_gibbous_moon
wc
weary
wedding
whale
whale2
wheelchair
white_check_mark
white_circle
white_flower
white_large_square
white_medium_small_square
white_medium_square
white_small_square
white_square_button
wind_chime
wine_glass
wink
wolf
woman
womans_clothes
womans_hat
womens
worried
wrench
x
yellow_heart
yen
yum
zap
zero
zzz


text:
import re

class RegexParser(object):
    def __init__(self):
        self.pattern = re.compile(r":<span\s+.+?>([\w_]+)</span>:")

    def parse(self, text):
        return self.pattern.findall(text)


text:
import logging

from helpers.fetcher import SimpleFetcher
from helpers.parser import RegexParser

logger = logging.getLogger(__name__)

# List of emojis not rendered by GitHub
BLACKLIST = [
    "person_frowning",
    "person_with_blond_hair",
    "person_with_pouting_face",
    "simple_smile",
    "squirrel",
]

def grab(dest: str):
    url = "https://www.webfx.com/tools/emoji-cheat-sheet/"
    fetcher = SimpleFetcher(url=url)
    parser = RegexParser()
    html = fetcher.request()

    unique_emoji = set(parser.parse(html))  # skip duplicates if any
    filtered_emoji = filter(lambda x: x not in BLACKLIST, unique_emoji)
    sorted_emoji = sorted(filtered_emoji)  # sort list alphabetically
    with open(dest, "w") as f:
        f.write("\n".join(sorted_emoji))

    logger.info(f"Downloaded {len(sorted_emoji)} emoji to database")

def render_preview(db_path: str):
    """
    Use database file to create a Markdown file that will be rendered with all
    the icons on a GitHub page
    """
    with (open(db_path) as db_file, open("PREVIEW.md", "w") as preview_file):
        emojis = db_file.readlines()
        preview_file.write("### All emoji rendered by GitHub\n")
        for e in emojis:
            preview_file.write(f":{e.strip()}: ")

def main():
    logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.DEBUG)
    db_path = "emoji-db-tmp"
    # db_path = os.path.join(config.RESOURCES_DIR, config.DATABASE_FILE)
    grab(db_path)
    render_preview(db_path)

if __name__ == "__main__":
    main()


text:
import logging
import contextlib
import urllib.request
from http import HTTPStatus

logger = logging.getLogger(__name__)

USER_AGENT = (
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
    "AppleWebKit/537.36 (KHTML, like Gecko) "
    "Chrome/98.0.4758.80 Safari/537.36"
)

class SimpleFetcher(object):
    def __init__(self, url):
        self.url = url

    def request(self, url=None):
        if url is None:
            url = self.url
        logger.info(f"Requesting {url}")

        req = urllib.request.Request(url)
        req.add_header("User-Agent", USER_AGENT)
        with contextlib.closing(urllib.request.urlopen(req)) as resp:
            if resp.status != HTTPStatus.OK:
                raise RuntimeError("Got wrong status %s" % resp.status)
            return resp.read().decode("utf-8")


text:
from unittest.mock import patch

from podmena import cli, config

def test_executable_invocation(runner):
    result = runner.invoke(cli.cli)

    assert result.exit_code == 0

    assert "Usage" in result.output
    assert "Commands" in result.output
    assert "Options" in result.output
    assert "podmena" in result.output

def test_version_invocation(runner):
    result = runner.invoke(cli.cli, ["--version"])

    assert result.exit_code == 0
    assert result.output.startswith("🍒 podmena, version")

def test_help_invocation(runner):
    result = runner.invoke(cli.cli, ["--help"])

    assert result.exit_code == 0
    assert "Show this message and exit" in result.output

@patch("podmena.cli.get_git_config_hooks_value")
def test_status_active_locally(
    get_git_config_hooks_value_mock,
    runner,
):
    runner.invoke(cli.cli, ["enable"])
    with patch(
        "podmena.cli.get_git_root_dir", return_value=runner.root_dir
    ) as get_git_root_dir_mock:
        result = runner.invoke(cli.cli, ["status"])
        get_git_root_dir_mock.assert_called_once_with()

    get_git_config_hooks_value_mock.assert_called_once_with()
    assert result.exit_code == 0
    assert result.output == "✨ podmena is activated for current repository.\n"

@patch("click.confirm")
@patch("podmena.cli.check_exists")
@patch("podmena.cli.get_git_root_dir")
@patch("podmena.cli.get_git_config_hooks_value")
def test_status_active_globally(
    get_git_config_hooks_value_mock,
    get_git_root_dir_mock,
    check_exists_mock,
    confirm_mock,
    runner,
):
    confirm_mock.return_value = False  # skip actual installation here
    check_exists_mock.return_value = True
    get_git_root_dir_mock.return_value = None
    get_git_config_hooks_value_mock.return_value = config.GLOBAL_HOOKS_DIR

    runner.invoke(cli.cli, ["install", "global"])
    confirm_mock.assert_called_once()

    result = runner.invoke(cli.cli, ["status"])
    get_git_root_dir_mock.assert_called_once_with()
    get_git_config_hooks_value_mock.assert_called_once_with()
    assert check_exists_mock.call_count == 2

    assert result.exit_code == 0
    assert result.output == "✨ podmena is activated globally.\n"

@patch("podmena.cli.get_git_root_dir")
@patch("podmena.cli.get_git_config_hooks_value")
def test_status_inactive(
    get_git_config_hooks_value_mock,
    get_git_root_dir_mock,
    runner,
):
    get_git_root_dir_mock.return_value = None

    result = runner.invoke(cli.cli, ["status"])
    get_git_root_dir_mock.assert_called_once_with()
    get_git_config_hooks_value_mock.assert_called_once_with()

    assert result.exit_code == 0
    assert (
        result.output
        == "🍄 podmena is not activated neither for current repository nor globally.\n"
    )


text:
import os
import sys
from unittest.mock import patch
from contextlib import contextmanager

import pytest
from click.testing import CliRunner

def fake_git_repo():
    """Creates directories with a same structure as within git repository"""
    hooks_path = os.path.join(os.getcwd(), ".git", "hooks")
    os.makedirs(hooks_path)

@pytest.fixture
def runner():
    cli_runner = CliRunner()
    with cli_runner.isolated_filesystem() as path:
        fake_git_repo()
        cli_runner.root_dir = path
        # Override global config directory
        with patch("podmena.config.GLOBAL_HOOKS_DIR", path):
            yield cli_runner

@contextmanager
def eject_module(module_name):
    module = sys.modules.pop(module_name)
    try:
        yield module
    finally:
        sys.modules[module_name] = module


text:
from unittest.mock import patch

from podmena import cli

def test_uninstall_local(runner):
    result_install = runner.invoke(cli.install, ["local"])
    assert result_install.exit_code == 0

    result_uninstall = runner.invoke(cli.remove, ["local"])
    assert result_uninstall.exit_code == 0
    assert result_uninstall.output == "💥 🚫 💥 Uninstalled for current repository.\n"

def test_uninstall_local_not_installed(runner):
    result_uninstall = runner.invoke(cli.remove)  # use default invocation

    assert result_uninstall.exit_code == 1
    assert (
        result_uninstall.output
        == "🍄 podmena is not installed for current repository.\n"
    )

@patch("podmena.cli.unset_git_global_hooks_path")
def test_uninstall_global(
    unset_git_global_hooks_path_mock,
    runner,
):
    unset_git_global_hooks_path_mock.return_value = 0
    result = runner.invoke(cli.cli, ["uninstall", "global"])

    assert result.exit_code == 0
    assert result.output == "💥 🚫 💥 Deactivated podmena globally.\n"
    unset_git_global_hooks_path_mock.assert_called_once_with()


text:
import os
import tempfile
from unittest.mock import patch

from podmena.utils import safe_delete

from .conftest import eject_module

def test_safe_delete():
    _, target_filepath = tempfile.mkstemp()

    assert os.path.exists(target_filepath)
    safe_delete(target_filepath)
    assert not os.path.exists(target_filepath)

def test_safe_delete_missing_file():
    tmp_dir = tempfile.gettempdir()
    fake_filepath = os.path.join(tmp_dir, "does_not_exist")

    assert not os.path.exists(fake_filepath)
    result = safe_delete(fake_filepath)
    # TODO: check for logger invocation once added
    assert result is None

@patch("podmena.utils.initialize")
def test_initialize_invoked_on_command(
    initialize_mock,
    runner,
):
    with eject_module("podmena"):
        from podmena import cli

        result = runner.invoke(cli.cli)
        assert result.exit_code == 0

        initialize_mock.assert_called_once_with()

@patch("podmena.utils.initialize")
def test_initialize_invoked_on_import(initialize_mock):
    with eject_module("podmena"):
        import podmena

        assert podmena.initialize is initialize_mock
        initialize_mock.assert_called_once_with()


text:
import os
from unittest.mock import patch

from podmena import cli, config
from podmena.utils import (
    Filetype,
    get_db_path,
    get_hook_path,
)

def test_install_local(runner):
    result = runner.invoke(cli.install, ["local"])

    assert result.exit_code == 0
    assert result.output == "✨ 🍒 ✨ Installed for current repository!\n"

    hook_file_path = get_hook_path(Filetype.LOCAL)
    assert os.path.exists(hook_file_path)

    db_file_path = get_db_path(Filetype.LOCAL)
    assert os.path.exists(db_file_path)

@patch("click.confirm")
@patch("podmena.cli.info")
@patch("podmena.cli.force_symlink")
@patch("podmena.cli.set_git_global_hooks_path")
def test_install_global(
    set_git_global_hooks_path_mock,
    force_symlink_mock,
    info_mock,
    confirm_mock,
    runner,
):
    confirm_mock.return_value = True

    result = runner.invoke(cli.cli, ["install", "global"])

    assert result.exit_code == 0
    # TODO: check alert message for the info
    info_mock.assert_called_once()
    force_symlink_mock.assert_called_once()
    set_git_global_hooks_path_mock.assert_called_once_with(config.GLOBAL_HOOKS_DIR)
    assert result.output == "✨ 🍒 ✨ Installed globally for all repositories!\n"

def test_install_alias_invocation(runner):
    result_on = runner.invoke(cli.cli, ["on"])

    assert result_on.exit_code == 0
    assert "Installed" in result_on.output

    result_activate = runner.invoke(cli.cli, ["activate"])
    assert result_activate.exit_code == 0
    assert "Installed" in result_activate.output

def test_install_default_invocation(runner):
    result = runner.invoke(cli.cli, ["install"])

    # Default should be a local installation
    assert result.exit_code == 0
    assert result.output == "✨ 🍒 ✨ Installed for current repository!\n"


text:
Package: nonparametricbayes
Description: Research in the use of nonparametric Bayesian approaches,
    primarily Gaussian processes, for ecological modeling and prediction.
Title: Nonparametric Bayes inference for ecological models
Version: 0.2.0
License: CC0
Author: Carl Boettiger, Marc Mangel, Steve Munch
Maintainer: Carl Boettiger, <cboettig@gmail.com>
URL: https://github.com/cboettig/nonparametric-bayes
BugReports: https://github.com/cboettig/nonparametric-bayes/issues
Roxygen: list(wrap = FALSE)
Suggests:
    testthat,
    knitr (>= 1.5),
    pander,
    modeest,
    MASS,
    coda,
    emdbook,
    R2jags,
    MCMCpack,
    rmarkdown,
    codetools,
    dplyr
Imports:
    data.table,
    kernlab,
    mcmc,
    pdgControl,
    plyr,
    ggplot2,
    reshape2 (>= 1.4.0.99)
Depends:
    R (>= 3.0)
Remotes:
    cboettig/pdg_control

repo_name: cboettig/nonparametric-bayes

repo_language: TeX

mime_type: text/plain


text:
# Generated by roxygen2 (4.0.0): do not edit by hand

S3method(plot,gpfit)
export(f_transition_matrix)
export(gp_fit)
export(gp_mcmc)
export(gp_predict)
export(gp_transition_matrix)
export(par_F)
export(summary_gp_mcmc)
import(data.table)
import(ggplot2)
import(kernlab)
import(mcmc)
import(pdgControl)
import(plyr)
import(reshape2)


text:
# nonparametric-bayes #

[![DOI](https://zenodo.org/badge/6271972.svg)](https://zenodo.org/badge/latestdoi/6271972)

* Authors:
  - Carl Boettiger
  - Marc Mangel
  - Steve Munch

doi: 10.5281/zenodo.12669

This repository contains the research compendium of our work in
nonparametric Bayesian inference for improving ecosystem management under
deep structural uncertainty. The compendium contains all data, code,
and text associated with the publication and has been permanently archived
at the DOI indicated by the above badge.

## R package ##

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

This repository is organized as an R package, providing functions to integrate
the __stochastic dynamic programming__ and __Gaussian process inference__
methods explored here. Nevertheless, this package has been written explicitly
for this project and may not yet be suitable for more general purpose use.

## Manuscript ##

[![Build Status](http://server.carlboettiger.info:88/api/badge/github.com/cboettig/nonparametric-bayes/status.svg?branch=master)](http://server.carlboettiger.info:88/github.com/cboettig/nonparametric-bayes)

The results of this project have now been written up for publication. See
the `manuscript` directory for details, including the code necessary to
run the examples shown.

## Notes and resources ##

* See my lab notebook entries under the
[nonparametric-bayes](http://www.carlboettiger.info/tags.html#nonparametric-bayes)
tag for ongoing description of this research.

* The [issues
tracker](https://github.com/cboettig/nonparametric-bayes/issues) lists
both current and accomplished goals in this project, and steps towards
their completion.

* See the repository
[history](https://github.com/cboettig/nonparametric-bayes/commits/master)
for a fine-grained view of progress and changes.

- This is an Open Laboratory Notebook project.  All work
under this project has been available on the [github
repo](https://github.com/cboettig/nonparametric-bayes) as the research
progresses.

--------------

![](http://www.carlboettiger.info/assets/img/ons-aci2-icon.svg)


text:
% Generated by roxygen2 (4.0.0): do not edit by hand
\name{gp_fit}
\alias{gp_fit}
\title{gp_fit fits a Gaussian process to the observed data}
\usage{
gp_fit(obs, X, pars = c(sigma_n = 1, tau = 1, l = 1), method = c("direct",
  "sequential", "kernlab", "cholesky", "conditional"), fit = TRUE,
  out_var = NULL, ...)
}
\arguments{
\item{obs}{a data frame of observations with columns obs$x and obs$y}

\item{X}{the desired points over which to predict}

\item{pars}{a named numeric specifying "sigma_n" for the (additive) noise and "l" for the covariance length scale}

\item{method}{select the method to use}

\item{out_var}{optionally force kernlab method to generate Cf using a different variance than sigma_n^2 assumed in the fit}

\item{fit}{logical, argument for the kernlab::gausspr method only}

\item{...}{potential additional arguments, unimplemented}
}
\value{
a list with items "mu", the expected Y values at X (mean of the posterior Gaussian process),
Sigma, the covariance matrix for the posterior Gaussian process, and
"loglik", the log likelihood of observering the given data under the process, marginalized over the prior
}
\description{
API not yet stable! Arguments to this function will probably be updated to be much more generic
}
\details{
so far treates the prior as mean 0 and covariance given by cov(X)
}


text:
% Generated by roxygen2 (4.0.0): do not edit by hand
\name{f_transition_matrix}
\alias{f_transition_matrix}
\title{calculate transition matrix at each harvest level based on parameterized function}
\usage{
f_transition_matrix(f, p, x_grid, h_grid = NULL, sigma_g)
}
\arguments{
\item{f}{the transtion function, a function of state x, harvest h, and parameters p}

\item{p}{the parameters p of the transition function}

\item{x_grid}{the grid over which to solve}

\item{h_grid}{the harvest grid over which to compute.  Assumed to match the stock grid gp$X if not given.}

\item{sigma_g}{the scale of the noise}
}
\value{
a list of matrices corresponding to the state transition matrix at each harvest level h
}
\description{
calculate transition matrix at each harvest level based on parameterized function
}


text:
% Generated by roxygen2 (4.0.0): do not edit by hand
\name{par_F}
\alias{par_F}
\title{calculate transition matrix based on parameterized function}
\usage{
par_F(h, f, p, x_grid, sigma_g)
}
\arguments{
\item{h}{the harvest over which to compute.}

\item{f}{the transtion function, a function of state x, harvest h, and parameters p}

\item{p}{the parameters p of the transition function}

\item{x_grid}{the grid over which to solve}

\item{sigma_g}{the scale of the noise}
}
\value{
a list of matrices corresponding to the state transition matrix at each harvest level h
}
\description{
calculate transition matrix based on parameterized function
}


text:
% Generated by roxygen2 (4.0.0): do not edit by hand
\name{plot.gpfit}
\alias{plot.gpfit}
\title{Plot a Gaussian Process}
\usage{
\method{plot}{gpfit}(x, true, ...)
}
\arguments{
\item{x}{a gpfit object from \code{\link{gp_fit}}}

\item{true}{an optional data.frame with columns "x" and "y" containing target curve.}

\item{...}{additional arguments (not implemented)}
}
\value{
a ggplot object of the plot
}
\description{
Plot the mean (line) and 2 standard deviations (ribbon)
}


text:
% Generated by roxygen2 (4.0.0): do not edit by hand
\name{gp_predict}
\alias{gp_predict}
\title{predict the expected values and posterior distributions of the Gaussian Process}
\usage{
gp_predict(gp, x_predict, burnin = 0, thin = 1, covs = FALSE)
}
\arguments{
\item{gp}{a fit of the gaussian process from gp_mcmc}

\item{x_predict}{the values at which we desire predictions}

\item{burnin}{length of sequence to discard as a transient}

\item{thin}{frequency of sub-sampling (make posterior distribution smaller if necessary)}

\item{covs}{if TRUE, return covariances instead of variances (set high thinning as this is memory intensive)}
}
\description{
predict the expected values and posterior distributions of the Gaussian Process
}


text:
% Generated by roxygen2 (4.0.0): do not edit by hand
\name{summary_gp_mcmc}
\alias{summary_gp_mcmc}
\title{Summary plots showing the trace and posteriors for the gp_mcmc estimates}
\usage{
summary_gp_mcmc(gp, burnin = 0, thin = 1)
}
\arguments{
\item{gp}{a fit of the gaussian process from gp_mcmc}

\item{burnin}{length of sequence to discard as a transient}

\item{thin}{frequency of sub-sampling (make posterior distribution smaller if necessary)}
}
\value{
two ggplot2 objects, one plotting the trace and one
plotting the posteriors in black with priors overlaid in red.
}
\description{
Summary plots showing the trace and posteriors for the gp_mcmc estimates
}


text:
% Generated by roxygen2 (4.0.0): do not edit by hand
\name{gp_transition_matrix}
\alias{gp_transition_matrix}
\title{Determine the transition matrix at each h given a Guassian process}
\usage{
gp_transition_matrix(Ef, V, x_grid, h_grid = NULL)
}
\arguments{
\item{Ef}{GP mean}

\item{V}{GP vector of variances}

\item{x_grid}{x grid over which to define transition matrices}

\item{h_grid}{the harvest grid over which to compute.  Assumed to match the stock grid x_grid if not given.}
}
\value{
a list of matrices corresponding to the state transition matrix at each harvest level h
}
\description{
Determine the transition matrix at each h given a Guassian process
}


text:
% Generated by roxygen2 (4.0.0): do not edit by hand
\name{gp_mcmc}
\alias{gp_mcmc}
\title{Basic regression in Gaussian processes}
\usage{
gp_mcmc(x, y, init_pars = c(l = 1, sigma.n = 1), n = 10000, d.p = c(5, 5),
  s2.p = c(5, 5))
}
\arguments{
\item{x}{Observed x values, (vector or matrix with columns for each dimension of data)}

\item{y}{Vector of observed y values in the training data}

\item{init_pars}{the initial guesses for lengthscale l and process noise sigma_n}

\item{n}{iterations of the metropolis algorithm}

\item{d.p}{parameters for the length-scale prior, as an inverse Gamma distribution}

\item{s2.p}{parameters for the noise prior, as an inverse Gamma distribution}
}
\value{
the MCMC output, as constructed by \link{metrop} from the mcmc package.
}
\description{
Basic regression in Gaussian processes
}
\details{
Currently assumes the covariance function.  By default we will use
the squared exponential (also called radial basis or Gaussian,
though it is not this that gives Gaussian process it's name;
any covariance function would do) as the covariance function,
\deqn{\operatorname{cov}(X_i, X_j) = \exp\left(-\frac{(X_i - X_j)^2}{2 \ell ^ 2}\right)}{}
}


text:
\documentclass[]{components/elsarticle}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{amssymb,amsmath}
\usepackage{ifxetex,ifluatex}
\usepackage{fixltx2e} % provides \textsubscript
% use upquote if available, for straight quotes in verbatim environments
\IfFileExists{upquote.sty}{\usepackage{upquote}}{}
\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
  \usepackage[utf8]{inputenc}
\else % if luatex or xelatex
  \ifxetex
    \usepackage{mathspec}
    \usepackage{xltxtra,xunicode}
  \else
    \usepackage{fontspec}
  \fi
  \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase}
  \newcommand{\euro}{€}
\fi
% use microtype if available
\IfFileExists{microtype.sty}{\usepackage{microtype}}{}
\usepackage[margin=1in]{geometry}
\usepackage{longtable,booktabs}
\usepackage{graphicx}
% Redefine \includegraphics so that, unless explicit options are
% given, the image width will not exceed the width of the page.
% Images get their normal width if they fit onto the page, but
% are scaled down if they would overflow the margins.
\makeatletter
\def\ScaleIfNeeded{%
  \ifdim\Gin@nat@width>\linewidth
    \linewidth
  \else
    \Gin@nat@width
  \fi
}
\makeatother
\let\Oldincludegraphics\includegraphics
{%
 \catcode`\@=11\relax%
 \gdef\includegraphics{\@ifnextchar[{\Oldincludegraphics}{\Oldincludegraphics[width=\ScaleIfNeeded]}}%
}%
\ifxetex
  \usepackage[setpagesize=false, % page size defined by xetex
              unicode=false, % unicode breaks when used with xetex
              xetex]{hyperref}
\else
  \usepackage[unicode=true]{hyperref}
\fi
\hypersetup{breaklinks=true,
            bookmarks=true,
            pdfauthor={},
            pdftitle={Supplement for: Avoiding tipping points in fisheries management through Gaussian Process Dynamic Programming},
            colorlinks=true,
            citecolor=blue,
            urlcolor=blue,
            linkcolor=magenta,
            pdfborder={0 0 0}}
\urlstyle{same}  % don't use monospace font for urls
\setlength{\parindent}{0pt}
\setlength{\parskip}{6pt plus 2pt minus 1pt}
\setlength{\emergencystretch}{3em}  % prevent overfull lines
\setcounter{secnumdepth}{0}

%%% Change title format to be more compact
\usepackage{titling}
\setlength{\droptitle}{-2em}
  \title{Supplement for: Avoiding tipping points in fisheries management through
Gaussian Process Dynamic Programming}
  \pretitle{\vspace{\droptitle}\centering\huge}
  \posttitle{\par}
  \author{true \and true \and true}
  \preauthor{\centering\large\emph}
  \postauthor{\par}
  \date{}
  \predate{}\postdate{}

\begin{document}

\maketitle

\tableofcontents

\appendix
\renewcommand*{\thefigure}{S\arabic{figure}}
\renewcommand*{\thetable}{S\arabic{table}} \setcounter{figure}{0}
\setcounter{table}{0}

\section{Code}\label{code}

All code used in producing this analysis has been embedded into the
manuscript sourcefile using the Dynamic Documentation tool,
\texttt{knitr} for the R language (Xie 2013), available at
\href{https://github.com/cboettig/nonparametric-bayes/}{github.com/cboettig/nonparametric-bayes/}

To help make the mathematical and computational approaches presented
here more accessible, we provide a free and open source (MIT License) R
package that implements the GPDP process as it is presented here. Users
should note that at this time, the R package has been developed and
tested explicitly for this analysis and is not yet intended as a general
purpose tool. The manuscript source-code described above illustrates how
these functions are used in this analysis. This package can be installed
following the directions above.

\subsection{Dependencies \&
Reproducibility}\label{dependencies-reproducibility}

The code provided should run on any common platform (Windows, Mac or
Linux) that has R and the necessary R packages installed (including
support for the jags Gibbs sampler). The DESCRIPTION file of our R
package, \texttt{nonparametricbayes}, lists all the software required to
use these methods. Additional software requirements for the other
comparisons shown here, such as the Gibbs sampling for the parametric
models, are listed under the Suggested packages list.

Nonetheless, installing the dependencies needed is not a trivial task,
and may become more difficult over time as software continues to evolve.
To facilitate reuse, we also provide a Dockerfile and Docker image that
can be used to replicate and explore the analyses here by providing a
copy of the computational environment we have used, with all software
installed. Docker sofware (see \href{http://www.docker.com}{docker.com})
runs on most platforms as well as cloud servers. Use the command:

\begin{verbatim}
docker run -dP cboettig/nonparametric-bayes
\end{verbatim}

to launch an RStudio instance with the necessary software already
installed. See the Rocker-org project,
\href{https://github.com/rocker-org}{github.com/rocker-org} for more
detailed documentation on using Docker with R.

\section{Data}\label{data}

\subsection{Dryad Data Archive}\label{dryad-data-archive}

While the data can be regenerated using the code provided, for
convenience CSV files of the data shown in each graph are made available
on Dryad, along with the source \texttt{.Rmd} files for the manuscript
and supplement that document them.

\subsection{Training data description}\label{training-data-description}

Each of our models $f(S_t)$ must be estimated from training data, which
we simulate from the Allen model with parameters $r = $ 2, $K =$ 8,
$C =$ 5, and $\sigma_g =$ 0.05 for $T=$ 40 timesteps, starting at
initial condition $X_0 = $ 5.5. The training data can be seen in Figure
1 and found in the table \texttt{figure1.csv}.

\subsection{Training data for sensitivity
analyses}\label{training-data-for-sensitivity-analyses}

A further 96 unique randomly generated training data sets are generated
for the sensitivity analysis, as described in the main text. The code
provided replicates the generation of these sets.

\section{Model performance outside the predicted range (Fig
S1)}\label{model-performance-outside-the-predicted-range-fig-s1}

Figure S1 illustrates the performance of the GP and parametric models
outside the observed training data. The mean trajectory under the
underlying model is shown by the black dots, while the corresponding
prediction made by the model shown by the box and whiskers plots.
Predictions are based on the true expected value in the previous time
step. Predicted distributions that lie entirely above the expected
dynamics indicate the expectation of stock sizes higher than what is
actually expected. The models differ both in their expectations and
their uncertainty (colored bands show two standard deviations away).
Note that the GP is particularly uncertain about the dynamics relative
to structurally incorrect models like the Ricker.

\begin{figure}[htbp]
\centering
\includegraphics{components/figure/supplement-figure_S1-1.pdf}
\caption{Outside the range of the training data (Figure 1), the true
dynamics (black dots) fall outside the uncertainty (two standard
deviations, colored bands) of the structurally incorrect parametric
models (Ricker, Myers), but inside the uncertainty predicted by the GP.
Points show the stock size simulated by the true model. Overlay shows
the range of states predicted by each model, based on the state observed
in the previous time step. The Ricker model always (wrongly) predicts
positive population growth, while the actual population shrinks in each
step as the initial condition falls below the Allee threshold of the
underlying model (Allen). Note that because it does not assume a
parametric form but instead relies more directly on the data, the GP is
both more pessimistic and more uncertain about the future state than the
parametric models.}
\end{figure}

\newpage

\section{Further sensitivity analysis (Fig S2 -
3)}\label{further-sensitivity-analysis-fig-s2---3}

We perform 2 sensitivity analyses. The first focuses on illustrating the
robustness of the approach to the two parameters that most influence
stochastic transitions across the tipping point: the position of the
Allee threshold and the scale of the noise (Fig S2).

Changing the intensity of the stochasticity or the distance between
stable and unstable steady states does not impact the performance of the
GP relative to the optimal solution obtained from the true model and
true parameters. The parametric models are more sensitive to this
difference. Large values of $\sigma$ relative to the distance between
the stable and unstable point increases the chance of a stochastic
transition below the tipping point. More precisely, if we let $L$ be the
distance between the stable and unstable steady states, then the
probability that fluctuations drive the population across the unstable
steady state scales as

$\exp\left(\frac{L^2}{\sigma^2}\right)$

(see Gardiner (2009) or Mangel (2006) for the derivation).

Thus, the impact of using a model that underestimates the risk of
harvesting beyond the critical point is considerable, since this such a
situation occurs more often. Conversely, with large enough distance
between the optimal escapement and unstable steady state relative to
$\sigma$, the chance of a transition becomes vanishingly small and all
models can be estimated near-optimally. Models that underestimate the
cost incurred by population sizes fluctuating significantly below the
optimal escapement level will not perform poorly as long as those
fluctuations are sufficiently small. Fig S2 shows the net present value
of managing under teh GPDP remains close to the optimal value (ratio of
1), despite varying across either noise level or the the position of the
allee threshold.

\begin{figure}[htbp]
\centering
\includegraphics{components/figure/supplement-figure_S2-1.pdf}
\caption{The effect of increasing noise or decreasing Allee threshold
levels on the net present value of the fishery when managed under the
GPDP, relative to managing under the true model (with known parameters).
Other than the focal parameter (stochasticity, Allee threshold), other
parameters are held fixed as above to illustrate this effect.}
\end{figure}

The Latin hypercube approach systematically varies all combinations of
parameters, providing a more general test than varying only one
parameter at a time. We loop across eight replicates of three different
randomly generated parameter sets for each of two different generating
models (Allen and Myers) over two different noise levels (0.01 and
0.05), for a total of 8 x 3 x 2 x 2 = 96 scenarios. The Gaussian Process
performs nearly optimally in each case, relative to the optimal solution
with no parameter or model uncertainty (Figure S10, appendix).

\begin{figure}[htbp]
\centering
\includegraphics{components/figure/supplement-figure_S3-1.pdf}
\caption{Sensitivity Analysis. Histograms shows the ratio of the
realized net present value derived when managing under the GPDP over the
optimal value given the true model and true parameters. Values of 1
indicate optimal performance. Columns indicate different models, rows
different noise levels, and colors indicate the parameter set used.
Grouped over stochastic replicates applying the contol policy and
stochastic replicates of training data generated from the model
indicated, see raw data for details. Randomly chosen parameter values
for the models shown in tables below.}
\end{figure}

\begin{longtable}[c]{@{}cccc@{}}
\toprule\addlinespace
\begin{minipage}[b]{0.15\columnwidth}\centering
~
\end{minipage} & \begin{minipage}[b]{0.07\columnwidth}\centering
r
\end{minipage} & \begin{minipage}[b]{0.07\columnwidth}\centering
K
\end{minipage} & \begin{minipage}[b]{0.09\columnwidth}\centering
theta
\end{minipage}
\\\addlinespace
\midrule\endhead
\begin{minipage}[t]{0.15\columnwidth}\centering
\textbf{set.A}
\end{minipage} & \begin{minipage}[t]{0.07\columnwidth}\centering
1.103
\end{minipage} & \begin{minipage}[t]{0.07\columnwidth}\centering
7.949
\end{minipage} & \begin{minipage}[t]{0.09\columnwidth}\centering
2.288
\end{minipage}
\\\addlinespace
\begin{minipage}[t]{0.15\columnwidth}\centering
\textbf{set.B}
\end{minipage} & \begin{minipage}[t]{0.07\columnwidth}\centering
1.485
\end{minipage} & \begin{minipage}[t]{0.07\columnwidth}\centering
9.775
\end{minipage} & \begin{minipage}[t]{0.09\columnwidth}\centering
3.524
\end{minipage}
\\\addlinespace
\bottomrule
\addlinespace
\caption{Randomly chosen parameter sets for the Allen models in Figure
S3.}
\end{longtable}

\begin{longtable}[c]{@{}cccc@{}}
\toprule\addlinespace
\begin{minipage}[b]{0.15\columnwidth}\centering
~
\end{minipage} & \begin{minipage}[b]{0.07\columnwidth}\centering
r
\end{minipage} & \begin{minipage}[b]{0.07\columnwidth}\centering
K
\end{minipage} & \begin{minipage}[b]{0.07\columnwidth}\centering
C
\end{minipage}
\\\addlinespace
\midrule\endhead
\begin{minipage}[t]{0.15\columnwidth}\centering
\textbf{set.C}
\end{minipage} & \begin{minipage}[t]{0.07\columnwidth}\centering
1.769
\end{minipage} & \begin{minipage}[t]{0.07\columnwidth}\centering
10.46
\end{minipage} & \begin{minipage}[t]{0.07\columnwidth}\centering
4.301
\end{minipage}
\\\addlinespace
\begin{minipage}[t]{0.15\columnwidth}\centering
\textbf{set.D}
\end{minipage} & \begin{minipage}[t]{0.07\columnwidth}\centering
2.075
\end{minipage} & \begin{minipage}[t]{0.07\columnwidth}\centering
10.95
\end{minipage} & \begin{minipage}[t]{0.07\columnwidth}\centering
4.915
\end{minipage}
\\\addlinespace
\bottomrule
\addlinespace
\caption{Randomly chosen parameter sets for the Myers models in Figure
S3.}
\end{longtable}

\newpage

\section{MCMC analyses}\label{mcmc-analyses}

This section provides figures and tables showing the traces from each of
the MCMC runs used to estimate the parameters of the models presented,
along with the resulting posterior distributions for each parameter.
Priors usually appear completely falt when shown against the posteriors,
but are summarized by tables the parameters of their corresponding
distributions for each case.

\subsection{GP MCMC (Fig S4-5)}\label{gp-mcmc-fig-s4-5}

\begin{figure}[htbp]
\centering
\includegraphics{components/figure/supplement-figure_S4-1.pdf}
\caption{Traces from the MCMC estimates of the GP model show reasonable
mixing (no trend) and sampling rejection rate (no piecewise jumps)}
\end{figure}

\begin{figure}[htbp]
\centering
\includegraphics{components/figure/supplement-figure_S5-1.pdf}
\caption{Posterior distributions from the MCMC estimate of the GP model.
Prior curves shown in red; note the posterior distributions are
significantly more peaked than the priors, showing that the data has
been informative and is not driven by the priors.}
\end{figure}

\newpage
\newpage

\subsection{Ricker Model MCMC (Fig
S6-7)}\label{ricker-model-mcmc-fig-s6-7}

\begin{figure}[htbp]
\centering
\includegraphics{components/figure/supplement-figure_S6-1.pdf}
\caption{Traces from the MCMC estimates of the Ricker model show
reasonable mixing (no trend) and sampling rejection rate (no piecewise
jumps). stdQ refers to the estimate of $\sigma$; deviance is -2 times
the log likelihood.}
\end{figure}

\begin{figure}[htbp]
\centering
\includegraphics{components/figure/supplement-figure_S7-1.pdf}
\caption{Posteriors from the MCMC estimate of the Ricker model. Note
that the model estimates a carrying capacity $K$ very close to the true
equilibrium where most of the observations were made, but is less
certain about the growth rate.}
\end{figure}

\begin{longtable}[c]{@{}ccc@{}}
\toprule\addlinespace
\begin{minipage}[b]{0.15\columnwidth}\centering
parameter
\end{minipage} & \begin{minipage}[b]{0.18\columnwidth}\centering
lower.bound
\end{minipage} & \begin{minipage}[b]{0.18\columnwidth}\centering
upper.bound
\end{minipage}
\\\addlinespace
\midrule\endhead
\begin{minipage}[t]{0.15\columnwidth}\centering
$r$
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
0.01
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
20
\end{minipage}
\\\addlinespace
\begin{minipage}[t]{0.15\columnwidth}\centering
$K$
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
0.01
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
40
\end{minipage}
\\\addlinespace
\begin{minipage}[t]{0.15\columnwidth}\centering
$\sigma$
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
1e-06
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
100
\end{minipage}
\\\addlinespace
\bottomrule
\addlinespace
\caption{Parameterization range for the uniform priors in the Ricker
model}
\end{longtable}

\newpage
\newpage

\subsection{Myers Model MCMC (Fig
S8-9)}\label{myers-model-mcmc-fig-s8-9}

\begin{figure}[htbp]
\centering
\includegraphics{components/figure/supplement-figure_S8-1.pdf}
\caption{Traces from the MCMC estimates of the Myers model show
reasonable mixing (no trend) and sampling rejection rate (no piecewise
jumps). stdQ refers to the estimate of $\sigma$; deviance is -2 times
the log likelihood.}
\end{figure}

\begin{figure}[htbp]
\centering
\includegraphics{components/figure/supplement-figure_S9-1.pdf}
\caption{Posterior distributions from the MCMC estimates of the Myers
model. Note that with more free parameters, the posteriors reflect
greater uncertainty. In particular, the parameter $\theta$ includes
values both above 2, resulting in a tipping point, and below 2, where no
tipping point exists in the model. Though the dynamic program will
integrate over the full distribution, including those values
corresponding to tipping points, the weight of the model lies in the
region without tipping points.}
\end{figure}

\begin{longtable}[c]{@{}ccc@{}}
\toprule\addlinespace
\begin{minipage}[b]{0.15\columnwidth}\centering
parameter
\end{minipage} & \begin{minipage}[b]{0.18\columnwidth}\centering
lower.bound
\end{minipage} & \begin{minipage}[b]{0.18\columnwidth}\centering
upper.bound
\end{minipage}
\\\addlinespace
\midrule\endhead
\begin{minipage}[t]{0.15\columnwidth}\centering
$r$
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
1e-04
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
10
\end{minipage}
\\\addlinespace
\begin{minipage}[t]{0.15\columnwidth}\centering
$K$
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
1e-04
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
40
\end{minipage}
\\\addlinespace
\begin{minipage}[t]{0.15\columnwidth}\centering
$\theta$
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
1e-04
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
10
\end{minipage}
\\\addlinespace
\begin{minipage}[t]{0.15\columnwidth}\centering
$\sigma$
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
1e-06
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
100
\end{minipage}
\\\addlinespace
\bottomrule
\addlinespace
\caption{Parameterization range for the uniform priors in the Myers
model}
\end{longtable}

\newpage
\newpage

\subsection{Allen Model MCMC (Fig
S10-11)}\label{allen-model-mcmc-fig-s10-11}

\begin{figure}[htbp]
\centering
\includegraphics{components/figure/supplement-figure_S10-1.pdf}
\caption{Traces from the MCMC estimates of the Allen model show
reasonable mixing (no trend) and sampling rejection rate (no piecewise
jumps). stdQ refers to the estimate of $\sigma$; deviance is -2 times
the log likelihood.}
\end{figure}

\begin{figure}[htbp]
\centering
\includegraphics{components/figure/supplement-figure_S11-1.pdf}
\caption{Posteriors from the MCMC estimate of the Allen model. The Allen
model is the structurally correct model. Despite potential
identfiability issues in distinguishing between the stable and unstable
points ($K$ and $\theta$ respectively), the posterior estimates
successfully reflect both the the upper stable point ($K$), as well as
the significant probabilty of a tipping point ($\theta$) somewhere
between $K$ and extinction (0).}
\end{figure}

\begin{longtable}[c]{@{}ccc@{}}
\toprule\addlinespace
\begin{minipage}[b]{0.15\columnwidth}\centering
parameter
\end{minipage} & \begin{minipage}[b]{0.18\columnwidth}\centering
lower.bound
\end{minipage} & \begin{minipage}[b]{0.18\columnwidth}\centering
upper.bound
\end{minipage}
\\\addlinespace
\midrule\endhead
\begin{minipage}[t]{0.15\columnwidth}\centering
$r$
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
0.01
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
6
\end{minipage}
\\\addlinespace
\begin{minipage}[t]{0.15\columnwidth}\centering
$K$
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
0.01
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
20
\end{minipage}
\\\addlinespace
\begin{minipage}[t]{0.15\columnwidth}\centering
$X_C$
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
0.01
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
20
\end{minipage}
\\\addlinespace
\begin{minipage}[t]{0.15\columnwidth}\centering
$\sigma$
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
1e-06
\end{minipage} & \begin{minipage}[t]{0.18\columnwidth}\centering
100
\end{minipage}
\\\addlinespace
\bottomrule
\addlinespace
\caption{Parameterization range for the uniform priors in the Allen
model}
\end{longtable}

Gardiner, C. 2009. Stochastic Methods: A Handbook for the Natural and
Social Sciences (Springer Series in Synergetics). Page 447. Springer.

Mangel, M. 2006. The Theoretical Biologist's Toolbox: Quantitative
Methods for Ecology and Evolutionary Biology. Cambridge University
Press.

Xie, Y. 2013. Dynamic Documents with R and knitr. Chapman; Hall/CRCBoca
Raton, Florida.

\end{document}