[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\njobs:\n  build:\n    runs-on: ubuntu-22.04\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - python-version: '3.8'\n            toxenv: py38\n          - python-version: '3.9'\n            toxenv: py39\n          - python-version: '3.10'\n            toxenv: py310\n          - python-version: '3.11'\n            toxenv: py311\n          - python-version: '3.12'\n            toxenv: py312\n          - python-version: '3.13'\n            toxenv: py313\n          - python-version: 'pypy-3.10'\n            toxenv: pypy3\n          - python-version: '3.9'\n            toxenv: mypy\n    steps:\n      - uses: actions/checkout@v6\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n      - name: Install tox\n        run: pip install tox\n      - name: Tox\n        run: tox\n        env:\n          TOXENV: ${{ matrix.toxenv }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n.#*\n\\#*\n.tox\ndist/\n.eggs/\nbuild/\n*.pyc\n*.pyo\n*.egg\n*.egg-info\n.aider*\nplayground\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright 2018 Ansgar Grunseid\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE.txt README.md\nprune tests\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">\n  <img src=\"logo.svg\" width=\"220px\" height=\"370px\" alt=\"IceCream\">\n</h1>\n\n<p align=\"center\">\n  <a href=\"https://pypi.python.org/pypi/icecream\"><img src=\"https://badge.fury.io/py/icecream.svg\"></a>\n  <a href=\"https://github.com/gruns/icecream/actions/workflows/ci.yml\"><img src=\"https://github.com/gruns/icecream/actions/workflows/ci.yml/badge.svg\"></a>\n  <a href=\"/LICENSE.txt\"><img src=\"https://img.shields.io/pypi/l/icecream.svg\"></a>\n  <a href=\"https://pypi.python.org/pypi/icecream\"><img src=\"https://img.shields.io/pypi/pyversions/icecream.svg\"></a>\n</p>\n\n\n### IceCream — Never use print() to debug again\n\nDo you ever use `print()` or `log()` to debug your code? Of course you\ndo. IceCream, or `ic` for short, makes print debugging a little sweeter.\n\n`ic()` is like `print()`, but better:\n\n  1. It prints both variables and expressions along with their values.\n  2. It's 60% faster to type.\n  3. Data structures are formatted and pretty printed.\n  4. Output is syntax highlighted.\n  5. It optionally includes program context: filename, line number, and\n     parent function.\n\nIceCream is well tested, [permissively licensed](LICENSE.txt), and supports Python 3 and PyPy3.\n\nIceCream is maintained by [Jakeroid (Ivan Karabadzhak)](https://github.com/Jakeroid), with support from the confidential computing folks at [🌖 Lunal](https://lunal.dev/).\n\n\n### Inspect Variables\n\nHave you ever printed variables or expressions to debug your program? If\nyou've ever typed something like\n\n```python\nprint(foo('123'))\n```\n\nor the more thorough\n\n```python\nprint(\"foo('123')\", foo('123'))\n```\n\nthen `ic()` will put a smile on your face. With arguments, `ic()`\ninspects itself and prints both its own arguments and the values of\nthose arguments.\n\n```python\nfrom icecream import ic\n\ndef foo(i):\n    return i + 333\n\nic(foo(123))\n```\n\nPrints\n\n```\nic| foo(123): 456\n```\n\nSimilarly,\n\n```python\nd = {'key': {1: 'one'}}\nic(d['key'][1])\n\nclass klass():\n    attr = 'yep'\nic(klass.attr)\n```\n\nPrints\n\n```\nic| d['key'][1]: 'one'\nic| klass.attr: 'yep'\n```\n\nJust give `ic()` a variable or expression and you're done. Easy.\n\n\n### Inspect Execution\n\nHave you ever used `print()` to determine which parts of your program are\nexecuted, and in which order they're executed? For example, if you've ever added\nprint statements to debug code like\n\n```python\ndef foo():\n    print(0)\n    first()\n\n    if expression:\n        print(1)\n        second()\n    else:\n        print(2)\n        third()\n```\n\nthen `ic()` helps here, too. Without arguments, `ic()` inspects itself and\nprints the calling filename, line number, and parent function.\n\n```python\nfrom icecream import ic\n\ndef foo():\n    ic()\n    first()\n\n    if expression:\n        ic()\n        second()\n    else:\n        ic()\n        third()\n```\n\nPrints\n\n```\nic| example.py:4 in foo()\nic| example.py:11 in foo()\n```\n\nJust call `ic()` and you're done. Simple.\n\n\n### Return Value\n\n`ic()` returns its argument(s), so `ic()` can easily be inserted into\npre-existing code.\n\n```pycon\n>>> a = 6\n>>> def half(i):\n>>>     return i / 2\n>>> b = half(ic(a))\nic| a: 6\n>>> ic(b)\nic| b: 3\n```\n\n\n### Miscellaneous\n\n`ic.format(*args)` is like `ic()` but the output is returned as a string instead\nof written to stderr.\n\n```pycon\n>>> from icecream import ic\n>>> s = 'sup'\n>>> out = ic.format(s)\n>>> print(out)\nic| s: 'sup'\n```\n\nAdditionally, `ic()`'s output can be entirely disabled, and later re-enabled, with\n`ic.disable()` and `ic.enable()` respectively.\n\n```python\nfrom icecream import ic\n\nic(1)\n\nic.disable()\nic(2)\n\nic.enable()\nic(3)\n```\n\nPrints\n\n```\nic| 1: 1\nic| 3: 3\n```\n\n`ic()` continues to return its arguments when disabled, of course; no existing\ncode with `ic()` breaks.\n\n\n### Import Tricks\n\nTo make `ic()` available in every file without needing to be imported in\nevery file, you can `install()` it. For example, in a root `A.py`:\n\n```python\n#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nfrom icecream import install\ninstall()\n\nfrom B import foo\nfoo()\n```\n\nand then in `B.py`, which is imported by `A.py`, just call `ic()`:\n\n```python\n# -*- coding: utf-8 -*-\n\ndef foo():\n    x = 3\n    ic(x)\n```\n\n`install()` adds `ic()` to the\n[builtins](https://docs.python.org/3.8/library/builtins.html) module,\nwhich is shared amongst all files imported by the interpreter.\nSimilarly, `ic()` can later be `uninstall()`ed, too.\n\n`ic()` can also be imported in a manner that fails gracefully if\nIceCream isn't installed, like in production environments (i.e. not\ndevelopment). To that end, this fallback import snippet may prove\nuseful:\n\n```python\ntry:\n    from icecream import ic\nexcept ImportError:  # Graceful fallback if IceCream isn't installed.\n    ic = lambda *a: None if not a else (a[0] if len(a) == 1 else a)  # noqa\n```\n\n\n### Configuration\n\n`ic.configureOutput(prefix, outputFunction, argToStringFunction,\nincludeContext, contextAbsPath)` controls `ic()`'s output.\n\n`prefix`, if provided, adopts a custom output prefix. `prefix` can be a\nstring, like\n\n```pycon\n>>> from icecream import ic\n>>> ic.configureOutput(prefix='hello -> ')\n>>> ic('world')\nhello -> 'world'\n```\n\nor a function.\n\n```pycon\n>>> import time\n>>> from icecream import ic\n>>>  \n>>> def unixTimestamp():\n>>>     return '%i |> ' % int(time.time())\n>>>\n>>> ic.configureOutput(prefix=unixTimestamp)\n>>> ic('world')\n1519185860 |> 'world': 'world'\n```\n\n`prefix`'s default value is `ic| `.\n\n`outputFunction`, if provided, is called once for every `ic()` call with\n`ic()`'s output, as a string, instead of that string being written to\nstderr (the default).\n\n```pycon\n>>> import logging\n>>> from icecream import ic\n>>>\n>>> def warn(s):\n>>>     logging.warning(\"%s\", s)\n>>>\n>>> ic.configureOutput(outputFunction=warn)\n>>> ic('eep')\nWARNING:root:ic| 'eep': 'eep'\n```\n\n`argToStringFunction`, if provided, is called with argument values to be\nserialized to displayable strings. The default is PrettyPrint's\n[pprint.pformat()](https://docs.python.org/3/library/pprint.html#pprint.pformat),\nbut this can be changed to, for example, handle non-standard datatypes\nin a custom fashion.\n\n```pycon\n>>> from icecream import ic\n>>>\n>>> def toString(obj):\n>>>    if isinstance(obj, str):\n>>>        return '[!string %r with length %i!]' % (obj, len(obj))\n>>>    return repr(obj)\n>>>\n>>> ic.configureOutput(argToStringFunction=toString)\n>>> ic(7, 'hello')\nic| 7: 7, 'hello': [!string 'hello' with length 5!]\n```\n\nThe default `argToStringFunction` is `icecream.argumentToString`, and\nhas methods to `register` and `unregister` functions to be dispatched\nfor specific classes using `functools.singledispatch`. It also has a\n`registry` property to view registered functions.\n\n```pycon\n>>> from icecream import ic, argumentToString\n>>> import numpy as np\n>>>\n>>> # Register a function to summarize numpy array\n>>> @argumentToString.register(np.ndarray)\n>>> def _(obj):\n>>>     return f\"ndarray, shape={obj.shape}, dtype={obj.dtype}\"\n>>>\n>>> x = np.zeros((1, 2))\n>>> ic(x)\nic| x: ndarray, shape=(1, 2), dtype=float64\n>>>\n>>> # View registered functions\n>>> argumentToString.registry\nmappingproxy({object: <function icecream.icecream.argumentToString(obj)>,\n              numpy.ndarray: <function __main__._(obj)>})\n>>>\n>>> # Unregister a function and fallback to the default behavior\n>>> argumentToString.unregister(np.ndarray)\n>>> ic(x)\nic| x: array([[0., 0.]])\n```\n\n`includeContext`, if provided and True, adds the `ic()` call's filename,\nline number, and parent function to `ic()`'s output.\n\n```pycon\n>>> from icecream import ic\n>>> ic.configureOutput(includeContext=True)\n>>>\n>>> def foo():\n>>>   i = 3\n>>>   ic(i)\n>>> foo()\nic| example.py:12 in foo()- i: 3\n```\n\n`includeContext` is False by default.\n\n`contextAbsPath`, if provided and True, outputs absolute filepaths, like\n`/path/to/foo.py`, over just filenames, like `foo.py`, when `ic()` is\ncalled with `includeContext == True`. This is useful when debugging\nmultiple files that share the same filename(s). Moreover, some editors,\nlike VSCode, turn absolute filepaths into clickable links that open the\nfile where `ic()` was called.\n\n```pycon\n>>> from icecream import ic\n>>> ic.configureOutput(includeContext=True, contextAbsPath=True)\n>>>\n>>> i = 3\n>>>\n>>> def foo():\n>>>   ic(i)\n>>> foo()\nic| /absolute/path/to/example.py:12 in foo()- i: 3\n>>>\n>>> ic.configureOutput(includeContext=True, contextAbsPath=False)\n>>>\n>>> def foo():\n>>>   ic(i)\n>>> foo()\nic| example.py:18 in foo()- i: 3\n```\n\n`contextAbsPath` is False by default.\n\nIf you want to use icecream with multiple log levels, like with Python’s\n`logging` module, you can use `ic.format()` to integrate icecream’s\ndebugging with your logger:\n\n```python\nimport logging\nfrom icecream import ic\n\nfoo = 'bar'\nlogging.debug(ic.format(foo))\n```\n\n❕ This is a bit clunky. Would you prefer built-in log level support in\nicecream? If so, please share your thoughts in\n[issue](https://github.com/gruns/icecream/issues/146).\n\n\n### Installation\n\nInstalling IceCream with pip is easy.\n\n```\n$ pip install icecream\n```\n\n\n### Related Python libraries\n\n`ic()` uses [**`executing`**](https://github.com/alexmojaki/executing)\nby [**@alexmojaki**](https://github.com/alexmojaki) to reliably locate\n`ic()` calls in Python source. It's magic.\n\n\n### IceCream in Other Languages\n\nDelicious IceCream should be enjoyed in every language.\n\n- Dart: [icecream](https://github.com/HallerPatrick/icecream)\n- Rust: [icecream-rs](https://github.com/ericchang00/icecream-rs)\n- Node.js: [node-icecream](https://github.com/jmerle/node-icecream)\n- C++: [IceCream-Cpp](https://github.com/renatoGarcia/icecream-cpp)\n- C99: [icecream-c](https://github.com/chunqian/icecream-c)\n- PHP: [icecream-php](https://github.com/ntzm/icecream-php)\n- Go: [icecream-go](https://github.com/WAY29/icecream-go)\n- Ruby: [Ricecream](https://github.com/nodai2hITC/ricecream)\n- Java: [icecream-java](https://github.com/Akshay-Thakare/icecream-java)\n- R: [icecream](https://github.com/lewinfox/icecream)\n- Lua: [icecream-lua](https://github.com/wlingze/icecream-lua)\n- Clojure(Script): [icecream-cljc](https://github.com/Eigenbahn/icecream-cljc)\n- Bash: [IceCream-Bash](https://github.com/jtplaarj/IceCream-Bash)\n- SystemVerilog: [icecream_sv](https://github.com/xver/icecream_sv)\n- GameMaker Language: [GMIceCream](https://github.com/dicksonlaw583/GMIceCream)\n\nIf you'd like a similar `ic()` function in your favorite language, please open a\npull request! IceCream's goal is to sweeten print debugging with a handy-dandy\n`ic()` function in every language.\n"
  },
  {
    "path": "changelog.txt",
    "content": "================================================================================\nv2.1.10\n================================================================================\nImproved: This change excludes the test folder from wheels. \n\nBig thanks to the community! This release was made possible by the people\n  who contributed to the library.\n\n================================================================================\nv2.1.9\n================================================================================\nRemoved: Support for Python 3.8.  \nFixed: Issues #229 and #60, which means improved lists output.  \n\nBig thanks to the community! This release was made possible by the people\n  who contributed to the library.\n\n================================================================================\nv2.1.8\n================================================================================\nAdded: You can pass a pre-configured ic instance to builtins.\nAdded: You can configure IceCream to output to either stdout or stderr.\n\nBig thanks to the community! This release was made possible by the people\n  who contributed to the library.\n\n================================================================================\nv2.1.7\n================================================================================\nAdded: Configurable line wrap length.\nImproved: The package no longer includes tests in the production installation.\n\n================================================================================\nv2.1.6\n================================================================================\nFixed: Pretty-printing of SymPy (and similar) objects.\n\nPreviously, calling ic() on structures containing SymPy objects could raise\n  a TypeError because pprint.pformat(sort_dicts=True) attempted to sort\n  unorderable keys. IceCream now keeps sort_dicts=True on the fast path and\n  falls back to sort_dicts=False when pprint raises, ensuring robust output\n  without crashes.\n\n================================================================================\n v2.1.5\n================================================================================\nChanged: Improved printing for variables of type `str`.\n\nFixed issues that affected the output of multiline strings and strings\n  containing special characters such as escaped newlines and tabs.\n\nStrings are now printed exactly as they are, faithfully representing their\n  actual value.\n\n================================================================================\n v2.1.4\n================================================================================\nChanged: Drop support for all Python versions prior to Python 3.8, which\n  are now long past EOL. Notably: Python 2 is no longer supported.\nChanged: Update the 'executing' dependency to >= v2.1.0 to improve\n  source code analysis and support Python 3.13.\n\n================================================================================\n v2.1.3\n================================================================================\nAdded: The contextAbsPath= parameter to ic.configureOutput() which, when\n  True, outputs absolute paths, like /path/to/foo.py, instead of just\n  filenames, like foo.py. See https://github.com/gruns/icecream/pull/122.\n  Huge thank you to @HelinXu!\nChanged: Raise TypeError if no arguments are provided to\n  ic.configureOutput().\n\n================================================================================\n v2.1.2\n================================================================================\nAdded: Ability to register and unregister singledispatch argumentToString\n  functions. See https://github.com/gruns/icecream/pull/115. Huge thank you\n  to @atusy!\n\n================================================================================\n v2.1.1\n================================================================================\nAdded: Support for Python 3.9.\nChanged: Use timestamps in the local timezone instead of less helpful\n  UTC timestamps.\n\n================================================================================\n v2.1.0\n================================================================================\nAdded: install() and uninstall() functions that add or remove ic() from\n  the builtins module.\nChanged: Switch to ast.literal_eval() to determine if an argument and\n  value are the same, and thus only the value should be output. Huge\n  thank you to Ed Cardinal and Alex Hall.\n\n================================================================================\n v2.0.0\n================================================================================\nAdded: Support for Python 3.8.\nRemoved: Support for Python 3.4.\nChanged: Switched core AST parsing engine to Alex Hall's executing\n  (https://github.com/alexmojaki/executing). Huge thank you to Alex Hall.\nChanged: Whitespace in arguments is no longer collapsed. Indentation in\n  multiline arguments is now preserved.\n\n================================================================================\n v1.5.0\n================================================================================\nFixed: Support multiline container arguments. e.g.\n  ic([a,\n        b])\nFixed: Include LICENSE.txt in source distributions.\nChanged: Collapse argument whitespace, e.g. ic([ a,  b ]) -> ic| [a, b].\n\n================================================================================\n v1.4.0\n================================================================================\nAdded: Colorize output with pygments.\nAdded: Test Python style with pycodestyle.\nFixed: Parse and print tuple arguments correctly, e.g. ic((a, b)).\nFixed: Fail gracefully when the underlying source code changes during execution.\nChanged: Print values (e.g. 1, 'foo', etc) by themselves, nonredundantly. For\n  example, ic(3) now prints 'ic| 3' instead of 'ic| 3: 3'.\n\n================================================================================\n v1.3.1\n================================================================================\nRemoved: Support for Python 3.3, which reached EOL on 2017-09-29.\nFixed: ic() invocations that fail to find or access source code (e.g. eval(),\n  exec(), python -i, etc) now print an error message instead of throwing an\n  IOError (Python 2) or OSError (Python 3).\n\n================================================================================\n v1.3\n================================================================================\nFirst release.\n\n\nThis changelog wasn't maintained prior to v1.3.\n"
  },
  {
    "path": "failures-to-investigate/freshsales.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n# Copyright _!_\n#\n# License _!_\n\nfrom os.path import abspath, dirname, join as pjoin\nimport pprint\nimport sys\nimport time\n\nimport requests\nfrom icecream import ic\n\n_corePath = abspath(pjoin(dirname(__file__), '../'))\nif _corePath not in sys.path:\n    sys.path.append(_corePath)\nfrom common.utils import lget\n\n\nDEFAULT_FIRST_NAME = 'there'\nDEFAULT_LAST_NAME = '-'\nFRESH_SALES_API_KEY = 'P3bYheaquAHH1_hNxhMUDQ'\nFS_API_URL = 'https://arcindustriesinc.freshsales.io/api'\nFS_AUTH_HEADERS = {'Authorization': 'Token token=P3bYheaquAHH1_hNxhMUDQ'}\n\n\n#\n# FreshSales' Contact field magic values looked up with\n#\n#   curl -H \"Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ\" -H \"Content-Type: application/json\" -X GET \"https://arcindustriesinc.freshsales.io/api/settings/contacts/fields\"\n#\n#\n# FreshSales' Lead field magic values looked up with\n#\n#   curl -H \"Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ\" -H \"Content-Type: application/json\" -X GET \"https://arcindustriesinc.freshsales.io/api/settings/leads/fields\"\n#\n# FreshSales' Company field magic values looked up with\n#\n#   curl -H \"Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ\" -H \"Content-Type: application/json\" -X GET \"https://arcindustriesinc.freshsales.io/api/settings/sales_accounts/fields\"\n#\n\n\ndef splitName(name):\n    # Intelligently split <name> into first and last name, if provided.\n    #   '' -> firstName: '', lastName: '-'\n    #   'Susan' -> firstName: 'Susan', lastName: '-'\n    #   'Greg Borp' -> firstName: 'Greg', lastName: 'Borp'\n    #   'Freddy van der Field' -> firstName: 'Freddy', lastName: 'van der Field'\n    toks = name.split(None, 1)\n    firstName = lget(toks, 0, DEFAULT_FIRST_NAME)\n    lastName = lget(toks, 1, DEFAULT_LAST_NAME)\n\n    return firstName, lastName\n\n\ndef lookupFullContact(contact):\n    contactId = contact['id']\n    resp = requests.get(\n        f'{FS_API_URL}/contacts/{contactId}?include=sales_accounts',\n        headers=FS_AUTH_HEADERS)\n    contact = (resp.json() or {}).get('contact')\n    return contact\n\n\ndef findFirstContactWithEmail(emailAddr):\n    return _findFirstEntityOf('contact', 'email', emailAddr)\ndef findFirstCompanyWithWebsite(websiteUrl):\n    return _findFirstEntityOf('sales_account', 'website', websiteUrl)\ndef _findFirstEntityOf(entityType, query, queryValue):\n    url = f'{FS_API_URL}/lookup?f={query}&entities={entityType}'\n    from furl import furl\n    ic(url, furl(f'{FS_API_URL}/lookup?f={query}&entities={entityType}').set(\n        {'q': queryValue}).url)\n\n    resp = requests.get(\n        f'{FS_API_URL}/lookup?f={query}&entities={entityType}',\n        params={'q': queryValue}, headers=FS_AUTH_HEADERS)\n    entities = (\n        resp.json() or {}).get(f'{entityType}s', {}).get(f'{entityType}s', [])\n\n    entity = lget(entities, 0)\n    return entity\n\n\ndef createNote(entityType, entityId, message):\n    data = {\n        'note': {\n            'description': message,\n            'targetable_id': entityId,\n            'targetable_type': entityType,\n            }\n        }\n    resp = requests.post(\n        f'{FS_API_URL}/notes', json=data, headers=FS_AUTH_HEADERS)\n\n    if resp.status_code != 201:\n        err = f'Failed to create {entityType} note for id {entityId}.'\n        raise RuntimeError(err)\n\n\ndef createLead(data):\n    return _createEntity('lead', data)\n\ndef createContact(data):\n    ANSGAR_GRUNSEID = 9000013180\n    data.setdefault('owner_id', ANSGAR_GRUNSEID)\n    return _createEntity('contact', data)\n\ndef createCompany(data):\n    return _createEntity('sales_account', data)\n\ndef _createEntity(entityType, data):\n    wrapped = {entityType: data}\n    url = f'{FS_API_URL}/{entityType}s'\n    resp = requests.post(url, json=wrapped, headers=FS_AUTH_HEADERS)\n\n    if resp.status_code not in [200, 201]:\n        raise RuntimeError(f'Failed to create new {entityType}.')\n\n    entity = (resp.json() or {}).get(entityType)\n    return entity\n\n\ndef updateLead(leadId, data):\n    return _updateEntity('lead', leadId, data)\n\ndef updateContact(contactId, data):\n    return _updateEntity('contact', contactId, data)\n\ndef updateCompany(companyId, data):\n    return _updateEntity('sales_account', companyId, data)\n\ndef _updateEntity(entityType, entityId, data):\n    wrapped = {entityType: data}\n    url = f'{FS_API_URL}/{entityType.lower()}s/{entityId}'\n    resp = requests.put(url, json=wrapped, headers=FS_AUTH_HEADERS)\n\n    if resp.status_code != 200:\n        err = f'Failed to update {entityType.title()} with id {entityId}.'\n        raise RuntimeError(err)\n\n    entity = (resp.json() or {}).get(entityType)\n    return entity\n\n\ndef lookupContactsInView(viewId):\n    return _lookupEntitiesInView('contact', viewId)\n\ndef _lookupEntitiesInView(entityType, viewId):\n    entities = []\n\n    url = f'{FS_API_URL}/{entityType.lower()}s/view/{viewId}'\n    def pageUrl(pageNo):\n        return url + f'?page={pageNo}'\n\n    resp = requests.get(url, headers=FS_AUTH_HEADERS)\n    js = resp.json()\n    entities += js.get(f'{entityType}s')\n    totalPages = js.get('meta', {}).get('total_pages')\n\n    for pageNo in range(2, totalPages + 1):\n        resp = requests.get(pageUrl(pageNo), headers=FS_AUTH_HEADERS)\n        entities += (resp.json() or {}).get(f'{entityType}s')\n\n    return entities\n\n\ndef unsubscribeContact(contact, reasons):\n    UNSUBSCRIBED = 9000159966\n    updateContact(contact['id'], {\n        'do_not_disturb': True,\n        'contact_status_id': UNSUBSCRIBED,\n    })\n\n    dateStr = time.ctime()\n    reasonsStr = pprint.pformat(reasons)\n    note = (\n        f'This Contact unsubscribed on arc.io/unsubscribe at [{dateStr}] '\n        'because:\\n'\n        '\\n'\n        f'{reasonsStr}\\n'\n        '\\n')\n    createNote('Contact', contact['id'], note)\n\n\ndef optContactIn(contact):\n    OPTED_IN = 9000159976\n    updateContact(contact['id'], {\n        'contact_status_id': OPTED_IN,\n        })\n\n\ndef createAndOrAssociateCompanyWithContact(websiteUrl, contact):\n    if 'sales_accounts' not in contact:\n        contact = lookupFullContact(contact)\n\n    companyToAdd = None\n    companies = contact.get('sales_accounts', [])\n    company = findFirstCompanyWithWebsite(websiteUrl)\n    if company:\n        companyId = company['id']\n        alreadyRelated = any(companyId == c['id'] for c in companies)\n        if not alreadyRelated:\n            companyToAdd = company\n    else:\n        companyToAdd = createCompany({\n            'name': websiteUrl,\n            'website': websiteUrl,\n            })\n\n    if companyToAdd:\n        companyData = {\n            'id': companyToAdd['id'],\n            # There can only be one primary Company associated with a\n            # Contact. See https://www.freshsales.io/api/#create_contact.\n            'is_primary': False if companies else True,\n            }\n        companies.append(companyData)\n\n    updateContact(contact['id'], { 'sales_accounts': companies })\n\n    return company or companyToAdd\n\n\ndef upgradeContactWhoSubmittedSplashPage(contact, websiteUrl):\n    createAndOrAssociateCompanyWithContact(websiteUrl, contact)\n\n    SUBMITTED_ARC_IO_SIGN_UP_FORM = 9000159955\n    updateContact(contact['id'], {\n        'contact_status_id': SUBMITTED_ARC_IO_SIGN_UP_FORM,\n    })\n\n    dateStr = time.ctime()\n    emailAddr = contact['email']\n    note = (\n        f'This Contact submitted the sign up form on arc.io at [{dateStr}] '\n        f'with email address [{emailAddr}] and website [{websiteUrl}].')\n    createNote('Contact', contact['id'], note)\n\n\ndef noteContactSubmittedPepSplashPage(contact, websiteUrl):\n    createAndOrAssociateCompanyWithContact(websiteUrl, contact)\n    \n    PEP = 9000004543\n    updateContact(contact['id'], {\n        'custom_field': {\n            'cf_product': 'Pep',\n            },\n        })\n\n    dateStr = time.ctime()\n    emailAddr = contact['email']\n    note = (\n        f\"This Contact submitted Pep's sign up form on pep.dev at [{dateStr}] \"\n        f'with email address [{emailAddr}] and website [{websiteUrl}].')\n    createNote('Contact', contact['id'], note)\n\n\ndef createCrawledIndieHackersContact(name, emailAddr, websiteUrl, noteData):\n    INDIE_HACKERS = 9000321821\n    _createCrawledContact(name, emailAddr, websiteUrl, INDIE_HACKERS, noteData)\n\ndef _createCrawledContact(name, emailAddr, websiteUrl, leadSourceId, noteData):\n    firstName, lastName = splitName(name)\n\n    SUSPECT = 9000073090\n    contact = createContact({\n        'email': emailAddr,\n        'first_name': firstName,\n        'last_name': lastName,\n        'contact_status_id': SUSPECT,\n        'lead_source_id': leadSourceId,\n    })\n\n    createAndOrAssociateCompanyWithContact(websiteUrl, contact)\n\n    dateStr = time.ctime()\n    reasonsStr = pprint.pformat(noteData)\n    note = (\n        f'This Contact was crawled and created on [{dateStr}]. '\n        'Other data:'\n        '\\n'\n        f'{reasonsStr}\\n'\n        '\\n')\n    createNote('Contact', contact['id'], note)\n\n\ndef createSplashPageLead(name, emailAddr, websiteUrl):\n    firstName, lastName = splitName(name)\n\n    INTERESTED = 9000057526\n    ARC_IO_SIGN_UP_FORM = 9000315608\n    lead = createLead({\n        'first_name': firstName,\n        'last_name': lastName,\n        'email': emailAddr,\n        'company': {\n            'website': websiteUrl,\n            },\n        'lead_stage_id': INTERESTED,\n        'lead_source_id': ARC_IO_SIGN_UP_FORM,\n    })\n\n    dateStr = time.ctime()\n    note = (\n        f'This Lead was created on [{dateStr}] because they submitted '\n        f'the sign up form on arc.io with email address [{emailAddr}] '\n        f'and website [{websiteUrl}].')\n    createNote('Lead', lead['id'], note)\n\n\ndef createPepSplashPageLead(emailAddr, websiteUrl):\n    PEP = 9000004543\n    INTERESTED = 9000057526\n    PEP_SIGN_UP_FORM = 9000321929\n    lead = createLead({\n        'first_name': DEFAULT_FIRST_NAME,\n        'last_name': DEFAULT_LAST_NAME,\n        'email': emailAddr,\n        'company': {\n            'website': websiteUrl,\n            },\n        'deal': {\n            'deal_product_id': PEP,\n            },\n        'lead_stage_id': INTERESTED,\n        'lead_source_id': PEP_SIGN_UP_FORM,\n    })\n\n    dateStr = time.ctime()\n    note = (\n        f'This Lead was created on [{dateStr}] because they submitted '\n        f'the sign up form on pep.dev with email address [{emailAddr}] '\n        f'and website [{websiteUrl}].')\n    createNote('Lead', lead['id'], note)\n\n\ndef noteACustomersFirstWidgetReport(emailAddr, seenOnUrl):\n    raise NotImplementedError\n\n    # TODO(grun): Finish me.\n\n    contact = findFirstContactWithEmail(emailAddr)\n    if contact:\n        note = (\n            f'The widget for Arc account with email {emailAddr} was just seen '\n            f'live for the first seen for the first time live on {seenOnUrl}.')\n        createNote('Contact', contact['id'], note)\n    else:\n        # TODO(grun): Log this scenario, which means someone added Arc's widget\n        # to someone\n        ic()\n\n\n# TODO(grun): Refactor and/or rename the below handleWordPressPlugin*()\n# functions, like how the above handle*() functions were refactored.\ndef handleWordPressPluginInstall(emailAddr, websiteUrl):\n    WORDPRESS = 9000321857\n    ALPHA_CODE = 9000124404\n\n    contact = findFirstContactWithEmail(emailAddr)\n    if contact:\n        updateContact(contact['id'], {\n            'lead_source_id': WORDPRESS,\n            'contact_status_id': ALPHA_CODE,\n            })\n    else:\n        contact = createContact({\n            'email': emailAddr,\n            'first_name': 'there',\n            'last_name': websiteUrl,\n            'lead_source_id': WORDPRESS,\n            'contact_status_id': ALPHA_CODE,\n            })\n\n    CUSTOMER = 9000095000\n    company = createAndOrAssociateCompanyWithContact(websiteUrl, contact)\n    updateCompany(company['id'], {\n        'business_type_id': CUSTOMER,\n        'custom_field': {\n            'cf_source': 'Wordpress',\n            },\n        })\n\n    dateStr = time.ctime()\n    note = (\n        f\"This Contact installed Arc's WordPress plugin at [{dateStr}] on \"\n        \"website [{websiteUrl}].\")\n    createNote('Contact', contact['id'], note)\n\n\ndef handleWordPressPluginCreatedArcAccount(emailAddr):\n    contact = findFirstContactWithEmail(emailAddr)\n\n    if not contact:\n        return\n\n    CUSTOMER = 9000066454\n    updateContact(contact['id'], { 'contact_status_id': CUSTOMER })\n\n    dateStr = time.ctime()\n    note = (\n        f'This WordPress Contact created their Arc account at [{dateStr}].')\n    createNote('Contact', contact['id'], note)\n\n\ndef handleWordPressPluginUninstall(emailAddr):\n    contact = findFirstContactWithEmail(emailAddr)\n\n    if not contact:\n        return\n\n    FORMER_CUSTOMER = 9000124405\n    updateContact(contact['id'], { 'contact_status_id': FORMER_CUSTOMER })\n\n    dateStr = time.ctime()\n    note = (\n        f'This Contact uninstalled their WordPress plugin at [{dateStr}].')\n    createNote('Contact', contact['id'], note)\n\n\nif __name__ == '__main__':  # For development only.\n    ic(findFirstCompanyWithWebsite('http://blockchainexamples.com'))\n"
  },
  {
    "path": "failures-to-investigate/freshsales2.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n# Copyright _!_\n#\n# License _!_\n\nfrom os.path import abspath, dirname, join as pjoin\nimport pprint\nimport sys\nimport time\nfrom urllib.parse import urlparse\n\nimport requests\nfrom icecream import ic\n\n_corePath = abspath(pjoin(dirname(__file__), '../'))\nif _corePath not in sys.path:\n    sys.path.append(_corePath)\nfrom common.utils import lget, stripStringStart\n\n\nDEFAULT_FIRST_NAME = 'there'\nDEFAULT_LAST_NAME = '-'\nFRESH_SALES_API_KEY = 'P3bYheaquAHH1_hNxhMUDQ'\nFS_API_URL = 'https://arcindustriesinc.freshsales.io/api'\nFS_AUTH_HEADERS = {'Authorization': 'Token token=P3bYheaquAHH1_hNxhMUDQ'}\n\n\n#\n# FreshSales' Contact field magic values looked up with\n#\n#   curl -H \"Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ\" -H \"Content-Type: application/json\" -X GET \"https://arcindustriesinc.freshsales.io/api/settings/contacts/fields\"\n#\n#\n# FreshSales' Lead field magic values looked up with\n#\n#   curl -H \"Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ\" -H \"Content-Type: application/json\" -X GET \"https://arcindustriesinc.freshsales.io/api/settings/leads/fields\"\n#\n# FreshSales' Company field magic values looked up with\n#\n#   curl -H \"Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ\" -H \"Content-Type: application/json\" -X GET \"https://arcindustriesinc.freshsales.io/api/settings/sales_accounts/fields\"\n#\n\n\ndef splitName(name):\n    # Intelligently split <name> into first and last name, if provided.\n    #   '' -> firstName: '', lastName: '-'\n    #   'Susan' -> firstName: 'Susan', lastName: '-'\n    #   'Greg Borp' -> firstName: 'Greg', lastName: 'Borp'\n    #   'Freddy van der Field' -> firstName: 'Freddy', lastName: 'van der Field'\n    toks = name.split(None, 1)\n    firstName = lget(toks, 0, DEFAULT_FIRST_NAME)\n    lastName = lget(toks, 1, DEFAULT_LAST_NAME)\n\n    return firstName, lastName\n\n\ndef lookupFullContact(contact):\n    contactId = contact['id']\n    resp = requests.get(\n        f'{FS_API_URL}/contacts/{contactId}?include=sales_accounts',\n        headers=FS_AUTH_HEADERS)\n    contact = (resp.json() or {}).get('contact')\n    return contact\n\n\ndef findFirstContactWithEmail(emailAddr):\n    contacts = _findEntitiesWith('contact', 'email', emailAddr)\n    return lget(contacts, 0)\ndef findFirstCompanyWithWebsite(websiteUrl):\n    # FreshSales' API returns unrelated companies. For example, searching for\n    # companies with website 'http://blockchainexamples.com', ie\n    #\n    #   ?f=website&entities=sales_account&q=http%3A%2F%2Fblockchainexamples.com\n    #\n    # returns https://arcindustriesinc.freshsales.io/accounts/9001963743 with\n    # name 'http://culturetv.club' and website\n    # 'http://104.225.221.170:8082'. Why? Who knows.\n    #\n    # As a workaround, filter all returned companies to verify that the domains\n    # match.\n    hostNoWww = lambda url: stripStringStart(urlparse(url).hostname, 'www.')\n    allCompanies = _findEntitiesWith('sales_account', 'website', websiteUrl)\n    ic(allCompanies)\n    companies = [\n        c for c in _findEntitiesWith('sales_account', 'website', websiteUrl)\n        if ic(hostNoWww(c.get('website'))) == ic(hostNoWww(websiteUrl))]\n    firstCompany = lget(companies, 0)\n    return firstCompany\ndef _findEntitiesWith(entityType, query, queryValue):\n    resp = requests.get(\n        f'{FS_API_URL}/lookup?f={query}&entities={entityType}',\n        params={'q': queryValue}, headers=FS_AUTH_HEADERS)\n    entities = (\n        resp.json() or {}).get(f'{entityType}s', {}).get(f'{entityType}s', [])\n    return entities\n\n\ndef createNote(entityType, entityId, message):\n    data = {\n        'note': {\n            'description': message,\n            'targetable_id': entityId,\n            'targetable_type': entityType,\n            }\n        }\n    resp = requests.post(\n        f'{FS_API_URL}/notes', json=data, headers=FS_AUTH_HEADERS)\n\n    if resp.status_code != 201:\n        err = f'Failed to create {entityType} note for id {entityId}.'\n        raise RuntimeError(err)\n\n\ndef createLead(data):\n    return _createEntity('lead', data)\n\ndef createContact(data):\n    ANSGAR_GRUNSEID = 9000013180\n    data.setdefault('owner_id', ANSGAR_GRUNSEID)\n    return _createEntity('contact', data)\n\ndef createCompany(data):\n    return _createEntity('sales_account', data)\n\ndef _createEntity(entityType, data):\n    wrapped = {entityType: data}\n    url = f'{FS_API_URL}/{entityType}s'\n    resp = requests.post(url, json=wrapped, headers=FS_AUTH_HEADERS)\n\n    if resp.status_code not in [200, 201]:\n        raise RuntimeError(f'Failed to create new {entityType}.')\n\n    entity = (resp.json() or {}).get(entityType)\n    return entity\n\n\ndef updateLead(leadId, data):\n    return _updateEntity('lead', leadId, data)\n\ndef updateContact(contactId, data):\n    return _updateEntity('contact', contactId, data)\n\ndef updateCompany(companyId, data):\n    return _updateEntity('sales_account', companyId, data)\n\ndef _updateEntity(entityType, entityId, data):\n    wrapped = {entityType: data}\n    url = f'{FS_API_URL}/{entityType.lower()}s/{entityId}'\n    resp = requests.put(url, json=wrapped, headers=FS_AUTH_HEADERS)\n\n    if resp.status_code != 200:\n        err = f'Failed to update {entityType.title()} with id {entityId}.'\n        raise RuntimeError(err)\n\n    entity = (resp.json() or {}).get(entityType)\n    return entity\n\n\ndef lookupContactsInView(viewId):\n    return _lookupEntitiesInView('contact', viewId)\n\ndef _lookupEntitiesInView(entityType, viewId):\n    entities = []\n\n    url = f'{FS_API_URL}/{entityType.lower()}s/view/{viewId}'\n    def pageUrl(pageNo):\n        return url + f'?page={pageNo}'\n\n    resp = requests.get(url, headers=FS_AUTH_HEADERS)\n    js = resp.json()\n    entities += js.get(f'{entityType}s')\n    totalPages = js.get('meta', {}).get('total_pages')\n\n    for pageNo in range(2, totalPages + 1):\n        resp = requests.get(pageUrl(pageNo), headers=FS_AUTH_HEADERS)\n        entities += (resp.json() or {}).get(f'{entityType}s')\n\n    return entities\n\n\ndef unsubscribeContact(contact, reasons):\n    UNSUBSCRIBED = 9000159966\n    updateContact(contact['id'], {\n        'do_not_disturb': True,\n        'contact_status_id': UNSUBSCRIBED,\n    })\n\n    dateStr = time.ctime()\n    reasonsStr = pprint.pformat(reasons)\n    note = (\n        f'This Contact unsubscribed on arc.io/unsubscribe at [{dateStr}] '\n        'because:\\n'\n        '\\n'\n        f'{reasonsStr}\\n'\n        '\\n')\n    createNote('Contact', contact['id'], note)\n\n\ndef optContactIn(contact):\n    OPTED_IN = 9000159976\n    updateContact(contact['id'], {\n        'contact_status_id': OPTED_IN,\n        })\n\n\ndef createAndOrAssociateCompanyWithContact(websiteUrl, contact):\n    if 'sales_accounts' not in contact:\n        contact = lookupFullContact(contact)\n\n    companyToAdd = None\n    companies = contact.get('sales_accounts', [])\n    company = findFirstCompanyWithWebsite(websiteUrl)\n    if company:\n        companyId = company['id']\n        alreadyRelated = any(companyId == c['id'] for c in companies)\n        if not alreadyRelated:\n            companyToAdd = company\n    else:\n        companyToAdd = createCompany({\n            'name': websiteUrl,\n            'website': websiteUrl,\n            })\n\n    if companyToAdd:\n        companyData = {\n            'id': companyToAdd['id'],\n            # There can only be one primary Company associated with a\n            # Contact. See https://www.freshsales.io/api/#create_contact.\n            'is_primary': False if companies else True,\n            }\n        companies.append(companyData)\n\n    updateContact(contact['id'], { 'sales_accounts': companies })\n\n    return company or companyToAdd\n\n\ndef upgradeContactWhoSubmittedSplashPage(contact, websiteUrl):\n    createAndOrAssociateCompanyWithContact(websiteUrl, contact)\n\n    SUBMITTED_ARC_IO_SIGN_UP_FORM = 9000159955\n    updateContact(contact['id'], {\n        'contact_status_id': SUBMITTED_ARC_IO_SIGN_UP_FORM,\n    })\n\n    dateStr = time.ctime()\n    emailAddr = contact['email']\n    note = (\n        f'This Contact submitted the sign up form on arc.io at [{dateStr}] '\n        f'with email address [{emailAddr}] and website [{websiteUrl}].')\n    createNote('Contact', contact['id'], note)\n\n\ndef noteContactSubmittedPepSplashPage(contact, websiteUrl):\n    createAndOrAssociateCompanyWithContact(websiteUrl, contact)\n    \n    PEP = 9000004543\n    updateContact(contact['id'], {\n        'custom_field': {\n            'cf_product': 'Pep',\n            },\n        })\n\n    dateStr = time.ctime()\n    emailAddr = contact['email']\n    note = (\n        f\"This Contact submitted Pep's sign up form on pep.dev at [{dateStr}] \"\n        f'with email address [{emailAddr}] and website [{websiteUrl}].')\n    createNote('Contact', contact['id'], note)\n\n\ndef createCrawledIndieHackersContact(name, emailAddr, websiteUrl, noteData):\n    INDIE_HACKERS = 9000321821\n    _createCrawledContact(name, emailAddr, websiteUrl, INDIE_HACKERS, noteData)\n\ndef _createCrawledContact(name, emailAddr, websiteUrl, leadSourceId, noteData):\n    firstName, lastName = splitName(name)\n\n    SUSPECT = 9000073090\n    contact = createContact({\n        'email': emailAddr,\n        'first_name': firstName,\n        'last_name': lastName,\n        'contact_status_id': SUSPECT,\n        'lead_source_id': leadSourceId,\n    })\n\n    createAndOrAssociateCompanyWithContact(websiteUrl, contact)\n\n    dateStr = time.ctime()\n    reasonsStr = pprint.pformat(noteData)\n    note = (\n        f'This Contact was crawled and created on [{dateStr}]. '\n        'Other data:'\n        '\\n'\n        f'{reasonsStr}\\n'\n        '\\n')\n    createNote('Contact', contact['id'], note)\n\n\ndef createSplashPageLead(name, emailAddr, websiteUrl):\n    firstName, lastName = splitName(name)\n\n    INTERESTED = 9000057526\n    ARC_IO_SIGN_UP_FORM = 9000315608\n    lead = createLead({\n        'first_name': firstName,\n        'last_name': lastName,\n        'email': emailAddr,\n        'company': {\n            'website': websiteUrl,\n            },\n        'lead_stage_id': INTERESTED,\n        'lead_source_id': ARC_IO_SIGN_UP_FORM,\n    })\n\n    dateStr = time.ctime()\n    note = (\n        f'This Lead was created on [{dateStr}] because they submitted '\n        f'the sign up form on arc.io with email address [{emailAddr}] '\n        f'and website [{websiteUrl}].')\n    createNote('Lead', lead['id'], note)\n\n\ndef createPepSplashPageLead(emailAddr, websiteUrl):\n    PEP = 9000004543\n    INTERESTED = 9000057526\n    PEP_SIGN_UP_FORM = 9000321929\n    lead = createLead({\n        'first_name': DEFAULT_FIRST_NAME,\n        'last_name': DEFAULT_LAST_NAME,\n        'email': emailAddr,\n        'company': {\n            'website': websiteUrl,\n            },\n        'deal': {\n            'deal_product_id': PEP,\n            },\n        'lead_stage_id': INTERESTED,\n        'lead_source_id': PEP_SIGN_UP_FORM,\n    })\n\n    dateStr = time.ctime()\n    note = (\n        f'This Lead was created on [{dateStr}] because they submitted '\n        f'the sign up form on pep.dev with email address [{emailAddr}] '\n        f'and website [{websiteUrl}].')\n    createNote('Lead', lead['id'], note)\n\n\ndef noteACustomersFirstWidgetReport(emailAddr, seenOnUrl):\n    raise NotImplementedError\n\n    # TODO(grun): Finish me.\n\n    contact = findFirstContactWithEmail(emailAddr)\n    if contact:\n        note = (\n            f'The widget for Arc account with email {emailAddr} was just seen '\n            f'live for the first seen for the first time live on {seenOnUrl}.')\n        createNote('Contact', contact['id'], note)\n    else:\n        # TODO(grun): Log this scenario, which means someone added Arc's widget\n        # to someone\n        ic()\n\n\n# TODO(grun): Refactor and/or rename the below handleWordPressPlugin*()\n# functions, like how the above handle*() functions were refactored.\ndef handleWordPressPluginInstall(emailAddr, websiteUrl):\n    WORDPRESS = 9000321857\n    ALPHA_CODE = 9000124404\n\n    contact = findFirstContactWithEmail(emailAddr)\n    if contact:\n        updateContact(contact['id'], {\n            'lead_source_id': WORDPRESS,\n            'contact_status_id': ALPHA_CODE,\n            })\n    else:\n        contact = createContact({\n            'email': emailAddr,\n            'first_name': 'there',\n            'last_name': websiteUrl,\n            'lead_source_id': WORDPRESS,\n            'contact_status_id': ALPHA_CODE,\n            })\n\n    CUSTOMER = 9000095000\n    company = createAndOrAssociateCompanyWithContact(websiteUrl, contact)\n    updateCompany(company['id'], {\n        'business_type_id': CUSTOMER,\n        'custom_field': {\n            'cf_source': 'Wordpress',\n            },\n        })\n\n    dateStr = time.ctime()\n    note = (\n        f\"This Contact installed Arc's WordPress plugin at [{dateStr}] on \"\n        \"website [{websiteUrl}].\")\n    createNote('Contact', contact['id'], note)\n\n\ndef handleWordPressPluginCreatedArcAccount(emailAddr):\n    contact = findFirstContactWithEmail(emailAddr)\n\n    if not contact:\n        return\n\n    CUSTOMER = 9000066454\n    updateContact(contact['id'], { 'contact_status_id': CUSTOMER })\n\n    dateStr = time.ctime()\n    note = (\n        f'This WordPress Contact created their Arc account at [{dateStr}].')\n    createNote('Contact', contact['id'], note)\n\n\ndef handleWordPressPluginUninstall(emailAddr):\n    contact = findFirstContactWithEmail(emailAddr)\n\n    if not contact:\n        return\n\n    FORMER_CUSTOMER = 9000124405\n    updateContact(contact['id'], { 'contact_status_id': FORMER_CUSTOMER })\n\n    dateStr = time.ctime()\n    note = (\n        f'This Contact uninstalled their WordPress plugin at [{dateStr}].')\n    createNote('Contact', contact['id'], note)\n\n\nif __name__ == '__main__':  # For development only.\n    ic(findFirstCompanyWithWebsite('http://blockchainexamples.com'))\n    #ic(findFirstCompanyWithWebsite('http://culturetv.club'))\n"
  },
  {
    "path": "failures-to-investigate/freshsales3.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n# Copyright _!_\n#\n# License _!_\n\nfrom os.path import abspath, dirname, join as pjoin\nimport pprint\nimport sys\nimport time\nfrom urllib.parse import urlparse\n\nimport requests\nfrom icecream import ic\n\n_corePath = abspath(pjoin(dirname(__file__), '../'))\nif _corePath not in sys.path:\n    sys.path.append(_corePath)\nfrom common.utils import lget, stripStringStart\n\n\nDEFAULT_FIRST_NAME = 'there'\nDEFAULT_LAST_NAME = '-'\nFRESH_SALES_API_KEY = 'P3bYheaquAHH1_hNxhMUDQ'\nFS_API_URL = 'https://arcindustriesinc.freshsales.io/api'\nFS_AUTH_HEADERS = {'Authorization': 'Token token=P3bYheaquAHH1_hNxhMUDQ'}\n\n\n#\n# FreshSales' Contact field magic values looked up with\n#\n#   curl -H \"Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ\" -H \"Content-Type: application/json\" -X GET \"https://arcindustriesinc.freshsales.io/api/settings/contacts/fields\"\n#\n#\n# FreshSales' Lead field magic values looked up with\n#\n#   curl -H \"Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ\" -H \"Content-Type: application/json\" -X GET \"https://arcindustriesinc.freshsales.io/api/settings/leads/fields\"\n#\n# FreshSales' Company field magic values looked up with\n#\n#   curl -H \"Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ\" -H \"Content-Type: application/json\" -X GET \"https://arcindustriesinc.freshsales.io/api/settings/sales_accounts/fields\"\n#\n\n\ndef splitName(name):\n    # Intelligently split <name> into first and last name, if provided.\n    #   '' -> firstName: '', lastName: '-'\n    #   'Susan' -> firstName: 'Susan', lastName: '-'\n    #   'Greg Borp' -> firstName: 'Greg', lastName: 'Borp'\n    #   'Freddy van der Field' -> firstName: 'Freddy', lastName: 'van der Field'\n    toks = name.split(None, 1)\n    firstName = lget(toks, 0, DEFAULT_FIRST_NAME)\n    lastName = lget(toks, 1, DEFAULT_LAST_NAME)\n\n    return firstName, lastName\n\n\ndef lookupFullContact(contact):\n    contactId = contact['id']\n    resp = requests.get(\n        f'{FS_API_URL}/contacts/{contactId}?include=sales_accounts',\n        headers=FS_AUTH_HEADERS)\n    contact = (resp.json() or {}).get('contact')\n    return contact\n\n\ndef findFirstContactWithEmail(emailAddr):\n    contacts = _findEntitiesWith('contact', 'email', emailAddr)\n    return lget(contacts, 0)\ndef findFirstCompanyWithWebsite(websiteUrl):\n    ic('before', websiteUrl)\n    if ic(urlparse(websiteUrl).scheme) is None:\n        websiteUrl = f'http://{websiteUrl}'\n    ic('after', websiteUrl)\n\n    # FreshSales' API returns unrelated companies. For example, searching for\n    # companies with website 'http://blockchainexamples.com', ie\n    #\n    #   ?f=website&entities=sales_account&q=http%3A%2F%2Fblockchainexamples.com\n    #\n    # returns https://arcindustriesinc.freshsales.io/accounts/9001963743 with\n    # name 'http://culturetv.club' and website\n    # 'http://104.225.221.170:8082'. Why? Who knows.\n    #\n    # As a workaround, filter all returned companies to verify that the domains\n    # match.\n    hostNoWww = lambda url: stripStringStart(urlparse(url).hostname, 'www.')\n    allCompanies = _findEntitiesWith('sales_account', 'website', websiteUrl)\n    ic(allCompanies)\n    ic(websiteUrl)\n    companies = [\n        c for c in _findEntitiesWith('sales_account', 'website', websiteUrl)\n        if hostNoWww(c.get('website')) == hostNoWww(websiteUrl)]\n    ic(companies)\n    firstCompany = lget(companies, 0)\n    return firstCompany\ndef _findEntitiesWith(entityType, query, queryValue):\n    resp = requests.get(\n        f'{FS_API_URL}/lookup?f={query}&entities={entityType}',\n        params={'q': queryValue}, headers=FS_AUTH_HEADERS)\n    entities = (\n        resp.json() or {}).get(f'{entityType}s', {}).get(f'{entityType}s', [])\n    return entities\n\n\ndef createNote(entityType, entityId, message):\n    data = {\n        'note': {\n            'description': message,\n            'targetable_id': entityId,\n            'targetable_type': entityType,\n            }\n        }\n    resp = requests.post(\n        f'{FS_API_URL}/notes', json=data, headers=FS_AUTH_HEADERS)\n\n    if resp.status_code != 201:\n        err = f'Failed to create {entityType} note for id {entityId}.'\n        raise RuntimeError(err)\n\n\ndef createLead(data):\n    return _createEntity('lead', data)\n\ndef createContact(data):\n    ANSGAR_GRUNSEID = 9000013180\n    data.setdefault('owner_id', ANSGAR_GRUNSEID)\n    return _createEntity('contact', data)\n\ndef createCompany(data):\n    return _createEntity('sales_account', data)\n\ndef _createEntity(entityType, data):\n    wrapped = {entityType: data}\n    url = f'{FS_API_URL}/{entityType}s'\n    resp = requests.post(url, json=wrapped, headers=FS_AUTH_HEADERS)\n\n    if resp.status_code not in [200, 201]:\n        raise RuntimeError(f'Failed to create new {entityType}.')\n\n    entity = (resp.json() or {}).get(entityType)\n    return entity\n\n\ndef updateLead(leadId, data):\n    return _updateEntity('lead', leadId, data)\n\ndef updateContact(contactId, data):\n    return _updateEntity('contact', contactId, data)\n\ndef updateCompany(companyId, data):\n    return _updateEntity('sales_account', companyId, data)\n\ndef _updateEntity(entityType, entityId, data):\n    wrapped = {entityType: data}\n    url = f'{FS_API_URL}/{entityType.lower()}s/{entityId}'\n    resp = requests.put(url, json=wrapped, headers=FS_AUTH_HEADERS)\n\n    if resp.status_code != 200:\n        err = f'Failed to update {entityType.title()} with id {entityId}.'\n        raise RuntimeError(err)\n\n    entity = (resp.json() or {}).get(entityType)\n    return entity\n\n\ndef lookupContactsInView(viewId):\n    return _lookupEntitiesInView('contact', viewId)\n\ndef _lookupEntitiesInView(entityType, viewId):\n    entities = []\n\n    url = f'{FS_API_URL}/{entityType.lower()}s/view/{viewId}'\n    def pageUrl(pageNo):\n        return url + f'?page={pageNo}'\n\n    resp = requests.get(url, headers=FS_AUTH_HEADERS)\n    js = resp.json()\n    entities += js.get(f'{entityType}s')\n    totalPages = js.get('meta', {}).get('total_pages')\n\n    for pageNo in range(2, totalPages + 1):\n        resp = requests.get(pageUrl(pageNo), headers=FS_AUTH_HEADERS)\n        entities += (resp.json() or {}).get(f'{entityType}s')\n\n    return entities\n\n\ndef unsubscribeContact(contact, reasons):\n    UNSUBSCRIBED = 9000159966\n    updateContact(contact['id'], {\n        'do_not_disturb': True,\n        'contact_status_id': UNSUBSCRIBED,\n    })\n\n    dateStr = time.ctime()\n    reasonsStr = pprint.pformat(reasons)\n    note = (\n        f'This Contact unsubscribed on arc.io/unsubscribe at [{dateStr}] '\n        'because:\\n'\n        '\\n'\n        f'{reasonsStr}\\n'\n        '\\n')\n    createNote('Contact', contact['id'], note)\n\n\ndef optContactIn(contact):\n    OPTED_IN = 9000159976\n    updateContact(contact['id'], {\n        'contact_status_id': OPTED_IN,\n        })\n\n\ndef createAndOrAssociateCompanyWithContact(websiteUrl, contact):\n    if 'sales_accounts' not in contact:\n        contact = lookupFullContact(contact)\n\n    companyToAdd = None\n    companies = contact.get('sales_accounts', [])\n    company = findFirstCompanyWithWebsite(websiteUrl)\n    if company:\n        companyId = company['id']\n        alreadyRelated = any(companyId == c['id'] for c in companies)\n        if not alreadyRelated:\n            companyToAdd = company\n    else:\n        companyToAdd = createCompany({\n            'name': websiteUrl,\n            'website': websiteUrl,\n            })\n\n    if companyToAdd:\n        companyData = {\n            'id': companyToAdd['id'],\n            # There can only be one primary Company associated with a\n            # Contact. See https://www.freshsales.io/api/#create_contact.\n            'is_primary': False if companies else True,\n            }\n        companies.append(companyData)\n\n    updateContact(contact['id'], { 'sales_accounts': companies })\n\n    return company or companyToAdd\n\n\ndef upgradeContactWhoSubmittedSplashPage(contact, websiteUrl):\n    createAndOrAssociateCompanyWithContact(websiteUrl, contact)\n\n    SUBMITTED_ARC_IO_SIGN_UP_FORM = 9000159955\n    updateContact(contact['id'], {\n        'contact_status_id': SUBMITTED_ARC_IO_SIGN_UP_FORM,\n    })\n\n    dateStr = time.ctime()\n    emailAddr = contact['email']\n    note = (\n        f'This Contact submitted the sign up form on arc.io at [{dateStr}] '\n        f'with email address [{emailAddr}] and website [{websiteUrl}].')\n    createNote('Contact', contact['id'], note)\n\n\ndef noteContactSubmittedPepSplashPage(contact, websiteUrl):\n    createAndOrAssociateCompanyWithContact(websiteUrl, contact)\n    \n    PEP = 9000004543\n    updateContact(contact['id'], {\n        'custom_field': {\n            'cf_product': 'Pep',\n            },\n        })\n\n    dateStr = time.ctime()\n    emailAddr = contact['email']\n    note = (\n        f\"This Contact submitted Pep's sign up form on pep.dev at [{dateStr}] \"\n        f'with email address [{emailAddr}] and website [{websiteUrl}].')\n    createNote('Contact', contact['id'], note)\n\n\ndef createCrawledIndieHackersContact(name, emailAddr, websiteUrl, noteData):\n    INDIE_HACKERS = 9000321821\n    _createCrawledContact(name, emailAddr, websiteUrl, INDIE_HACKERS, noteData)\n\ndef _createCrawledContact(name, emailAddr, websiteUrl, leadSourceId, noteData):\n    firstName, lastName = splitName(name)\n\n    SUSPECT = 9000073090\n    contact = createContact({\n        'email': emailAddr,\n        'first_name': firstName,\n        'last_name': lastName,\n        'contact_status_id': SUSPECT,\n        'lead_source_id': leadSourceId,\n    })\n\n    createAndOrAssociateCompanyWithContact(websiteUrl, contact)\n\n    dateStr = time.ctime()\n    reasonsStr = pprint.pformat(noteData)\n    note = (\n        f'This Contact was crawled and created on [{dateStr}]. '\n        'Other data:'\n        '\\n'\n        f'{reasonsStr}\\n'\n        '\\n')\n    createNote('Contact', contact['id'], note)\n\n\ndef createSplashPageLead(name, emailAddr, websiteUrl):\n    firstName, lastName = splitName(name)\n\n    INTERESTED = 9000057526\n    ARC_IO_SIGN_UP_FORM = 9000315608\n    lead = createLead({\n        'first_name': firstName,\n        'last_name': lastName,\n        'email': emailAddr,\n        'company': {\n            'website': websiteUrl,\n            },\n        'lead_stage_id': INTERESTED,\n        'lead_source_id': ARC_IO_SIGN_UP_FORM,\n    })\n\n    dateStr = time.ctime()\n    note = (\n        f'This Lead was created on [{dateStr}] because they submitted '\n        f'the sign up form on arc.io with email address [{emailAddr}] '\n        f'and website [{websiteUrl}].')\n    createNote('Lead', lead['id'], note)\n\n\ndef createPepSplashPageLead(emailAddr, websiteUrl):\n    PEP = 9000004543\n    INTERESTED = 9000057526\n    PEP_SIGN_UP_FORM = 9000321929\n    lead = createLead({\n        'first_name': DEFAULT_FIRST_NAME,\n        'last_name': DEFAULT_LAST_NAME,\n        'email': emailAddr,\n        'company': {\n            'website': websiteUrl,\n            },\n        'deal': {\n            'deal_product_id': PEP,\n            },\n        'lead_stage_id': INTERESTED,\n        'lead_source_id': PEP_SIGN_UP_FORM,\n    })\n\n    dateStr = time.ctime()\n    note = (\n        f'This Lead was created on [{dateStr}] because they submitted '\n        f'the sign up form on pep.dev with email address [{emailAddr}] '\n        f'and website [{websiteUrl}].')\n    createNote('Lead', lead['id'], note)\n\n\ndef noteACustomersFirstWidgetReport(emailAddr, seenOnUrl):\n    raise NotImplementedError\n\n    # TODO(grun): Finish me.\n\n    contact = findFirstContactWithEmail(emailAddr)\n    if contact:\n        note = (\n            f'The widget for Arc account with email {emailAddr} was just seen '\n            f'live for the first seen for the first time live on {seenOnUrl}.')\n        createNote('Contact', contact['id'], note)\n    else:\n        # TODO(grun): Log this scenario, which means someone added Arc's widget\n        # to someone\n        ic()\n\n\n# TODO(grun): Refactor and/or rename the below handleWordPressPlugin*()\n# functions, like how the above handle*() functions were refactored.\ndef handleWordPressPluginInstall(emailAddr, websiteUrl):\n    WORDPRESS = 9000321857\n    ALPHA_CODE = 9000124404\n\n    contact = findFirstContactWithEmail(emailAddr)\n    if contact:\n        updateContact(contact['id'], {\n            'lead_source_id': WORDPRESS,\n            'contact_status_id': ALPHA_CODE,\n            })\n    else:\n        contact = createContact({\n            'email': emailAddr,\n            'first_name': 'there',\n            'last_name': websiteUrl,\n            'lead_source_id': WORDPRESS,\n            'contact_status_id': ALPHA_CODE,\n            })\n\n    CUSTOMER = 9000095000\n    company = createAndOrAssociateCompanyWithContact(websiteUrl, contact)\n    updateCompany(company['id'], {\n        'business_type_id': CUSTOMER,\n        'custom_field': {\n            'cf_source': 'Wordpress',\n            },\n        })\n\n    dateStr = time.ctime()\n    note = (\n        f\"This Contact installed Arc's WordPress plugin at [{dateStr}] on \"\n        \"website [{websiteUrl}].\")\n    createNote('Contact', contact['id'], note)\n\n\ndef handleWordPressPluginCreatedArcAccount(emailAddr):\n    contact = findFirstContactWithEmail(emailAddr)\n\n    if not contact:\n        return\n\n    CUSTOMER = 9000066454\n    updateContact(contact['id'], { 'contact_status_id': CUSTOMER })\n\n    dateStr = time.ctime()\n    note = (\n        f'This WordPress Contact created their Arc account at [{dateStr}].')\n    createNote('Contact', contact['id'], note)\n\n\ndef handleWordPressPluginUninstall(emailAddr):\n    contact = findFirstContactWithEmail(emailAddr)\n\n    if not contact:\n        return\n\n    FORMER_CUSTOMER = 9000124405\n    updateContact(contact['id'], { 'contact_status_id': FORMER_CUSTOMER })\n\n    dateStr = time.ctime()\n    note = (\n        f'This Contact uninstalled their WordPress plugin at [{dateStr}].')\n    createNote('Contact', contact['id'], note)\n\n\nif __name__ == '__main__':  # For development only.\n    #ic(findFirstCompanyWithWebsite('http://www.blockchainexamples.com'))\n    #ic(findFirstCompanyWithWebsite('http://www.culturetv.club'))\n    ic(findFirstCompanyWithWebsite('www.realizeventures.com'))\n"
  },
  {
    "path": "icecream/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@gmail.com\n#\n# License: MIT\n#\n\nfrom .icecream import *  # noqa\nfrom .builtins import install, uninstall\n\n# Import all variables in __version__.py without explicit imports.\nfrom . import __version__\nglobals().update(dict((k, v) for k, v in __version__.__dict__.items()))\n"
  },
  {
    "path": "icecream/__version__.py",
    "content": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@gmail.com\n#\n# License: MIT\n#\n\n__title__ = 'icecream'\n__license__ = 'MIT'\n__version__ = '2.1.10'\n__author__ = 'Ansgar Grunseid'\n__contact__ = 'grunseid@gmail.com'\n__url__ = 'https://github.com/gruns/icecream'\n__description__ = (\n    'Never use print() to debug again: inspect variables, expressions, and '\n    'program execution with a single, simple function call.')\n"
  },
  {
    "path": "icecream/builtins.py",
    "content": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@gmail.com\n#\n# License: MIT\n#\n\nfrom typing import Optional\nimport icecream\n\nbuiltins = __import__('builtins')\n\n\ndef install(\n    ic: str = 'ic',\n    configured_ic: Optional[icecream.IceCreamDebugger] = None\n) -> None:\n    if configured_ic is None:\n        configured_ic = icecream.ic\n    setattr(builtins, ic, configured_ic)\n\n\ndef uninstall(ic: str = 'ic') -> None:\n    delattr(builtins, ic)\n"
  },
  {
    "path": "icecream/coloring.py",
    "content": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@gmail.com\n#\n# License: MIT\n#\n\nfrom pygments.style import Style\nfrom pygments.token import (\n    Text, Name, Error, Other, String, Number, Keyword, Generic, Literal,\n    Comment, Operator, Whitespace, Punctuation)\n\n\n# Solarized: https://ethanschoonover.com/solarized/\nclass SolarizedDark(Style):\n\n    BASE03  = '#002b36' # noqa\n    BASE02  = '#073642' # noqa\n    BASE01  = '#586e75' # noqa\n    BASE00  = '#657b83' # noqa\n    BASE0   = '#839496' # noqa\n    BASE1   = '#93a1a1' # noqa\n    BASE2   = '#eee8d5' # noqa\n    BASE3   = '#fdf6e3' # noqa\n    YELLOW  = '#b58900' # noqa\n    ORANGE  = '#cb4b16' # noqa\n    RED     = '#dc322f' # noqa\n    MAGENTA = '#d33682' # noqa\n    VIOLET  = '#6c71c4' # noqa\n    BLUE    = '#268bd2' # noqa\n    CYAN    = '#2aa198' # noqa\n    GREEN   = '#859900' # noqa\n\n    styles = {\n        Text:                   BASE0,\n        Whitespace:             BASE03,\n        Error:                  RED,\n        Other:                  BASE0,\n\n        Name:                   BASE1,\n        Name.Attribute:         BASE0,\n        Name.Builtin:           BLUE,\n        Name.Builtin.Pseudo:    BLUE,\n        Name.Class:             BLUE,\n        Name.Constant:          YELLOW,\n        Name.Decorator:         ORANGE,\n        Name.Entity:            ORANGE,\n        Name.Exception:         ORANGE,\n        Name.Function:          BLUE,\n        Name.Property:          BLUE,\n        Name.Label:             BASE0,\n        Name.Namespace:         YELLOW,\n        Name.Other:             BASE0,\n        Name.Tag:               GREEN,\n        Name.Variable:          ORANGE,\n        Name.Variable.Class:    BLUE,\n        Name.Variable.Global:   BLUE,\n        Name.Variable.Instance: BLUE,\n\n        String:                 CYAN,\n        String.Backtick:        CYAN,\n        String.Char:            CYAN,\n        String.Doc:             CYAN,\n        String.Double:          CYAN,\n        String.Escape:          ORANGE,\n        String.Heredoc:         CYAN,\n        String.Interpol:        ORANGE,\n        String.Other:           CYAN,\n        String.Regex:           CYAN,\n        String.Single:          CYAN,\n        String.Symbol:          CYAN,\n\n        Number:                 CYAN,\n        Number.Float:           CYAN,\n        Number.Hex:             CYAN,\n        Number.Integer:         CYAN,\n        Number.Integer.Long:    CYAN,\n        Number.Oct:             CYAN,\n\n        Keyword:                GREEN,\n        Keyword.Constant:       GREEN,\n        Keyword.Declaration:    GREEN,\n        Keyword.Namespace:      ORANGE,\n        Keyword.Pseudo:         ORANGE,\n        Keyword.Reserved:       GREEN,\n        Keyword.Type:           GREEN,\n\n        Generic:                BASE0,\n        Generic.Deleted:        BASE0,\n        Generic.Emph:           BASE0,\n        Generic.Error:          BASE0,\n        Generic.Heading:        BASE0,\n        Generic.Inserted:       BASE0,\n        Generic.Output:         BASE0,\n        Generic.Prompt:         BASE0,\n        Generic.Strong:         BASE0,\n        Generic.Subheading:     BASE0,\n        Generic.Traceback:      BASE0,\n\n        Literal:                BASE0,\n        Literal.Date:           BASE0,\n\n        Comment:                BASE01,\n        Comment.Multiline:      BASE01,\n        Comment.Preproc:        BASE01,\n        Comment.Single:         BASE01,\n        Comment.Special:        BASE01,\n\n        Operator:               BASE0,\n        Operator.Word:          GREEN,\n\n        Punctuation:            BASE0,\n    }\n"
  },
  {
    "path": "icecream/icecream.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@gmail.com\n#\n# License: MIT\n#\n\nimport ast\nimport enum\nimport inspect\nimport pprint\nimport sys\nfrom types import FrameType\nfrom typing import Optional, cast, Any, Callable, Generator, List, Sequence, Tuple, Type, Union, cast, Literal\nimport warnings\nfrom datetime import datetime\nimport functools\nfrom contextlib import contextmanager\nfrom os.path import basename, realpath\nfrom textwrap import dedent\n\nimport colorama\nimport executing\nfrom pygments import highlight\n\n# See https://gist.github.com/XVilka/8346728 for color support in various\n# terminals and thus whether to use Terminal256Formatter or\n# TerminalTrueColorFormatter.\nfrom pygments.formatters import Terminal256Formatter\nfrom pygments.lexers import PythonLexer as PyLexer, Python3Lexer as Py3Lexer\n\nfrom .coloring import SolarizedDark\n\n\nclass Sentinel(enum.Enum):\n    absent = object()\n\n\ndef bindStaticVariable(name: str, value: Any) -> Callable:\n    def decorator(fn: Callable) -> Callable:\n        setattr(fn, name, value)\n        return fn\n    return decorator\n\n\n@bindStaticVariable('formatter', Terminal256Formatter(style=SolarizedDark))\n@bindStaticVariable('lexer', Py3Lexer(ensurenl=False))\ndef colorize(s: str) -> str:\n    self = colorize\n    return highlight(\n        s,\n        cast(Py3Lexer, self.lexer),\n        cast(Terminal256Formatter, self.formatter)\n    )  # pyright: ignore[reportFunctionMemberAccess]\n\n\n@contextmanager\ndef supportTerminalColorsInWindows() -> Generator:\n    # Filter and replace ANSI escape sequences on Windows with equivalent Win32\n    # API calls. This code does nothing on non-Windows systems.\n    if sys.platform.startswith('win'):\n        colorama.init()\n        yield\n        colorama.deinit()\n    else:\n        yield\n\n\ndef stderrPrint(*args: object) -> None:\n    print(*args, file=sys.stderr)\n\n\ndef isLiteral(s: str) -> bool:\n    try:\n        ast.literal_eval(s)\n    except Exception:\n        return False\n    return True\n\n\ndef colorizedStderrPrint(s: str) -> None:\n    colored = colorize(s)\n    with supportTerminalColorsInWindows():\n        stderrPrint(colored)\n\n\ndef colorizedStdoutPrint(s: str) -> None:\n    colored = colorize(s)\n    with supportTerminalColorsInWindows():\n        print(colored)\n\n\ndef safe_pformat(obj: object, *args: Any, **kwargs: Any) -> str:\n    \"\"\"pprint.pformat() with a couple of small safety/usability tweaks.\n\n    In addition to the usual TypeError handling below, we special–case\n    \"medium sized\" flat lists. For those, the standard pprint heuristics\n    sometimes choose a one-item-per-line layout which makes the order of\n    values hard to visually follow in ic()'s output. For such lists we\n    prefer the more compact repr()-style representation.\n    \"\"\"\n\n    def _pformat(extra_kwargs: Optional[dict] = None) -> str:\n        # Helper so we always pass the same args/kwargs to pprint.\n        final_kwargs = dict(kwargs)\n        if extra_kwargs:\n            final_kwargs.update(extra_kwargs)\n        return pprint.pformat(obj, *args, **final_kwargs)\n\n    try:\n        # For flat lists we try a slightly wider layout first. This keeps\n        # simple medium-sized lists on a single line in the common case.\n        is_flat_list = (\n            isinstance(obj, list)\n            and not args\n            and 'width' not in kwargs\n            and not any(isinstance(el, (list, tuple, dict, set)) for el in obj)\n        )\n        if is_flat_list:\n            formatted = _pformat({'width': 120})\n        else:\n            formatted = _pformat(None)\n    except TypeError as e:\n        # Sorting likely tripped on symbolic/elementwise comparisons.\n        warnings.warn(f\"pprint failed ({e}); retrying without dict sorting\")\n        try:\n            # Py 3.8+: disable sorting globally for all nested dicts.\n            return _pformat({'sort_dicts': False})\n        except TypeError:\n            # Py < 3.8: last-ditch, always works.\n            return repr(obj)\n\n    # Heuristic: if pprint decided to break a flat, medium-sized list across\n    # many lines, fall back to repr() which keeps the list visually compact\n    # and easier to read in ic()'s prefix/value layout.\n    if is_flat_list and isinstance(obj, list) and 13 <= len(obj) <= 35:\n        lines = formatted.splitlines()\n        if len(lines) > 10:\n            one_line = repr(obj)\n            if len(one_line) <= 120:\n                return one_line\n\n    return formatted\n\n\nDEFAULT_PREFIX = 'ic| '\nDEFAULT_LINE_WRAP_WIDTH = 70  # Characters.\nDEFAULT_CONTEXT_DELIMITER = '- '\nDEFAULT_OUTPUT_FUNCTION = colorizedStderrPrint\nDEFAULT_ARG_TO_STRING_FUNCTION = safe_pformat\n\n\"\"\"\nThis info message is printed instead of the arguments when icecream\nfails to find or access source code that's required to parse and analyze.\nThis can happen, for example, when\n\n  - ic() is invoked inside a REPL or interactive shell, e.g. from the\n    command line (CLI) or with python -i.\n\n  - The source code is mangled and/or packaged, e.g. with a project\n    freezer like PyInstaller.\n\n  - The underlying source code changed during execution. See\n    https://stackoverflow.com/a/33175832.\n\"\"\"\nNO_SOURCE_AVAILABLE_WARNING_MESSAGE = (\n    'Failed to access the underlying source code for analysis. Was ic() '\n    'invoked in a REPL (e.g. from the command line), a frozen application '\n    '(e.g. packaged with PyInstaller), or did the underlying source code '\n    'change during execution?')\n\n\ndef callOrValue(obj: object) -> object:\n    return obj() if callable(obj) else obj\n\n\nclass Source(executing.Source):\n    def get_text_with_indentation(self, node: ast.expr) -> str:\n        result = self.asttokens().get_text(node)\n        if '\\n' in result:\n            result = ' ' * node.first_token.start[1] + result  # type: ignore[attr-defined]\n            result = dedent(result)\n        result = result.strip()\n        return result\n\n\ndef prefixLines(prefix: str, s: str, startAtLine: int = 0) -> List[str]:\n    lines = s.splitlines()\n\n    for i in range(startAtLine, len(lines)):\n        lines[i] = prefix + lines[i]\n\n    return lines\n\n\ndef prefixFirstLineIndentRemaining(prefix: str, s: str) -> List[str]:\n    indent = ' ' * len(prefix)\n    lines = prefixLines(indent, s, startAtLine=1)\n    lines[0] = prefix + lines[0]\n    return lines\n\n\ndef formatPair(prefix: str, arg: Union[str, Sentinel], value: str) -> str:\n    if arg is Sentinel.absent:\n        argLines = []\n        valuePrefix = prefix\n    else:\n        argLines = prefixFirstLineIndentRemaining(prefix, arg)\n        valuePrefix = argLines[-1] + ': '\n\n    looksLikeAString = (value[0] + value[-1]) in [\"''\", '\"\"']\n    if looksLikeAString:  # Align the start of multiline strings.\n        valueLines = prefixLines(' ', value, startAtLine=1)\n        value = '\\n'.join(valueLines)\n\n    valueLines = prefixFirstLineIndentRemaining(valuePrefix, value)\n    lines = argLines[:-1] + valueLines\n    return '\\n'.join(lines)\n\n\nclass _SingleDispatchCallable:\n    def __call__(self, *_: object) -> str:\n        # This is a marker class, not a real thing you should use\n        raise NotImplementedError\n    register: Callable[[Type], Callable]\n\n\ndef singledispatch(func: Callable) -> _SingleDispatchCallable:\n    func = functools.singledispatch(func)\n\n    # add unregister based on https://stackoverflow.com/a/25951784\n    assert func.register.__closure__ is not None\n    closure = dict(zip(func.register.__code__.co_freevars,\n                       func.register.__closure__))\n    registry = closure['registry'].cell_contents\n    dispatch_cache = closure['dispatch_cache'].cell_contents\n\n    def unregister(cls: Type) -> None:\n        del registry[cls]\n        dispatch_cache.clear()\n\n    func.unregister = unregister  # type: ignore[attr-defined]\n    return cast(_SingleDispatchCallable, func)\n\n\n@singledispatch\ndef argumentToString(obj: object) -> str:\n    s = DEFAULT_ARG_TO_STRING_FUNCTION(obj)\n    s = s.replace('\\\\n', '\\n')  # Preserve string newlines in output.\n    return s\n\n\n@argumentToString.register(str)\ndef _(obj: str) -> str:\n    if '\\n' in obj:\n        return \"'''\" + obj + \"'''\"\n\n    return \"'\" + obj.replace('\\\\', '\\\\\\\\') + \"'\"\n\n\nclass IceCreamDebugger:\n    _pairDelimiter = ', '  # Used by the tests in tests/.\n    lineWrapWidth = DEFAULT_LINE_WRAP_WIDTH\n    contextDelimiter = DEFAULT_CONTEXT_DELIMITER\n\n    def __init__(self, prefix: Union[str, Callable[[], str]] =DEFAULT_PREFIX,\n                 outputFunction: Callable[[str], None]=DEFAULT_OUTPUT_FUNCTION,\n                 argToStringFunction: Union[_SingleDispatchCallable, Callable[[Any], str]]=argumentToString, includeContext: bool=False,\n                 contextAbsPath: bool=False):\n        self.enabled = True\n        self.prefix = prefix\n        self.includeContext = includeContext\n        self.outputFunction = outputFunction\n        self.argToStringFunction = argToStringFunction\n        self.contextAbsPath = contextAbsPath\n\n    def __call__(self, *args: object) -> object:\n        if self.enabled:\n            currentFrame = inspect.currentframe()\n            assert currentFrame is not None and currentFrame.f_back is not None\n            callFrame = currentFrame.f_back\n            self.outputFunction(self._format(callFrame, *args))\n\n        if not args:  # E.g. ic().\n            passthrough = None\n        elif len(args) == 1:  # E.g. ic(1).\n            passthrough = args[0]\n        else:  # E.g. ic(1, 2, 3).\n            passthrough = args\n\n        return passthrough\n\n    def format(self, *args: object) -> str:\n        currentFrame = inspect.currentframe()\n        assert currentFrame is not None and currentFrame.f_back is not None\n        callFrame = currentFrame.f_back\n        out = self._format(callFrame, *args)\n        return out\n\n    def _format(self, callFrame: FrameType, *args: object) -> str:\n        prefix = cast(str, callOrValue(self.prefix))\n\n        context = self._formatContext(callFrame)\n        if not args:\n            time = self._formatTime()\n            out = prefix + context + time\n        else:\n            if not self.includeContext:\n                context = ''\n            out = self._formatArgs(\n                callFrame, prefix, context, args)\n\n        return out\n\n    def _formatArgs(self, callFrame: FrameType, prefix: str, context: str, args: Sequence[object]) -> str:\n        callNode = Source.executing(callFrame).node\n        if callNode is not None:\n            assert isinstance(callNode, ast.Call)\n            source = cast(Source, Source.for_frame(callFrame))\n            sanitizedArgStrs = [\n                source.get_text_with_indentation(arg)\n                for arg in callNode.args]\n        else:\n            warnings.warn(\n                NO_SOURCE_AVAILABLE_WARNING_MESSAGE,\n                category=RuntimeWarning, stacklevel=4)\n            sanitizedArgStrs = [Sentinel.absent] * len(args)\n\n        pairs = list(zip(sanitizedArgStrs, cast(List[str], args)))\n\n        out = self._constructArgumentOutput(prefix, context, pairs)\n        return out\n\n    def _constructArgumentOutput(self, prefix: str, context: str, pairs: Sequence[Tuple[Union[str, Sentinel], str]]) -> str:\n        def argPrefix(arg: str) -> str:\n            return '%s: ' % arg\n\n        pairs = [(arg, self.argToStringFunction(val)) for arg, val in pairs]\n        # For cleaner output, if <arg> is a literal, eg 3, \"a string\",\n        # b'bytes', etc, only output the value, not the argument and the\n        # value, because the argument and the value will be identical or\n        # nigh identical. Ex: with ic(\"hello\"), just output\n        #\n        #   ic| 'hello',\n        #\n        # instead of\n        #\n        #   ic| \"hello\": 'hello'.\n        #\n        # When the source for an arg is missing we also only print the value,\n        # since we can't know anything about the argument itself.\n        pairStrs = [\n            val if (arg is Sentinel.absent or isLiteral(arg))\n            else (argPrefix(arg) + val)\n            for arg, val in pairs]\n\n        allArgsOnOneLine = self._pairDelimiter.join(pairStrs)\n        multilineArgs = len(allArgsOnOneLine.splitlines()) > 1\n\n        contextDelimiter = self.contextDelimiter if context else ''\n        allPairs = prefix + context + contextDelimiter + allArgsOnOneLine\n        firstLineTooLong = len(allPairs.splitlines()[0]) > self.lineWrapWidth\n\n        if multilineArgs or firstLineTooLong:\n            # ic| foo.py:11 in foo()\n            #     multilineStr: 'line1\n            #                    line2'\n            #\n            # ic| foo.py:11 in foo()\n            #     a: 11111111111111111111\n            #     b: 22222222222222222222\n            if context:\n                lines = [prefix + context] + [\n                    formatPair(len(prefix) * ' ', arg, value)\n                    for arg, value in pairs\n                ]\n            # ic| multilineStr: 'line1\n            #                    line2'\n            #\n            # ic| a: 11111111111111111111\n            #     b: 22222222222222222222\n            else:\n                argLines = [\n                    formatPair('', arg, value)\n                    for arg, value in pairs\n                ]\n                lines = prefixFirstLineIndentRemaining(prefix, '\\n'.join(argLines))\n        # ic| foo.py:11 in foo()- a: 1, b: 2\n        # ic| a: 1, b: 2, c: 3\n        else:\n            lines = [prefix + context + contextDelimiter + allArgsOnOneLine]\n\n        return '\\n'.join(lines)\n\n    def _formatContext(self, callFrame: FrameType) -> str:\n        filename, lineNumber, parentFunction = self._getContext(callFrame)\n\n        if parentFunction != '<module>':\n            parentFunction = '%s()' % parentFunction\n\n        context = '%s:%s in %s' % (filename, lineNumber, parentFunction)\n        return context\n\n    def _formatTime(self) -> str:\n        now = datetime.now()\n        formatted = now.strftime('%H:%M:%S.%f')[:-3]\n        return ' at %s' % formatted\n\n    def _getContext(self, callFrame: FrameType) -> Tuple[str, int, str]:\n        frameInfo = inspect.getframeinfo(callFrame)\n        lineNumber = frameInfo.lineno\n        parentFunction = frameInfo.function\n\n        filepath = (realpath if self.contextAbsPath else basename)(frameInfo.filename)  # type: ignore[operator]\n        return filepath, lineNumber, parentFunction\n\n    def enable(self) -> None:\n        self.enabled = True\n\n    def disable(self) -> None:\n        self.enabled = False\n\n    def use_stdout(self) -> None:\n        self.outputFunction = colorizedStdoutPrint\n\n    def use_stderr(self) -> None:\n        self.outputFunction = colorizedStderrPrint\n\n    def configureOutput(\n        self: \"IceCreamDebugger\",\n        prefix: Union[str, Literal[Sentinel.absent]] = Sentinel.absent,\n        outputFunction: Union[Callable, Literal[Sentinel.absent]] = Sentinel.absent,\n        argToStringFunction: Union[Callable, Literal[Sentinel.absent]] = Sentinel.absent,\n        includeContext: Union[bool, Literal[Sentinel.absent]] = Sentinel.absent,\n        contextAbsPath: Union[bool, Literal[Sentinel.absent]] = Sentinel.absent,\n        lineWrapWidth: Union[bool, Literal[Sentinel.absent]] = Sentinel.absent\n    ) -> None:\n        noParameterProvided = all(\n            v is Sentinel.absent for k, v in locals().items() if k != 'self')\n        if noParameterProvided:\n            raise TypeError('configureOutput() missing at least one argument')\n\n        if prefix is not Sentinel.absent:\n            self.prefix = prefix\n\n        if outputFunction is not Sentinel.absent:\n            self.outputFunction = outputFunction\n\n        if argToStringFunction is not Sentinel.absent:\n            self.argToStringFunction = argToStringFunction\n\n        if includeContext is not Sentinel.absent:\n            self.includeContext = includeContext\n\n        if contextAbsPath is not Sentinel.absent:\n            self.contextAbsPath = contextAbsPath\n\n        if lineWrapWidth is not Sentinel.absent:\n            self.lineWrapWidth = lineWrapWidth\n\n\nic = IceCreamDebugger()\n"
  },
  {
    "path": "icecream/py.typed",
    "content": ""
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.mypy]\nshow_error_codes=true\ndisallow_untyped_defs=true\ndisallow_untyped_calls=true\nwarn_redundant_casts=true"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\nlicense_files = LICENSE.txt\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@gmail.com\n#\n# License: MIT\n#\n\nimport os\nimport sys\nfrom os.path import dirname, join as pjoin\nfrom setuptools import setup, find_packages, Command\nfrom setuptools.command.test import test as TestCommand\n\n\nmeta = {}\nwith open(pjoin('icecream', '__version__.py')) as f:\n    exec(f.read(), meta)\n\n\nclass Publish(Command):\n    \"\"\"Publish to PyPI with twine.\"\"\"\n    user_options = []\n\n    def initialize_options(self) -> None:\n        pass\n\n    def finalize_options(self) -> None:\n        pass\n\n    def run(self) -> None:\n        os.system('python3 setup.py sdist bdist_wheel')\n\n        sdist = 'dist/icecream-%s.tar.gz' % meta['__version__']\n        wheel = 'dist/icecream-%s-py3-none-any.whl' % meta['__version__']\n        rc = os.system('twine upload \"%s\" \"%s\"' % (sdist, wheel))\n\n        sys.exit(rc)\n\n\nclass RunTests(TestCommand):\n    \"\"\"\n    Run the unit tests.\n\n    By default, `python setup.py test` fails if tests/ isn't a Python\n    module (that is, if the tests/ directory doesn't contain an\n    __init__.py file). But the tests/ directory shouldn't contain an\n    __init__.py file and tests/ shouldn't be a Python module. See\n\n      http://doc.pytest.org/en/latest/goodpractices.html\n\n    Running the unit tests manually here enables `python setup.py test`\n    without tests/ being a Python module.\n    \"\"\"\n    def run_tests(self) -> None:\n        from unittest import TestLoader, TextTestRunner\n        tests_dir = pjoin(dirname(__file__), 'tests')\n        suite = TestLoader().discover(tests_dir)\n        result = TextTestRunner().run(suite)\n        sys.exit(0 if result.wasSuccessful() else -1)\n\n\nsetup(\n    name=meta['__title__'],\n    license=meta['__license__'],\n    version=meta['__version__'],\n    author=meta['__author__'],\n    author_email=meta['__contact__'],\n    url=meta['__url__'],\n    description=meta['__description__'],\n    long_description=(\n        'Information and documentation can be found at '\n        'https://github.com/gruns/icecream.'),\n    platforms=['any'],\n    packages=find_packages(exclude=['tests', 'tests.*']),\n    include_package_data=True,\n    package_data={'icecream': ['py.typed']},\n    classifiers=[\n        'License :: OSI Approved :: MIT License',\n        'Natural Language :: English',\n        'Intended Audience :: Developers',\n        'Topic :: Software Development :: Libraries',\n        'Development Status :: 5 - Production/Stable',\n        'Programming Language :: Python',\n        'Programming Language :: Python :: 3',\n        'Programming Language :: Python :: 3.9',\n        'Programming Language :: Python :: 3.10',\n        'Programming Language :: Python :: 3.11',\n        'Programming Language :: Python :: 3.12',\n        'Programming Language :: Python :: 3.13',\n        'Programming Language :: Python :: 3.14',\n        'Programming Language :: Python :: Implementation :: PyPy',\n        'Programming Language :: Python :: Implementation :: CPython',\n    ],\n    tests_require=[\n        'tox>=4',\n    ],\n    install_requires=[\n        'colorama>=0.3.9',\n        'pygments>=2.2.0',\n        'executing>=2.1.0',\n        'asttokens>=2.0.1',\n    ],\n    cmdclass={\n        'test': RunTests,\n        'publish': Publish,\n    },\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/install_test_import.py",
    "content": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@gmail.com\n#\n# License: MIT\n#\n\ndef runMe():\n    x = 3\n    ic(x)\n"
  },
  {
    "path": "tests/test_icecream.py",
    "content": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@gmail.com\n#\n# License: MIT\n#\n\nimport sys\nimport unittest\nimport warnings\n\nfrom io import StringIO\nfrom contextlib import contextmanager\nfrom os.path import basename, splitext, realpath\n\nimport icecream\nfrom icecream import ic, argumentToString, stderrPrint, NO_SOURCE_AVAILABLE_WARNING_MESSAGE\n\nTEST_PAIR_DELIMITER = '| '\nMY_FILENAME = basename(__file__)\nMY_FILEPATH = realpath(__file__)\n\n\na = 1\nb = 2\nc = 3\n\n\ndef noop(*args, **kwargs):\n    return\n\n\ndef has_ansi_escape_codes(s):\n    # Oversimplified, but ¯\\_(ツ)_/¯. TODO(grun): Test with regex.\n    return '\\x1b[' in s\n\n\nclass FakeTeletypeBuffer(StringIO):\n    \"\"\"\n    Extend StringIO to act like a TTY so ANSI control codes aren't stripped\n    when wrapped with colorama's wrap_stream().\n    \"\"\"\n    def isatty(self):\n        return True\n\n\n@contextmanager\ndef disable_coloring():\n    originalOutputFunction = ic.outputFunction\n\n    ic.configureOutput(outputFunction=stderrPrint)\n    yield\n    ic.configureOutput(outputFunction=originalOutputFunction)\n\n\n@contextmanager\ndef configure_icecream_output(prefix=None, outputFunction=None,\n                            argToStringFunction=None, includeContext=None,\n                            contextAbsPath=None):\n    oldPrefix = ic.prefix\n    oldOutputFunction = ic.outputFunction\n    oldArgToStringFunction = ic.argToStringFunction\n    oldIncludeContext = ic.includeContext\n    oldContextAbsPath = ic.contextAbsPath\n\n    if prefix:\n        ic.configureOutput(prefix=prefix)\n    if outputFunction:\n        ic.configureOutput(outputFunction=outputFunction)\n    if argToStringFunction:\n        ic.configureOutput(argToStringFunction=argToStringFunction)\n    if includeContext:\n        ic.configureOutput(includeContext=includeContext)\n    if contextAbsPath:\n        ic.configureOutput(contextAbsPath=contextAbsPath)\n\n    yield\n\n    ic.configureOutput(\n        oldPrefix, oldOutputFunction, oldArgToStringFunction,\n        oldIncludeContext, oldContextAbsPath)\n\n\n@contextmanager\ndef capture_standard_streams():\n    realStdout = sys.stdout\n    realStderr = sys.stderr\n    newStdout = FakeTeletypeBuffer()\n    newStderr = FakeTeletypeBuffer()\n    try:\n        sys.stdout = newStdout\n        sys.stderr = newStderr\n        yield newStdout, newStderr\n    finally:\n        sys.stdout = realStdout\n        sys.stderr = realStderr\n\n\ndef strip_prefix(line):\n    if line.startswith(ic.prefix):\n        line = line.strip()[len(ic.prefix):]\n    return line\n\n\ndef line_is_context_and_time(line):\n    line = strip_prefix(line)  # ic| f.py:33 in foo() at 08:08:51.389\n    context, time = line.split(' at ')\n\n    return (\n        line_is_context(context) and\n        len(time.split(':')) == 3 and\n        len(time.split('.')) == 2)\n\n\ndef line_is_context(line):\n    line = strip_prefix(line)  # ic| f.py:33 in foo()\n    sourceLocation, function = line.split(' in ')  # f.py:33 in foo()\n    filename, lineNumber = sourceLocation.split(':')  # f.py:33\n    name, ext = splitext(filename)\n\n    return (\n        int(lineNumber) > 0 and\n        ext in ['.py', '.pyc', '.pyo'] and\n        name == splitext(MY_FILENAME)[0] and\n        (function == '<module>' or function.endswith('()')))\n\ndef line_is_abs_path_context(line):\n    line = strip_prefix(line)  # ic| /absolute/path/to/f.py:33 in foo()\n    sourceLocation, function = line.split(' in ')  # /absolute/path/to/f.py:33 in foo()\n    filepath, lineNumber = sourceLocation.split(':')  # /absolute/path/to/f.py:33\n    path, ext = splitext(filepath)\n\n    return (\n        int(lineNumber) > 0 and\n        ext in ['.py', '.pyc', '.pyo'] and\n        path == splitext(MY_FILEPATH)[0] and\n        (function == '<module>' or function.endswith('()')))\n\ndef line_after_context(line, prefix):\n    if line.startswith(prefix):\n        line = line[len(prefix):]\n\n    toks = line.split(' in ', 1)\n    if len(toks) == 2:\n        rest = toks[1].split(' ')\n        line = ' '.join(rest[1:])\n\n    return line\n\ndef parse_output_into_pairs(out, err, assert_num_lines,\n                            prefix=icecream.DEFAULT_PREFIX):\n    if isinstance(out, StringIO):\n        out = out.getvalue()\n    if isinstance(err, StringIO):\n        err = err.getvalue()\n\n    assert not out\n\n    lines = err.splitlines()\n    if assert_num_lines:\n        assert len(lines) == assert_num_lines\n\n    line_pairs = []\n    for line in lines:\n        line = line_after_context(line, prefix)\n\n        if not line:\n            line_pairs.append([])\n            continue\n\n        pairStrs = line.split(TEST_PAIR_DELIMITER)\n        pairs = [tuple(s.split(':', 1)) for s in pairStrs]\n        # Indented line of a multiline value.\n        if len(pairs[0]) == 1 and line.startswith(' '):\n            arg, value = line_pairs[-1][-1]\n            looksLikeAString = value[0] in [\"'\", '\"']\n            prefix = ((arg + ': ' if arg is not None else '')  # A multiline value\n                      + (' ' if looksLikeAString else ''))\n            dedented = line[len(ic.prefix) + len(prefix):]\n            line_pairs[-1][-1] = (arg, value + '\\n' + dedented)\n        else:\n            items = [\n                (None, p[0].strip()) if len(p) == 1  # A value, like ic(3).\n                else (p[0].strip(), p[1].strip())  # A variable, like ic(a).\n                for p in pairs]\n            line_pairs.append(items)\n\n    return line_pairs\n\n\nclass TestIceCream(unittest.TestCase):\n    def setUp(self):\n        ic._pairDelimiter = TEST_PAIR_DELIMITER\n\n    def test_metadata(self):\n        def is_non_empty_string(s):\n            return isinstance(s, str) and s\n        assert is_non_empty_string(icecream.__title__)\n        assert is_non_empty_string(icecream.__version__)\n        assert is_non_empty_string(icecream.__license__)\n        assert is_non_empty_string(icecream.__author__)\n        assert is_non_empty_string(icecream.__contact__)\n        assert is_non_empty_string(icecream.__description__)\n        assert is_non_empty_string(icecream.__url__)\n\n    def test_without_args(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic()\n        assert line_is_context_and_time(err.getvalue())\n\n    def test_as_argument(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            noop(ic(a), ic(b))\n        pairs = parse_output_into_pairs(out, err, 2)\n        assert pairs[0][0] == ('a', '1') and pairs[1][0] == ('b', '2')\n\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            dic = {1: ic(a)}  # noqa\n            lst = [ic(b), ic()]  # noqa\n        pairs = parse_output_into_pairs(out, err, 3)\n        assert pairs[0][0] == ('a', '1')\n        assert pairs[1][0] == ('b', '2')\n        assert line_is_context_and_time(err.getvalue().splitlines()[-1])\n\n    def test_single_argument(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(a)\n        assert parse_output_into_pairs(out, err, 1)[0][0] == ('a', '1')\n\n    def test_multiple_arguments(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(a, b)\n        pairs = parse_output_into_pairs(out, err, 1)[0]\n        assert pairs == [('a', '1'), ('b', '2')]\n\n    def test_nested_multiline(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(\n                )\n        assert line_is_context_and_time(err.getvalue())\n\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(a,\n               'foo')\n        pairs = parse_output_into_pairs(out, err, 1)[0]\n        assert pairs == [('a',  '1'), (None, \"'foo'\")]\n\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            noop(noop(noop({1: ic(\n                noop())})))\n        assert parse_output_into_pairs(out, err, 1)[0][0] == ('noop()', 'None')\n\n    def test_expression_arguments(self):\n        class klass():\n            attr = 'yep'\n        d = {'d': {1: 'one'}, 'k': klass}\n\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(d['d'][1])\n        pair = parse_output_into_pairs(out, err, 1)[0][0]\n        assert pair == (\"d['d'][1]\", \"'one'\")\n\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(d['k'].attr)\n        pair = parse_output_into_pairs(out, err, 1)[0][0]\n        assert pair == (\"d['k'].attr\", \"'yep'\")\n\n    def test_multiple_calls_on_same_line(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(a); ic(b, c)  # noqa\n        pairs = parse_output_into_pairs(out, err, 2)\n        assert pairs[0][0] == ('a', '1')\n        assert pairs[1] == [('b', '2'), ('c', '3')]\n\n    def test_call_surrounded_by_expressions(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            noop(); ic(a); noop()  # noqa\n        assert parse_output_into_pairs(out, err, 1)[0][0] == ('a', '1')\n\n    def test_comments(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            \"\"\"Comment.\"\"\"; ic(); # Comment.  # noqa\n        assert line_is_context_and_time(err.getvalue())\n\n    def test_method_arguments(self):\n        class Foo:\n            def foo(self):\n                return 'foo'\n        f = Foo()\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(f.foo())\n        assert parse_output_into_pairs(out, err, 1)[0][0] == ('f.foo()', \"'foo'\")\n\n    def test_complicated(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            noop(); ic(); noop(); ic(a,  # noqa\n                                     b, noop.__class__.__name__,  # noqa\n                                         noop ()); noop()  # noqa\n        pairs = parse_output_into_pairs(out, err, 2)\n        assert line_is_context_and_time(err.getvalue().splitlines()[0])\n        assert pairs[1] == [\n            ('a', '1'), ('b', '2'), ('noop.__class__.__name__', \"'function'\"),\n            ('noop ()', 'None')]\n\n    def test_return_value(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            assert ic() is None\n            assert ic(1) == 1\n            assert ic(1, 2, 3) == (1, 2, 3)\n\n    def test_different_name(self):\n        from icecream import ic as foo\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            foo()\n        assert line_is_context_and_time(err.getvalue())\n\n        newname = foo\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            newname(a)\n        pair = parse_output_into_pairs(out, err, 1)[0][0]\n        assert pair == ('a', '1')\n\n    def test_prefix_configuration(self):\n        prefix = 'lolsup '\n        with configure_icecream_output(prefix, stderrPrint):\n            with disable_coloring(), capture_standard_streams() as (out, err):\n                ic(a)\n        pair = parse_output_into_pairs(out, err, 1, prefix=prefix)[0][0]\n        assert pair == ('a', '1')\n\n        def prefix_function():\n            return 'lolsup '\n        with configure_icecream_output(prefix=prefix_function):\n            with disable_coloring(), capture_standard_streams() as (out, err):\n                ic(b)\n        pair = parse_output_into_pairs(out, err, 1, prefix=prefix_function())[0][0]\n        assert pair == ('b', '2')\n\n    def test_output_function(self):\n        lst = []\n\n        def append_to(s):\n            lst.append(s)\n\n        with configure_icecream_output(ic.prefix, append_to):\n            with capture_standard_streams() as (out, err):\n                ic(a)\n        assert not out.getvalue() and not err.getvalue()\n\n        with configure_icecream_output(outputFunction=append_to):\n            with capture_standard_streams() as (out, err):\n                ic(b)\n        assert not out.getvalue() and not err.getvalue()\n\n        pairs = parse_output_into_pairs(out, '\\n'.join(lst), 2)\n        assert pairs == [[('a', '1')], [('b', '2')]]\n\n    def test_enable_disable(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            assert ic(a) == 1\n            assert ic.enabled\n\n            ic.disable()\n            assert not ic.enabled\n            assert ic(b) == 2\n\n            ic.enable()\n            assert ic.enabled\n            assert ic(c) == 3\n\n        pairs = parse_output_into_pairs(out, err, 2)\n        assert pairs == [[('a', '1')], [('c', '3')]]\n\n    def test_arg_to_string_function(self):\n        def hello(obj):\n            return 'zwei'\n\n        with configure_icecream_output(argToStringFunction=hello):\n            with disable_coloring(), capture_standard_streams() as (out, err):\n                eins = 'ein'\n                ic(eins)\n        pair = parse_output_into_pairs(out, err, 1)[0][0]\n        assert pair == ('eins', 'zwei')\n\n    def test_singledispatch_argument_to_string(self):\n        def argument_to_string_tuple(obj):\n            return \"Dispatching tuple!\"\n\n        # Prepare input and output\n        x = (1, 2)\n        default_output = ic.format(x)\n\n        # Register\n        argumentToString.register(tuple, argument_to_string_tuple)\n        assert tuple in argumentToString.registry\n        assert str.endswith(ic.format(x), argument_to_string_tuple(x))\n\n        # Unregister\n        argumentToString.unregister(tuple)\n        assert tuple not in argumentToString.registry\n        assert ic.format(x) == default_output\n\n    def test_single_argument_long_line_not_wrapped(self):\n        # A single long line with one argument is not line wrapped.\n        longStr = '*' * (ic.lineWrapWidth + 1)\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(longStr)\n        pair = parse_output_into_pairs(out, err, 1)[0][0]\n        assert len(err.getvalue()) > ic.lineWrapWidth\n        assert pair == ('longStr', ic.argToStringFunction(longStr))\n\n    def test_multiple_arguments_long_line_wrapped(self):\n        # A single long line with multiple variables is line wrapped.\n        val = '*' * int(ic.lineWrapWidth / 4)\n        valStr = ic.argToStringFunction(val)\n\n        v1 = v2 = v3 = v4 = val\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(v1, v2, v3, v4)\n\n        pairs = parse_output_into_pairs(out, err, 4)\n        assert pairs == [[(k, valStr)] for k in ['v1', 'v2', 'v3', 'v4']]\n\n        lines = err.getvalue().splitlines()\n        assert (\n            lines[0].startswith(ic.prefix) and\n            lines[1].startswith(' ' * len(ic.prefix)) and\n            lines[2].startswith(' ' * len(ic.prefix)) and\n            lines[3].startswith(' ' * len(ic.prefix)))\n\n    def test_multiline_value_wrapped(self):\n        # Multiline values are line wrapped.\n        multilineStr = 'line1\\nline2'\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(multilineStr)\n        pair = parse_output_into_pairs(out, err, 2)[0][0]\n        assert pair == ('multilineStr', ic.argToStringFunction(multilineStr))\n\n    def test_include_context_single_line(self):\n        i = 3\n        with configure_icecream_output(includeContext=True):\n            with disable_coloring(), capture_standard_streams() as (out, err):\n                ic(i)\n\n        pair = parse_output_into_pairs(out, err, 1)[0][0]\n        assert pair == ('i', '3')\n\n    def test_context_abs_path_single_line(self):\n        i = 3\n        with configure_icecream_output(includeContext=True, contextAbsPath=True):\n            with disable_coloring(), capture_standard_streams() as (out, err):\n                ic(i)\n        # Output with absolute path can easily exceed line width, so no assert line num here.\n        pairs = parse_output_into_pairs(out, err, 0)\n        assert [('i', '3')] in pairs\n\n    def test_values(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            # Test both 'asdf' and \"asdf\"; see\n            # https://github.com/gruns/icecream/issues/53.\n            ic(3, 'asdf', \"asdf\")\n\n        pairs = parse_output_into_pairs(out, err, 1)\n        assert pairs == [[(None, '3'), (None, \"'asdf'\"), (None, \"'asdf'\")]]\n\n    def test_include_context_multi_line(self):\n        multilineStr = 'line1\\nline2'\n        with configure_icecream_output(includeContext=True):\n            with disable_coloring(), capture_standard_streams() as (out, err):\n                ic(multilineStr)\n\n        firstLine = err.getvalue().splitlines()[0]\n        assert line_is_context(firstLine)\n\n        pair = parse_output_into_pairs(out, err, 3)[1][0]\n        assert pair == ('multilineStr', ic.argToStringFunction(multilineStr))\n\n    def test_context_abs_path_multi_line(self):\n        multilineStr = 'line1\\nline2'\n        with configure_icecream_output(includeContext=True, contextAbsPath=True):\n            with disable_coloring(), capture_standard_streams() as (out, err):\n                ic(multilineStr)\n\n        firstLine = err.getvalue().splitlines()[0]\n        assert line_is_abs_path_context(firstLine)\n\n        pair = parse_output_into_pairs(out, err, 3)[1][0]\n        assert pair == ('multilineStr', ic.argToStringFunction(multilineStr))\n\n    def test_format(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            \"\"\"comment\"\"\"; noop(); ic(  # noqa\n                'sup'); noop()  # noqa\n        \"\"\"comment\"\"\"; noop(); s = ic.format(  # noqa\n            'sup'); noop()  # noqa\n        assert s == err.getvalue().rstrip()\n\n    def test_multiline_invocation_with_comments(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(  # Comment.\n\n                a,  # Comment.\n\n                # Comment.\n\n                b,  # Comment.\n\n                )  # Comment.\n\n        pairs = parse_output_into_pairs(out, err, 1)[0]\n        assert pairs == [('a', '1'), ('b', '2')]\n\n    def test_no_source_available_prints_values(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            with warnings.catch_warnings():\n                # we ignore the warning so that it doesn't interfere\n                # with parsing ic's output\n                warnings.simplefilter(\"ignore\")\n                eval('ic(a, b)')\n                pairs = parse_output_into_pairs(out, err, 1)\n                self.assertEqual(pairs, [[(None, '1'), (None, \"2\")]])\n\n    def test_no_source_available_prints_multiline(self):\n        \"\"\"\n        This tests for a bug which caused only multiline prints to fail.\n        \"\"\"\n        multilineStr = 'line1\\nline2'\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            with warnings.catch_warnings():\n                # we ignore the warning so that it doesn't interfere\n                # with parsing ic's output\n                warnings.simplefilter(\"ignore\")\n                eval('ic(multilineStr)')\n                pair = parse_output_into_pairs(out, err, 2)[0][0]\n                self.assertEqual(pair, (None, ic.argToStringFunction(multilineStr)))\n\n    def test_no_source_available_issues_exactly_one_warning(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            with warnings.catch_warnings(record=True) as all_warnings:\n                eval('ic(a)')\n                eval('ic(b)')\n                assert len(all_warnings) == 1\n                warning = all_warnings[-1]\n                assert NO_SOURCE_AVAILABLE_WARNING_MESSAGE in str(warning.message)\n\n    def test_single_tuple_argument(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic((a, b))\n\n        pair = parse_output_into_pairs(out, err, 1)[0][0]\n        self.assertEqual(pair, ('(a, b)', '(1, 2)'))\n\n    def test_flat_medium_list_prints_on_one_line(self):\n        \"\"\"Flat medium-sized lists should not be split one item per line.\"\"\"\n        data = [1, 1, 1, 1, 1, 1, 1, 1,\n                0, 0, 0, 0, 0, 0, 0, 0,\n                0, 0, 0, 0,\n                1, 1, 1, 1, 1,\n                0, 1, 0, 1, 0, 1, 0]\n\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(data)\n\n        # The whole ic() call should fit on a single line.\n        self.assertEqual(len(err.getvalue().strip().splitlines()), 1)\n\n    def test_multiline_container_args(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic((a,\n                b))\n            ic([a,\n                b])\n            ic((a,\n                b),\n               [list(range(15)),\n                list(range(15))])\n\n        self.assertEqual(err.getvalue().strip(), \"\"\"\nic| (a,\n     b): (1, 2)\nic| [a,\n     b]: [1, 2]\nic| (a,\n     b): (1, 2)\n    [list(range(15)),\n     list(range(15))]: [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],\n                        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]]\n        \"\"\".strip())\n\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            with configure_icecream_output(includeContext=True):\n                ic((a,\n                    b),\n                   [list(range(15)),\n                    list(range(15))])\n\n        lines = err.getvalue().strip().splitlines()\n        self.assertRegex(\n            lines[0],\n            r'ic\\| test_icecream.py:\\d+ in test_multiline_container_args\\(\\)',\n        )\n        self.assertEqual('\\n'.join(lines[1:]), \"\"\"\\\n    (a,\n     b): (1, 2)\n    [list(range(15)),\n     list(range(15))]: [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],\n                        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]]\"\"\")\n\n    def test_multiple_tuple_arguments(self):\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic((a, b), (b, a), a, b)\n\n        pair = parse_output_into_pairs(out, err, 1)[0]\n        self.assertEqual(pair, [\n            ('(a, b)', '(1, 2)'), ('(b, a)', '(2, 1)'), ('a', '1'), ('b', '2')])\n\n    def test_coloring(self):\n        with capture_standard_streams() as (out, err):\n            ic({1: 'str'})  # Output should be colored with ANSI control codes.\n\n        assert has_ansi_escape_codes(err.getvalue())\n\n    def test_configure_output_with_no_parameters(self):\n        with self.assertRaises(TypeError):\n            ic.configureOutput()\n\n    def test_multiline_strings_output(self):\n\n        test1 = \"A\\\\veryvery\\\\long\\\\path\\\\to\\\\no\\\\even\\\\longer\\\\HelloWorld _01_Heritisfinallythe file.file\"\n        test2 = r\"A\\veryvery\\long\\path\\to\\no\\even\\longer\\HelloWorld _01_Heritisfinallythe file.file\"\n        test3 = \"line\\nline\"\n\n        with disable_coloring(), capture_standard_streams() as (_, err):\n            ic(test1)\n            curr_res = err.getvalue().strip()\n            expected = r\"ic| test1: 'A\\\\veryvery\\\\long\\\\path\\\\to\\\\no\\\\even\\\\longer\\\\HelloWorld _01_Heritisfinallythe file.file'\"\n            self.assertEqual(curr_res, expected)\n            del curr_res, expected\n\n        with disable_coloring(), capture_standard_streams() as (_, err):\n            ic(test2)\n            curr_res = err.getvalue().strip()\n            # expected = r\"ic| test2: 'A\\\\veryvery\\\\long\\\\path\\\\to\\\\no\\\\even\\\\longer\\\\HelloWorld _01_Heritisfinallythe file.file'\"\n            expected = r\"ic| test2: 'A\\\\veryvery\\\\long\\\\path\\\\to\\\\no\\\\even\\\\longer\\\\HelloWorld _01_Heritisfinallythe file.file'\"\n            self.assertEqual(curr_res, expected)\n            del curr_res, expected\n\n        with disable_coloring(), capture_standard_streams() as (_, err):\n            ic(test3)\n            curr_res = err.getvalue().strip()\n            expected = r\"\"\"ic| test3: '''line\n            line'''\"\"\"\n            self.assertEqual(curr_res, expected)\n            del curr_res, expected\n\n    def test_sympy_dict_keys_do_not_crash(self):\n        \"\"\"Regression: ic() must not raise when dict keys are SymPy symbols.\"\"\"\n\n        try:\n            import sympy as sp\n        except Exception:\n            self.skipTest(\"sympy not installed\")\n\n        x, y = sp.symbols(\"x y\")\n        d = {x: \"hello\", y: \"world\"}\n\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            # If the bug regresses, this line raises TypeError.\n            ic(d)\n\n        s = err.getvalue().strip()\n        # Basic sanity checks without assuming exact formatting or ordering.\n        self.assertIn(\"ic|\", s)\n        self.assertIn(\"hello\", s)\n        self.assertIn(\"world\", s)\n\n    def test_sympy_solve_result_does_not_crash(self):\n        \"\"\"Regression: ic() must handle SymPy solve() outputs.\"\"\"\n\n        try:\n            import sympy as sp\n        except Exception:\n            self.skipTest(\"sympy not installed\")\n\n        x, y = sp.symbols(\"x y\")\n        res = sp.solve([x + 2, y - 2])   # list/dict of symbolic items\n\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            ic(res)\n\n        s = err.getvalue()\n        self.assertIn(\"ic|\", s)\n        # Don’t assert exact text; just ensure something printed.\n        self.assertTrue(len(s) > 0)\n"
  },
  {
    "path": "tests/test_install.py",
    "content": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@gmail.com\n#\n# License: MIT\n#\n\nimport unittest\n\nimport icecream\nfrom tests.test_icecream import (\n    disable_coloring, capture_standard_streams, parse_output_into_pairs)\n\nfrom tests.install_test_import import runMe\n\n\nclass TestIceCreamInstall(unittest.TestCase):\n    def test_install(self):\n        icecream.install()\n        with disable_coloring(), capture_standard_streams() as (out, err):\n            runMe()\n        assert parse_output_into_pairs(out, err, 1)[0][0] == ('x', '3')\n        icecream.uninstall()  # Clean up builtins.\n\n    def test_uninstall(self):\n        try:\n            icecream.uninstall()\n        except AttributeError:  # Already uninstalled.\n            pass\n\n        # NameError: global name 'ic' is not defined.\n        with self.assertRaises(NameError):\n            runMe()\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = py39, py310, py311, py312, py313, py314, pypy3\n\n[testenv]\ndescription =\n    run unittest\ncommands =\n    python -m unittest\ndeps =\n    sympy>=1.12\n\n[testenv:mypy]\nbasepython = python3.9\ndeps =\n   mypy==1.7.1\n   types-pygments\n   types-colorama\ncommands =\n  mypy icecream\n"
  }
]