Repository: Nekmo/gkeep
Branch: develop
Commit: d786fd6401be
Files: 34
Total size: 65.0 KB
Directory structure:
gitextract_wq3pe4ix/
├── .bumpversion.cfg
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ └── workflows/
│ ├── pip-rating.yml
│ └── publish.yml
├── .gitignore
├── AUTHORS.rst
├── CONTRIBUTING.rst
├── HISTORY.rst
├── MANIFEST.in
├── Makefile
├── README.rst
├── docs/
│ ├── Makefile
│ ├── authors.rst
│ ├── conf.py
│ ├── contributing.rst
│ ├── history.rst
│ ├── index.rst
│ ├── installation.rst
│ ├── readme.rst
│ └── usage.rst
├── google_keep_tasks/
│ ├── __init__.py
│ ├── _compat.py
│ ├── auth.py
│ ├── cli.py
│ ├── exceptions.py
│ ├── items.py
│ ├── labels.py
│ ├── management.py
│ ├── notes.py
│ └── utils.py
├── requirements-dev.txt
├── requirements.txt
├── scripts/
│ └── gkeep
└── setup.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .bumpversion.cfg
================================================
[bumpversion]
current_version = 1.0.1
commit = True
tag = True
[bumpversion:file:google_keep_tasks/__init__.py]
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
* django-code-generator version:
* Python version:
* Operating System:
### Description
Describe what you were trying to get done.
Tell us what happened, what went wrong, and what you expected to happen.
### What I Did
```
Paste the command(s) you ran and the output.
If there was a crash, please include the traceback here.
```
================================================
FILE: .github/workflows/pip-rating.yml
================================================
name: Pip-rating
on:
push:
branches:
- develop
schedule:
- cron: '0 0 * * SUN'
jobs:
build:
runs-on: ubuntu-latest
permissions: write-all
steps:
- uses: actions/checkout@v2
- name: Run pip-rating
uses: Nekmo/pip-rating@master
with:
create_badge: true
badge_style: flat-square
badge_branch: pip-rating-badge
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox-gh-actions wheel twine
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
- name: Create packages
run: |
python setup.py sdist bdist_wheel
- name: Check packages
run: |
twine check dist/*
- name: Publish package
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
================================================
FILE: .gitignore
================================================
/.idea
/auth.txt
__pycache__
/dist
/*.egg-info
================================================
FILE: AUTHORS.rst
================================================
=======
Credits
=======
Development Lead
----------------
* Nekmo <contacto@nekmo.com>
Contributors
------------
None yet. Why not be the first?
================================================
FILE: CONTRIBUTING.rst
================================================
.. highlight:: shell
============
Contributing
============
Contributions are welcome, and they are greatly appreciated! Every
little bit helps, and credit will always be given.
You can contribute in many ways:
Types of Contributions
----------------------
Report Bugs
~~~~~~~~~~~
Report bugs at https://github.com/Nekmo/gkeep/issues.
If you are reporting a bug, please include:
* Your operating system name and version.
* Any details about your local setup that might be helpful in troubleshooting.
* Detailed steps to reproduce the bug.
Fix Bugs
~~~~~~~~
Look through the GitHub issues for bugs. Anything tagged with "bug"
and "help wanted" is open to whoever wants to implement it.
Implement Features
~~~~~~~~~~~~~~~~~~
Look through the GitHub issues for features. Anything tagged with "enhancement"
and "help wanted" is open to whoever wants to implement it.
Write Documentation
~~~~~~~~~~~~~~~~~~~
gkeep could always use more documentation, whether as part of the
official gkeep docs, in docstrings, or even on the web in blog posts,
articles, and such.
Submit Feedback
~~~~~~~~~~~~~~~
The best way to send feedback is to file an issue at https://github.com/Nekmo/gkeep/issues.
If you are proposing a feature:
* Explain in detail how it would work.
* Keep the scope as narrow as possible, to make it easier to implement.
* Remember that this is a volunteer-driven project, and that contributions
are welcome :)
Get Started!
------------
Ready to contribute? Here's how to set up `gkeep` for local development.
1. Fork the `gkeep` repo on GitHub.
2. Clone your fork locally::
$ git clone git@github.com:your_name_here/gkeep.git
3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development::
$ mkvirtualenv gkeep
$ cd gkeep/
$ python setup.py develop
4. Create a branch for local development::
$ git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox::
$ flake8 gkeep tests
$ python setup.py test or py.test
$ tox
To get flake8 and tox, just pip install them into your virtualenv.
6. Commit your changes and push your branch to GitHub::
$ git add .
$ git commit -m "Your detailed description of your changes."
$ git push origin name-of-your-bugfix-or-feature
7. Submit a pull request through the GitHub website.
Pull Request Guidelines
-----------------------
Before you submit a pull request, check that it meets these guidelines:
1. The pull request should include tests.
2. If the pull request adds functionality, the docs should be updated. Put
your new functionality into a function with a docstring, and add the
feature to the list in README.rst.
3. The pull request should work for Python 2.6, 2.7, 3.3, 3.4 and 3.5, and for PyPy. Check
https://travis-ci.org/Nekmo/gkeep/pull_requests
and make sure that the tests pass for all supported Python versions.
Tips
----
To run a subset of tests::
$ python -m unittest tests.test_gkeep
================================================
FILE: HISTORY.rst
================================================
=======
History
=======
1.0.0 (2020-10-23)
------------------
* Issue #32: Use entrypoints in setup
* Issue #31: AttributeError: 'ColorValue' object has no attribute 'title'
* Issue #7: Archived & Pinned options
* Issue #6: Labels commands
* Issue #27: Remove auth argument
* Issue #25: Authentication cli assistant
* Issue #28: Create docs
* Issue #30: Refactor notes query params
0.2.1 (2020-05-09)
------------------
* Issue #26: Do not use the pip's internal api as it isn't working anymore
0.2.0 (2020-04-25)
------------------
* Issue #23: Create changelog
* Issue #22: Upload to Pypi using Travis CI
* Issue #21: Document note commands
* Issue #5: Notes commands
0.1.1 (2018-05-07)
------------------
* Issue #9: How to install gkeep ( gkeep --auth ~/.gkeepauth FAILS)
0.1.1 (2018-04-16)
------------------
* Pip 10.0 support
0.1.0 (2018-01-02)
------------------
* Initial release (item commands).
================================================
FILE: MANIFEST.in
================================================
include common-requirements.txt
include py2-requirements.txt
include py3-requirements.txt
include README.rst
================================================
FILE: Makefile
================================================
.PHONY: clean clean-test clean-pyc clean-build docs help
.DEFAULT_GOAL := help
define BROWSER_PYSCRIPT
import os, webbrowser, sys
try:
from urllib import pathname2url
except:
from urllib.request import pathname2url
webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
endef
export BROWSER_PYSCRIPT
define PRINT_HELP_PYSCRIPT
import re, sys
for line in sys.stdin:
match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
if match:
target, help = match.groups()
print("%-20s %s" % (target, help))
endef
export PRINT_HELP_PYSCRIPT
BROWSER := python -c "$$BROWSER_PYSCRIPT"
help:
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
clean-build: ## remove build artifacts
rm -fr build/
rm -fr dist/
rm -fr .eggs/
find . -name '*.egg-info' -exec rm -fr {} +
find . -name '*.egg' -exec rm -f {} +
clean-pyc: ## remove Python file artifacts
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
find . -name '__pycache__' -exec rm -fr {} +
clean-test: ## remove test and coverage artifacts
rm -fr .tox/
rm -f .coverage
rm -fr htmlcov/
lint: ## check style with flake8
flake8 business_theme tests
test: ## run tests quickly with the default Python
python setup.py test
test-all: ## run tests on every Python version with tox
tox
coverage: ## check code coverage quickly with the default Python
coverage run --source business_theme setup.py test
coverage report -m
coverage html
$(BROWSER) htmlcov/index.html
docs: ## generate Sphinx HTML documentation, including API docs
cd docs/; make pdf
servedocs: docs ## compile the docs watching for changes
watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D .
release: clean ## package and upload a release
python setup.py sdist upload
python setup.py bdist_wheel upload
dist: build-sdist build-wheel
ls -l dist
install: clean ## install the package to the active Python's site-packages
python setup.py install
build-sdist:
python setup.py sdist
@echo
@echo "Build tar.gz package finished."
build-wheel:
python setup.py bdist_wheel
@echo
@echo "Build wheel package"
================================================
FILE: README.rst
================================================
.. image:: https://raw.githubusercontent.com/Nekmo/gkeep/master/logo.jpg
:width: 100%
|
.. image:: https://raw.githubusercontent.com/Nekmo/gkeep/pip-rating-badge/pip-rating-badge.svg
:target: https://github.com/Nekmo/gkeep/actions/workflows/pip-rating.yml
:alt: pip-rating badge
.. image:: https://img.shields.io/pypi/v/gkeep.svg?style=flat-square
:target: https://pypi.org/project/gkeep/
:alt: Latest PyPI version
.. image:: https://img.shields.io/pypi/pyversions/gkeep.svg?style=flat-square
:target: https://pypi.org/project/gkeep/
:alt: Python versions
.. image:: https://img.shields.io/github/stars/Nekmo/gkeep?style=flat-square
:target: https://github.com/Nekmo/gkeep
:alt: Github stars
**DEVELOPMENT BRANCH**: The current branch is a development version. Go to the stable release by clicking
on `the master branch <https://github.com/Nekmo/gkeep/tree/master>`_.
Google Keep Cli
###############
Work with Google Keep on your terminal. To install this module
(`more options in the documentation <https://docs.nekmo.org/gkeep/installation.html>`_)::
$ pip install -U gkeep
To get the available options use the ``--help`` parameter or
`see the documentation <https://docs.nekmo.org/gkeep/usage.html>`_::
$ gkeep --help
For example **to search for notes**::
$ gkeep notes search "Shopping list"
Gkeep allows you to use Google Keep **in your scripts**. For example to remember to buy milk::
$ gkeep items edit --uncheck 150ad84b557.97eb8e3bffcb03e1 "Milk"
Thanks
======
This module is a command-line interface of the module `gkeepapi <https://github.com/kiwiz/gkeepapi/>`_.
Thanks to Kiwiz for maintaining the module.
This module does not use an official Google API to work with Google Keep. As this module does not use an official
API, its operation is not guaranteed for a production environment.
================================================
FILE: docs/Makefile
================================================
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXAPIDOC = sphinx-apidoc
PAPER =
BUILDDIR = _build
PROJECT_NAME = Google Keep CLI
DRIVE_FOLDER =
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " pdf to make standalone PDF files"
@echo " html to make standalone HTML files"
@echo " watch Browser Sync watcher for build HTML files on real time"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
rinohpdf:
$(SPHINXAPIDOC) -o . ../
$(SPHINXBUILD) -b rinoh . $(BUILDDIR)/pdf
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
pdf:
HTML_THEME=business_theme make singlehtml
weasyprint _build/singlehtml/index.html "$(PROJECT_NAME).pdf"
rm -rf _build
python -m business_theme upload "$(PROJECT_NAME).pdf" "$(PROJECT_NAME).pdf" "$(DRIVE_FOLDER)"
@echo
@echo "Build finished. The PDF file is in $(BUILDDIR)/."
html:
rm -rf $(BUILDDIR)
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXAPIDOC) -o . ../
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/delfos.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/delfos.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/delfos"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/delfos"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
================================================
FILE: docs/authors.rst
================================================
.. include:: ../AUTHORS.rst
================================================
FILE: docs/conf.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Google Keep CLI documentation build configuration file, created by
# sphinx-quickstart on Tue Jul 9 22:26:36 2013.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import datetime
import sys
import os
# import django
# If extensions (or modules to document with autodoc) are in another
# directory, add these directories to sys.path here. If the directory is
# relative to the documentation root, use os.path.abspath to make it
# absolute, like shown here.
# Insert the project root dir as the first element in the PYTHONPATH.
# This lets us ensure that the source package is imported, and that its
# version is used.
directory = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.abspath(os.path.join(directory, '../')))
# sys.path.append(os.path.abspath(os.path.join(directory, '../')))
# os.environ['DJANGO_SETTINGS_MODULE'] = 'Google Keep CLI.settings.develop'
# django.setup()
# -- General configuration ---------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx_click.ext'
# 'sphinxcontrib.autohttp.drf',
# 'sphinxcontrib_django',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Google Keep CLI'
copyright = u"%i, Nekmo" % datetime.date.today().year
pdf_documents = [('index', u'rst2pdf', u'Google Keep CLI', u'Nekmo'), ]
rinoh_documents = [('index', # top-level file (index.rst)
'target', # output (target.pdf)
'Google Keep CLI', # document title
'Nekmo')] # document author
# rinoh_logo = '_static/logo.png'
rinoh_domain_indices = False
html_context = dict(docs_scope='external')
# The version info for the project you're documenting, acts as replacement
# for |version| and |release|, also used in various other places throughout
# the built documents.
#
# The short X.Y version.
version = '0.1.0'
# The full version, including alpha/beta/rc tags.
release = '0.1.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to
# some non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
# pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built
# documents.
#keep_warnings = False
# -- Options for HTML output -------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = os.environ.get('HTML_THEME', 'alabaster')
# Theme options are theme-specific and customize the look and feel of a
# theme further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {
'logo': 'logo.png',
'description': 'Google Keep Command Line Interface (CLI)',
'github_user': 'Nekmo',
'github_repo': 'gkeep',
'github_type': 'star',
'github_banner': True,
'travis_button': True,
'codecov_button': True,
'analytics_id': 'UA-62276079-1',
'canonical_url': 'http://docs.nekmo.org/gkeep/'
}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['.']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as
# html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the
# top of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon
# of the docs. This file should be a Windows icon file (.ico) being
# 16x16 or 32x32 pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets)
# here, relative to this directory. They are copied after the builtin
# static files, so a file named "default.css" will overwrite the builtin
# "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {
# '**': [
# 'about.html',
# 'navigation.html',
# 'relations.html',
# 'searchbox.html',
# 'donate.html',
# ]
# }
# Additional templates that should be rendered to pages, maps page names
# to template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer.
# Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer.
# Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages
# will contain a <link> tag referring to it. The value of this option
# must be the base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'Google Keep CLIdoc'
# -- Options for LaTeX output ------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index', 'Google Keep CLI.tex',
u'Google Keep CLI Documentation',
u'Nekmo', 'manual'),
]
# The name of an image file (relative to this directory) to place at
# the top of the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings
# are parts, not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output ------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'Google Keep CLI',
u'Google Keep CLI Documentation',
[u'Nekmo'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ----------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'Google Keep CLI',
u'Google Keep CLI Documentation',
u'Nekmo',
'Google Keep CLI',
'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
def setup(app):
# app.add_stylesheet('custom.css')
pass
================================================
FILE: docs/contributing.rst
================================================
.. include:: ../CONTRIBUTING.rst
================================================
FILE: docs/history.rst
================================================
.. include:: ../HISTORY.rst
================================================
FILE: docs/index.rst
================================================
Welcome to gkeep's documentation!
=================================
Google Keep Command Line Interface (CLI). Create and update notes from the command line. Use this program to automate
the creation of notes.
To **install** gkeep, run this command in your terminal:
.. code-block:: console
$ pip install -U gkeep
Contents
--------
.. toctree::
:maxdepth: 2
:glob:
installation
readme
usage
contributing
authors
history
..
_ modules
================================================
FILE: docs/installation.rst
================================================
.. highlight:: console
============
Installation
============
Stable release
--------------
To install gkeep, run these commands in your terminal:
.. code-block:: console
$ sudo pip3 install -U gkeep
This is the preferred method to install gkeep, as it will always install the most recent stable release.
If you don't have `pip`_ installed, this `Python installation guide`_ can guide
you through the process.
.. _pip: https://pip.pypa.io
.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/
Other releases
--------------
You can install other versions from Pypi using::
$ pip install gkeep==<version>
For versions that are not in Pypi (it is a development version)::
$ pip install git+https://github.com/Nekmo/gkeep.git@<branch>#egg=gkeep
If you do not have git installed::
$ pip install https://github.com/Nekmo/gkeep/archive/<branch>.zip
Troubleshooting
---------------
Gkeep is unstable because **it doesn't use an official Google api**. Before opening a new incident update gkeep and its
dependencies to the latest version::
$ pip install -U gkeep --upgrade-strategy eager
In case of problems with **authentication** check the credentials file in ``~/.config/gkeep/auth.json``. It is
recommended to protect this file for security. Accounts with two-step authentication must use an application password.
For more info: https://support.google.com/mail/answer/185833
================================================
FILE: docs/readme.rst
================================================
.. include:: ../README.rst
================================================
FILE: docs/usage.rst
================================================
Usage
#####
.. click:: google_keep_tasks.management:notes
:prog: gkeep notes
:show-nested:
.. click:: google_keep_tasks.management:items
:prog: gkeep items
:show-nested:
.. click:: google_keep_tasks.management:labels
:prog: gkeep labels
:show-nested:
================================================
FILE: google_keep_tasks/__init__.py
================================================
__version__ = '1.0.1'
================================================
FILE: google_keep_tasks/_compat.py
================================================
try:
from json import JSONDecodeError
except ImportError:
JSONDecodeError = ValueError
================================================
FILE: google_keep_tasks/auth.py
================================================
import json
import os
import click
import gkeepapi
from click import Abort
from gkeepapi.exception import LoginException
from google_keep_tasks.cli import choices_prompt
from google_keep_tasks.exceptions import UnavailableLoginError, LoginError
from google_keep_tasks._compat import JSONDecodeError
AUTH_FILE = os.path.expanduser('~/.config/gkeep/auth.json')
def get_auth(file='auth.txt'):
return [val.rstrip('\n\r') for val in open(file).read().split(' ')]
class GoogleKeepFileAuth(object):
def __init__(self, file=None):
self.file = file or AUTH_FILE
def save_credentials(self, username, password):
data = {'username': username, 'password': password}
directory = os.path.dirname(AUTH_FILE)
if not os.path.exists(directory):
os.makedirs(directory, 0o700)
json.dump(data, open(self.file, 'w'))
def get_credentials(self):
if not os.path.lexists(self.file):
raise UnavailableLoginError('Credential files "{}" does not exists'.format(self.file))
try:
data = json.load(open(self.file))
except JSONDecodeError as e:
raise LoginError('Invalid json from credentials file: {}. Error: {}'.format(
self.file, e
))
if not isinstance(data, dict) or 'username' not in data or 'password' not in data:
raise LoginError('Invalid credentials format file. username and password are required.')
return data['username'], data['password']
class GoogleKeep(object):
def __init__(self):
self.keep = gkeepapi.Keep()
self.auth = GoogleKeepFileAuth()
def login_or_input(self):
auth_changed = False
input_login = False
username = password = None
try:
username, password = self.auth.get_credentials()
except UnavailableLoginError:
click.echo('Welcome to Google Keep. Enter your username and password below. '
'If your account is protected, you need an application password: '
'https://support.google.com/mail/answer/185833')
input_login = True
except LoginError as e:
click.echo('The credentials file is corrupt: {}. Credentials must be re-entered.'.format(e))
input_login = True
while True:
if input_login:
# Request new credentials
auth_changed = True
username, password = self.get_credencials_assistant(username)
try:
self.keep.login(username, password)
except LoginException:
choice = choices_prompt('Authentication failed, what do you want to do?', [
'Enter new credentials',
'retry',
'abort',
], 'e')
if choice == 'e':
input_login = True
elif choice == 'r':
input_login = False
elif choice == 'a':
raise Abort
else:
if auth_changed:
self.auth.save_credentials(username, password)
break
def get_credencials_assistant(self, default_username):
username = click.prompt('Enter your Google username', type=str,
show_default=True, default=default_username)
password = click.prompt('Enter your password', hide_input=True)
return username, password
================================================
FILE: google_keep_tasks/cli.py
================================================
import click
def choices_prompt(text, choices, default_choice):
choices_descriptions = [' [{}]{}'.format(choice[0].upper() if default_choice == choice[0] else choice[0],
choice[1:])
for choice in choices]
choices_letters = [choice[0].upper() if default_choice == choice[0] else choice[0] for choice in choices]
choice = click.prompt(
'{}\n\n'.format(text) +
'\n'.join(choices_descriptions) +
'\nEnter a choice [{}]'.format('/'.join(choices_letters)),
default=default_choice, show_default=False
)
if not next(iter(filter(lambda x: x == choice.lower(), map(lambda x: x.lower(), choices_letters))), None):
return default_choice
return choice.lower()
class GkeepHelpFormatter(click.HelpFormatter):
def write(self, string):
"""Remove rst characters."""
return super().write(string.replace('``', ''))
class GkeepContext(click.Context):
def make_formatter(self):
return GkeepHelpFormatter(width=self.terminal_width,
max_width=self.max_content_width)
class GkeepGroup(click.Group):
def make_context(self, info_name, args, parent=None, **extra):
for key, value in self.context_settings.items():
if key not in extra:
extra[key] = value
ctx = GkeepContext(self, info_name=info_name, parent=parent, **extra)
with ctx.scope(cleanup=False):
self.parse_args(ctx, args)
return ctx
================================================
FILE: google_keep_tasks/exceptions.py
================================================
import sys
class GKeepError(Exception):
body = ''
def __init__(self, extra_body=''):
self.extra_body = extra_body
def __str__(self):
msg = self.__class__.__name__
if self.body:
msg += ': {}'.format(self.body)
if self.extra_body:
msg += ('. {}' if self.body else ': {}').format(self.extra_body)
return msg
class ItemNotFound(GKeepError):
def __init__(self, text):
super(ItemNotFound, self).__init__('Item text not found: {}'.format(text))
class LoginError(GKeepError):
body = 'Check credentials file. The syntax is: <username> <password>.'
class UnavailableLoginError(GKeepError):
body = 'Unavailable credentials'
class InvalidColor(GKeepError):
def __init__(self, invalid_color):
import gkeepapi
colors = [color.name for color in gkeepapi.node.ColorValue]
super(InvalidColor, self).__init__('Invalid color: {}. Available colors: {}'.format(
invalid_color, ', '.join(colors)
))
def catch(fn):
def wrap(*args, **kwargs):
try:
fn(*args, **kwargs)
except GKeepError as e:
sys.stderr.write('[Error] GKeep Exception:\n{}\n'.format(e))
return wrap
================================================
FILE: google_keep_tasks/items.py
================================================
import click
from google_keep_tasks.cli import GkeepGroup
from google_keep_tasks.exceptions import ItemNotFound
# from google_keep_tasks.management import cli
def search_item(items, text):
for item in items:
if item.text == text:
return item
raise ItemNotFound(text)
@click.group(cls=GkeepGroup)
def items():
"""Use ``items`` command to work with the note checkboxes. This command has
subcommands for adding, editing, deleting or check/uncheck items. To see all
subcommands of ``items`` use ``--help``::
gkeep items --help
An example of a subcommand is ``add``. To see help use
``gkeep items add --help``. In all ``items`` subcommands, note ``id`` argument is
mandatory. To get note ``id`` use ``gkeep notes search`` or ``gkeep notes get``.
"""
@items.command('add', options_metavar='[options]')
@click.option('--check/--uncheck', default=None, help='Item is checked or not')
@click.option('--duplicate/--no-duplicate', default=False,
help='By default if the element already exists, it is not duplicated. '
'If you want duplicate the element, use the this parameter')
@click.argument('id', metavar='<id>')
@click.argument('text', metavar='<text>')
@click.pass_context
def add_item(ctx, check, duplicate, id, text):
"""Add a item to an existing note. By default if the element already exists,
it is not duplicated. To duplicate the element use ``--duplicate`` param. By
default the item is created unchecked.
.. code-block:: shell
gkeep items add 75e4202b0c1.9fc0b868a7b34952 "Chip cookies" --check
The syntax is:
"""
keep = ctx.obj['keep']
gnote = keep.get(id)
try:
item = search_item(gnote.items, text)
check = item.checked if check is None else check
except ItemNotFound:
item = None
check = False if check is None else check
if item and not duplicate:
item.checked = check
else:
gnote.add(text, check)
keep.sync()
@items.command('edit', options_metavar='[options]')
@click.option('--check/--uncheck', default=None, help='Item is checked or not')
@click.option('--new-text', default='New element text', metavar='<text>')
@click.argument('id', metavar='<id>')
@click.argument('text', metavar='<text>')
@click.pass_context
def edit_item(ctx, check, new_text, id, text):
"""Edit an existing item. Use this command to change the text or
check or uncheck the item. For example:
.. code-block:: shell
gkeep items edit 75e4202b0c1.9fc0b868a7b34952 "Chip cookies" --uncheck
Another example:
.. code-block:: shell
gkeep items edit 75e4202b0c1.9fc0b868a7b34952 "Chip cookies"
--new-text "Chocolate orange cookies"
The syntax is:
"""
keep = ctx.obj['keep']
gnote = keep.get(id)
item = search_item(gnote.items, text)
item.text = new_text or item.text
item.checked = item.checked if check is None else check
keep.sync()
@items.command('delete', options_metavar='[options]')
@click.argument('id', metavar='<id>')
@click.argument('text', metavar='<text>')
@click.pass_context
def delete_item(ctx, id, text):
"""Delete a item to an existing note.
.. code-block:: shell
gkeep items delete 75e4202b0c1.9fc0b868a7b34952 "Chip cookies"
The syntax is:
"""
keep = ctx.obj['keep']
gnote = keep.get(id)
item = search_item(gnote.items, text)
item.delete()
keep.sync()
@items.command('is-checked', options_metavar='[options]')
@click.argument('id', metavar='<id>')
@click.argument('text', metavar='<text>')
@click.pass_context
def delete_item(ctx, id, text):
"""Returns ``True`` if the item is checked and ``False`` if it is unchecked.
.. code-block:: shell
gkeep items is-checked 75e4202b0c1.9fc0b868a7b34952 "Chip cookies"
The syntax is:
"""
keep = ctx.obj['keep']
gnote = keep.get(id)
item = search_item(gnote.items, text)
print(item.checked)
keep.sync()
================================================
FILE: google_keep_tasks/labels.py
================================================
import sys
import click
from gkeepapi.exception import LabelException
from google_keep_tasks.cli import GkeepGroup
from google_keep_tasks.utils import pretty_date
def format_label(label):
return u'━ {}'.format(label)
def format_label_with_timestaps(label):
return u'{} (created {}, updated {})'.format(
format_label(label),
pretty_date(label.timestamps.created),
pretty_date(label.timestamps.updated),
)
@click.group(cls=GkeepGroup)
def labels():
"""List, create, rename or delete labels using ``labels`` command.
This command has subcommands for adding, searching, editing, or
deleting labels. To see all subcommands of ``labels`` use ``--help``::
gkeep labels --help
An example of a subcommand is ``add``. To see help use
``gkeep labels add --help``.
"""
pass
@labels.command('list', options_metavar='[options]')
@click.option('--timestamps', is_flag=True, help='Include timestaps per each label.')
@click.pass_context
def list_labels(ctx, timestamps):
"""List labels on Google Keep. For example:
.. code-block:: shell
gkeep labels list
The syntax is:
"""
keep = ctx.obj['keep']
fmt = format_label_with_timestaps if timestamps else format_label
click.echo(u'\n'.join([
fmt(label) for label in keep.labels()]
))
@labels.command('add', options_metavar='[options]')
@click.argument('title', metavar='<title>')
@click.pass_context
def add_label(ctx, title):
"""Create a label on Google Keep. For example:
.. code-block:: shell
gkeep labels create "Label name"
The syntax is:
"""
keep = ctx.obj['keep']
try:
keep.createLabel(title)
except LabelException as e:
click.echo(u'Error creating label: {}'.format(e), err=True)
sys.exit(3)
keep.sync()
click.echo(f'Created label {title}')
@labels.command('edit', options_metavar='[options]')
@click.argument('old_title', metavar='<old_title>')
@click.argument('title', metavar='<new_title>')
@click.pass_context
def edit_label(ctx, old_title, title):
"""Rename a label title. For example:
.. code-block:: shell
gkeep labels edit "Old title" "New title"
The syntax is:
"""
keep = ctx.obj['keep']
label = keep.findLabel(old_title)
if not label:
click.echo(u'The label was not found', err=True)
sys.exit(2)
new_label = keep.findLabel(title)
if new_label:
click.echo(u'The label {} already exists'.format(title), err=True)
sys.exit(2)
label.name = title
keep.sync()
click.echo(f'Renamed label {old_title} to {title}')
@labels.command('delete', options_metavar='[options]')
@click.argument('title', metavar='<title>')
@click.pass_context
def edit_label(ctx, title):
"""Delete a label. For example:
.. code-block:: shell
gkeep labels delete "Label name"
The syntax is:
"""
keep = ctx.obj['keep']
label = keep.findLabel(title)
if label and (label.deleted or label.trashed):
click.echo(u'The label "{}" had already been deleted.'.format(title), err=True)
sys.exit(2)
elif label:
keep.deleteLabel(label)
keep.sync()
click.echo('Label "{}" deleted.'.format(title))
else:
click.echo('The label was not found', err=True)
sys.exit(2)
================================================
FILE: google_keep_tasks/management.py
================================================
import sys
import click
from google_keep_tasks.auth import GoogleKeep
from google_keep_tasks.cli import GkeepGroup
from google_keep_tasks.items import items
from google_keep_tasks.labels import labels
from google_keep_tasks.notes import notes
@click.group(cls=GkeepGroup)
@click.option('--debug/--no-debug', default=None)
@click.pass_context
def cli(ctx, debug):
google_keep = GoogleKeep()
if sys.argv[-1] not in ctx.help_option_names:
google_keep.login_or_input()
ctx.obj = {'keep': google_keep.keep}
cli.add_command(items)
cli.add_command(notes)
cli.add_command(labels)
================================================
FILE: google_keep_tasks/notes.py
================================================
# -*- coding: utf-8 -*-
import click
import gkeepapi
import sys
from google_keep_tasks.cli import GkeepGroup
from google_keep_tasks.exceptions import InvalidColor
COLORS = {
gkeepapi.node.ColorValue.Gray: {'bg': 'black', 'fg': 'white'},
gkeepapi.node.ColorValue.Red: {'bg': 'red', 'fg': 'white'},
gkeepapi.node.ColorValue.Green: {'bg': 'green'},
gkeepapi.node.ColorValue.Yellow: {'bg': 'green', 'fg': 'black'},
gkeepapi.node.ColorValue.Blue: {'bg': 'cyan', 'fg': 'white'},
gkeepapi.node.ColorValue.DarkBlue: {'bg': 'blue', 'fg': 'white'},
gkeepapi.node.ColorValue.Purple: {'bg': 'magenta', 'fg': 'white'},
gkeepapi.node.ColorValue.White: {'bg': 'white', 'fg': 'black'},
}
COLOR_NAMES = [color.name.lower() for color in gkeepapi.node.ColorValue]
def get_color(color):
if not color:
return
if isinstance(color, gkeepapi.node.ColorValue):
return color
color = color.title()
if color and not hasattr(gkeepapi.node.ColorValue, color):
raise InvalidColor(color)
return getattr(gkeepapi.node.ColorValue, color)
def get_click_color(ctx, param, value):
return get_color(value)
def find_or_create_label(keep, label_name):
label = keep.findLabel(label_name)
if not label:
label = keep.createLabel(label_name)
return label
def add_labels(keep, note, labels):
if not labels:
return
for label in labels:
note.labels.add(find_or_create_label(keep, label))
def get_labels(keep, labels):
return list(filter(bool, map(keep.findLabel, labels)))
def comma_separated(ctx, param, value):
return value.split(',') if value else []
def query_params(keep, **kwargs):
kwargs['colors'] = (list(filter(bool, [get_color(kwargs.pop('color'))])) if 'color' in kwargs else []) or None
labels = get_labels(keep, kwargs.pop('labels', []))
deleted = kwargs.pop('deleted', None)
title = kwargs.pop('title', None)
text = kwargs.pop('text', None)
if any(filter(lambda x: x is not None, [deleted, title, text])) or labels:
kwargs['func'] = lambda x: all(filter(lambda y: isinstance(y, bool),
[x.deleted == deleted if deleted is not None else None,
x.title == title if title is not None else None,
x.text == text if text is not None else None,
set(x.labels.all()) == set(labels) if labels is not None else None]))
return kwargs
def print_note(note):
params = COLORS.get(note.color, {})
note_id = (u'⚲ ' if note.pinned else '') + '(note id {})'.format(note.id)
note_id += u' 🗑' if note.deleted or note.trashed else ''
click.echo(click.style(note_id, **params))
click.echo(click.style('"' * len(note_id), **params))
if note.title:
click.echo(click.style(note.title, bold=True))
click.echo(note.text)
if note.labels:
click.echo()
click.echo(' '.join(click.style('[{}]'.format(label.name), underline=True, bold=True)
for label in note.labels.all()))
click.echo(click.style('"' * len(note_id), **params))
click.echo('\n')
def get_note_instance(keep, id=None, **kwargs):
if id:
note = keep.get(id)
else:
notes = keep.find(**query_params(keep, **kwargs))
note = next(notes, None)
return note
@click.group(cls=GkeepGroup)
def notes():
"""Manage Google Keep notes using ``notes`` command.
This command has subcommands for adding, searching, editing, or
deleting notes. To see all subcommands of ``notes`` use ``--help``::
gkeep notes --help
An example of a subcommand is ``add``. To see help use
``gkeep notes add --help``.
"""
pass
@notes.command('add', options_metavar='[options]')
@click.option('--color', default='', callback=get_click_color, metavar='<color>',
help='Set note color. Choices: {}'.format(', '.join(COLOR_NAMES)))
@click.option('--labels', default='', callback=comma_separated, metavar='<label>',
help='Set note labels. Add multiple labels separated by commas')
@click.argument('title', metavar='<title>')
@click.argument('text', metavar='<note_content>')
@click.pass_context
def add_note(ctx, color, labels, title, text):
"""Add a new note to Google Keep.
A title and a message body are required for the new note. For example:
.. code-block:: shell
gkeep notes add "Today's tasks" "Install gkeep cli and configure it"
The syntax is:
"""
keep = ctx.obj['keep']
gnote = keep.createNote(title, text)
if color:
gnote.color = color
add_labels(keep, gnote, labels)
keep.sync()
@notes.command('search', options_metavar='[options]')
@click.option('--color', default='', callback=get_click_color, metavar='<color>',
help='Filter by note color. Choices: {}'.format(', '.join(COLOR_NAMES)))
@click.option('--labels', default='', callback=comma_separated, metavar='<labels>',
help='Filter by label notes. Filter by multiple labels separated by commas.')
@click.option('--deleted/--not-deleted', default=None,
help='Filter by deleted notes or not')
@click.option('--trashed/--not-trashed', default=None,
help='Filter by deleted notes or not')
@click.option('--pinned/--not-pinned', default=None,
help='Filter by pinned notes or not')
@click.option('--archived/--not-archived', default=None,
help='Filter by archived notes or not')
@click.option('--title', default=None, metavar='<title>',
help='Filter by title note')
@click.option('--text', default=None, metavar='<note_content>',
help='Search in note content')
@click.argument('query', default='', metavar='[query]')
@click.pass_context
def search_notes(ctx, **kwargs):
"""Search for notes using filters or/and use query text. For example:
.. code-block:: shell
gkeep notes search --not-deleted "GKeep installed"
The syntax is:
"""
keep = ctx.obj['keep']
for note in keep.find(**query_params(keep, **kwargs)):
print_note(note)
@notes.command('get', options_metavar='[options]')
@click.argument('id', default=None, required=False, metavar='[id]')
@click.option('--title', default=None,
help='Filter by title note', metavar='<title>')
@click.option('--query', default='', help='Search in any note field', metavar='<term>')
@click.pass_context
def get_note(ctx, **kwargs):
"""Get a note by its id or by its title or text. If the id is unknown,
you can use the ``--title`` and/or ``--text`` filters. For example:
.. code-block:: shell
gkeep notes get 161d1ad8c82.b2ed17d26167c9bc
The syntax is:
"""
keep = ctx.obj['keep']
note = get_note_instance(keep, **kwargs)
if note:
print_note(note)
else:
click.echo('The note was not found', err=True)
sys.exit(2)
@notes.command('edit', options_metavar='[options]')
@click.option('--title', default=None, required=False, metavar='<new title>',
help='Change the note title')
@click.option('--text', default=None, required=False, metavar='<new_note_content>',
help='Change the note text')
@click.option('--filter-id', default=None, required=False, metavar='<id>',
help='Filter by id note. This is the preferred way to ensure editing the correct note')
@click.option('--filter-title', default=None, metavar='<title>',
help='Filter by note title. The titles of the notes are not unique')
@click.option('--filter-query', default='', metavar='<term>',
help='search in titles and body of the notes. This is the least accurate filter')
@click.option('--color', default='', callback=get_click_color, metavar='<color>',
help='Change note color. Choices: {}'.format(', '.join(COLOR_NAMES)))
@click.option('--archived/--not-archived', default=None,
help='Archive or unarchive note.')
@click.option('--pinned/--not-pinned', default=None,
help='Pin or unpin note.')
@click.option('--labels', default='', callback=comma_separated, metavar='<new labels>',
help='Set note labels')
@click.pass_context
def edit_note(ctx, title, text, color, labels, archived, pinned, filter_id, filter_title, filter_query):
"""It is possible to edit an existing note. The following parameters
are available to choose the note to edit. For example:
.. code-block:: shell
gkeep notes edit --filter-title "Today's tasks" --text "GKeep installed, continue reading the docs"
The syntax is:
"""
keep = ctx.obj['keep']
note = get_note_instance(keep, id=filter_id, title=filter_title, query=filter_query)
if not note:
click.echo('The note was not found', err=True)
sys.exit(2)
updated = {}
boolean_nullables = ['archived', 'pinned'] # 3 state params
for param in ['title', 'text', 'color', 'labels'] + boolean_nullables:
value = locals()[param]
if value or (param in boolean_nullables and value is not None):
updated[param] = (getattr(note, param), value)
setattr(note, param, value)
if labels:
add_labels(keep, note, labels)
keep.sync()
click.echo('Updated note fields:\n\n' + ('\n'.join([u'{}: {} 🠞 {}'.format(param, values[0], values[1])
for param, values in updated.items()])))
@notes.command('delete', options_metavar='[options]')
@click.argument('id', default=None, required=False, metavar='[id]')
@click.option('--title', default=None, help='Filter by title note', metavar='<title>')
@click.option('--query', default='', help='Search in any note field', metavar='<term>')
@click.pass_context
def delete_note(ctx, **kwargs):
"""It works just like get-note. Delete a note by its id or by its title
or text. If the id is unknown, you can use the --title and/or --text filters.
For example:
.. code-block:: shell
gkeep notes delete 161d1ad8c82.b2ed17d26167c9bc
The syntax is:
"""
keep = ctx.obj['keep']
note = get_note_instance(keep, **kwargs)
if note and (note.deleted or note.trashed):
click.echo('The note "{}" had already been deleted.'.format(note.title))
elif note:
note.delete()
keep.sync()
click.echo('Note with title "{}" deleted.'.format(note.title))
else:
click.echo('The note was not found', err=True)
sys.exit(2)
================================================
FILE: google_keep_tasks/utils.py
================================================
def pretty_date(time=False):
"""
Get a datetime object or a int() Epoch timestamp and return a
pretty string like 'an hour ago', 'Yesterday', '3 months ago',
'just now', etc
"""
from datetime import datetime
now = datetime.now()
if type(time) is int:
diff = now - datetime.fromtimestamp(time)
elif isinstance(time,datetime):
diff = now - time
else:
diff = now - now
second_diff = diff.seconds
day_diff = diff.days
if day_diff < 0:
return ''
if day_diff == 0:
if second_diff < 10:
return "just now"
if second_diff < 60:
return str(second_diff) + " seconds ago"
if second_diff < 120:
return "a minute ago"
if second_diff < 3600:
return str(second_diff // 60) + " minutes ago"
if second_diff < 7200:
return "an hour ago"
if second_diff < 86400:
return str(second_diff // 3600) + " hours ago"
if day_diff == 1:
return "Yesterday"
if day_diff < 7:
return str(day_diff) + " days ago"
if day_diff < 31:
return str(day_diff // 7) + " weeks ago"
if day_diff < 365:
return str(day_diff // 30) + " months ago"
return str(day_diff // 365) + " years ago"
================================================
FILE: requirements-dev.txt
================================================
-r requirements.txt
bumpversion
sphinx-click
================================================
FILE: requirements.txt
================================================
gkeepapi>=0.10.6
click
================================================
FILE: scripts/gkeep
================================================
#!/usr/bin/env python
from google_keep_tasks.exceptions import catch
from google_keep_tasks.management import cli
if __name__ == '__main__':
catch(cli)()
================================================
FILE: setup.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Google Keep Command Line Interface (CLI)
"""
from setuptools import setup, find_packages
from distutils.version import LooseVersion
from distutils.util import convert_path
from fnmatch import fnmatchcase
import os
import sys
import uuid
import pip
###############################
# Configuración del paquete #
###############################
# Información del autor
AUTHOR = 'Nekmo'
EMAIL = 'contacto@nekmo.com'
# Información del paquete
PACKAGE_NAME = 'gkeep'
PACKAGE_DOWNLOAD_URL = 'https://github.com/Nekmo/gkeep/archive/master.zip' # .tar.gz
URL = 'https://github.com/Nekmo/gkeep'
STATUS_LEVEL = 3 # 1:Planning 2:Pre-Alpha 3:Alpha 4:Beta 5:Production/Stable 6:Mature 7:Inactive
KEYWORDS = ['google', 'keep', 'tasks', 'google-keep'] # Palabras clave
# https://github.com/github/choosealicense.com/tree/gh-pages/_licenses
CLASSIFIERS = [
# Common licenses
'License :: OSI Approved :: MIT License',
# 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
# 'License :: OSI Approved :: BSD License',
] # https://pypi.python.org/pypi?%3Aaction=list_classifiers
NATURAL_LANGUAGE = 'English' # English...
# Requerido para la correcta instalación del paquete
PLATFORMS = [
# 'universal',
'linux',
# 'macosx',
# 'solaris',
# 'irix',
# 'win'
# 'bsd'
# 'ios'
# 'android'
]
ROOT_INCLUDE = [
'requirements.txt',
'VERSION',
'LICENSE.txt'
]
PYTHON_VERSIONS = ['2.7', '3.5-3.10']
######## FIN DE LA CONFIGURACIÓN DEL PAQUTE ########
__author__ = AUTHOR
__dir__ = os.path.abspath(os.path.dirname(__file__))
# paths
readme_path = os.path.join(__dir__, 'README')
if not os.path.exists(readme_path):
readme_path = os.path.join(__dir__, 'README.rst')
version_path = os.path.join(__dir__, 'VERSION')
requirements_path = os.path.join(__dir__, 'requirements.txt')
scripts_path = os.path.join(__dir__, 'scripts')
def get_url(ir):
if hasattr(ir, 'url'): return ir.url
if ir.link is None: return
return ir.link.url
##############################################################################
# find_package_data is an Ian Bicking creation.
# Provided as an attribute, so you can append to these instead
# of replicating them:
standard_exclude = ('*.py', '*.pyc', '*~', '.*', '*.bak', '*.swp*')
standard_exclude_directories = ('.*', 'CVS', '_darcs', './build',
'./dist', 'EGG-INFO', '*.egg-info')
def find_package_data(where='.', package='',
exclude=standard_exclude,
exclude_directories=standard_exclude_directories,
only_in_packages=True,
show_ignored=False):
"""
Return a dictionary suitable for use in ``package_data``
in a distutils ``setup.py`` file.
The dictionary looks like::
{'package': [files]}
Where ``files`` is a list of all the files in that package that
don't match anything in ``exclude``.
If ``only_in_packages`` is true, then top-level directories that
are not packages won't be included (but directories under packages
will).
Directories matching any pattern in ``exclude_directories`` will
be ignored; by default directories with leading ``.``, ``CVS``,
and ``_darcs`` will be ignored.
If ``show_ignored`` is true, then all the files that aren't
included in package data are shown on stderr (for debugging
purposes).
Note patterns use wildcards, or can be exact paths (including
leading ``./``), and all searching is case-insensitive.
This function is by Ian Bicking.
"""
out = {}
stack = [(convert_path(where), '', package, only_in_packages)]
while stack:
where, prefix, package, only_in_packages = stack.pop(0)
for name in os.listdir(where):
fn = os.path.join(where, name)
if os.path.isdir(fn):
bad_name = False
for pattern in exclude_directories:
if (fnmatchcase(name, pattern)
or fn.lower() == pattern.lower()):
bad_name = True
if show_ignored:
sys.stderr.write(
"Directory %s ignored by pattern %s\n"
% (fn, pattern))
break
if bad_name:
continue
if os.path.isfile(os.path.join(fn, '__init__.py')):
if not package:
new_package = name
else:
new_package = package + '.' + name
stack.append((fn, '', new_package, False))
else:
stack.append(
(fn, prefix + name + '/', package, only_in_packages)
)
elif package or not only_in_packages:
# is a file
bad_name = False
for pattern in exclude:
if (fnmatchcase(name, pattern)
or fn.lower() == pattern.lower()):
bad_name = True
if show_ignored:
sys.stderr.write(
"File %s ignored by pattern %s\n"
% (fn, pattern))
break
if bad_name:
continue
out.setdefault(package, []).append(prefix + name)
return out
##############################################################################
# Todos los módulos y submódulos a instalar (module, module.submodule, module.submodule2...)
packages = find_packages(__dir__)
# Prevent include symbolic links
for package in tuple(packages):
path = os.path.join(__dir__, package.replace('.', '/'))
if not os.path.exists(path):
continue
if not os.path.islink(path):
continue
packages.remove(package)
# Otros archivos que no son Python y que son requeridos
package_data = {'': ROOT_INCLUDE}
# Obtener la lista de módulos que se instalarán
modules = list(filter(lambda x: '.' not in x, packages))
for module in modules:
package_data.update(find_package_data(
module,
package=module,
only_in_packages=False,
))
# Descripción larga si existe un archivo README
try:
long_description = open(readme_path, 'rt').read()
except IOError:
long_description = ''
# Tomar por defecto la versión de un archivo VERSION. Si no, del módulo
if os.path.exists(version_path):
package_version = open(version_path).read().replace('\n', '')
else:
package_version = __import__(modules[0]).__version__
# Si hay un directorio scripts, tomar todos sus archivos
if os.path.exists(scripts_path):
scripts_dir_name = scripts_path.replace(__dir__, '', 1)
scripts_dir_name = scripts_dir_name[1:] if scripts_dir_name.startswith(os.sep) else scripts_dir_name
scripts = [os.path.join(scripts_dir_name, file) for file in os.listdir(scripts_path)]
else:
scripts = []
# Eliminar archivos de ROOT_INCLUDE que no existen
for d in tuple(ROOT_INCLUDE):
if not os.path.exists(os.path.join(__dir__, d)):
ROOT_INCLUDE.remove(d)
# Nombre del estado de desarrollo
status_name = ['Planning', 'Pre-Alpha', 'Alpha', 'Beta',
'Production/Stable', 'Mature', 'Inactive'][STATUS_LEVEL - 1]
# Añadir en los classifiers la plataforma
platforms_classifiers = {'linux': ('POSIX', 'Linux'), 'win': ('Microsoft', 'Windows'),
'solaris': ('POSIX', 'SunOS/Solaris'), 'aix': ('POSIX', 'Linux'), 'unix': ('Unix',),
'bsd': ('POSIX', 'BSD')}
for key, parts in platforms_classifiers.items():
if not key in PLATFORMS:
continue
CLASSIFIERS.append('Operating System :: {}'.format(' :: '.join(parts)))
# Añadir la versión de Python a los Classifiers
def frange(x, y, jump):
while x < y:
yield x
x += jump
python_versions = []
for version in PYTHON_VERSIONS:
if '-' in version:
version = version.split('-')
if len(version) != 2:
raise ValueError('Invalid Python version range: {}'.format('-'.join(version)))
version = list(map(float, version))
version[1] += 0.1 # Para que frange incluya la última versión
python_versions.extend(frange(version[0], version[1], 0.1))
elif isinstance(version, int) or version.isdigit():
python_versions.append(str(version))
else:
python_versions.append(float(version))
python_versions = map(lambda x: x if isinstance(x, str) else '%.1f' % x, python_versions)
# Eliminar versiones 0-2.3 y 2.8-2.9
remove_python_versions = map(str, list(frange(0, 2.3, 0.1)) + list(frange(2.8, 3.0, 0.1)))
python_versions = list(filter(lambda x: x not in remove_python_versions, python_versions))
for version in range(2, 4):
if not len(list(filter(lambda x: int(float(x)) != version, python_versions))):
# Sólo se encuentran versiones para la versión <version>
python_versions.append('%i :: Only' % version)
break
CLASSIFIERS.extend(['Programming Language :: Python :: %s' % version for version in python_versions])
CLASSIFIERS.extend([
'Natural Language :: {}'.format(NATURAL_LANGUAGE),
'Development Status :: {} - {}'.format(STATUS_LEVEL, status_name),
])
def read_file(file):
with open(file, 'r') as f:
return f.read()
install_requires = read_file(requirements_path)
setup(
name=PACKAGE_NAME,
version=package_version,
description=__doc__.strip(),
long_description=long_description,
author=AUTHOR,
author_email=EMAIL,
url=URL,
classifiers=CLASSIFIERS,
platforms=PLATFORMS,
provides=modules,
install_requires=install_requires,
packages=packages,
include_package_data=True,
# Scan the input for package information
# to grab any data files (text, images, etc.)
# associated with sub-packages.
package_data=package_data,
download_url=PACKAGE_DOWNLOAD_URL,
keywords=KEYWORDS,
entry_points={'console_scripts':
['gkeep = google_keep_tasks.management:cli']},
zip_safe=False,
)
gitextract_wq3pe4ix/ ├── .bumpversion.cfg ├── .github/ │ ├── ISSUE_TEMPLATE.md │ └── workflows/ │ ├── pip-rating.yml │ └── publish.yml ├── .gitignore ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs/ │ ├── Makefile │ ├── authors.rst │ ├── conf.py │ ├── contributing.rst │ ├── history.rst │ ├── index.rst │ ├── installation.rst │ ├── readme.rst │ └── usage.rst ├── google_keep_tasks/ │ ├── __init__.py │ ├── _compat.py │ ├── auth.py │ ├── cli.py │ ├── exceptions.py │ ├── items.py │ ├── labels.py │ ├── management.py │ ├── notes.py │ └── utils.py ├── requirements-dev.txt ├── requirements.txt ├── scripts/ │ └── gkeep └── setup.py
SYMBOL INDEX (61 symbols across 10 files)
FILE: docs/conf.py
function setup (line 315) | def setup(app):
FILE: google_keep_tasks/auth.py
function get_auth (line 16) | def get_auth(file='auth.txt'):
class GoogleKeepFileAuth (line 20) | class GoogleKeepFileAuth(object):
method __init__ (line 21) | def __init__(self, file=None):
method save_credentials (line 24) | def save_credentials(self, username, password):
method get_credentials (line 31) | def get_credentials(self):
class GoogleKeep (line 45) | class GoogleKeep(object):
method __init__ (line 46) | def __init__(self):
method login_or_input (line 50) | def login_or_input(self):
method get_credencials_assistant (line 88) | def get_credencials_assistant(self, default_username):
FILE: google_keep_tasks/cli.py
function choices_prompt (line 4) | def choices_prompt(text, choices, default_choice):
class GkeepHelpFormatter (line 20) | class GkeepHelpFormatter(click.HelpFormatter):
method write (line 21) | def write(self, string):
class GkeepContext (line 26) | class GkeepContext(click.Context):
method make_formatter (line 27) | def make_formatter(self):
class GkeepGroup (line 32) | class GkeepGroup(click.Group):
method make_context (line 33) | def make_context(self, info_name, args, parent=None, **extra):
FILE: google_keep_tasks/exceptions.py
class GKeepError (line 4) | class GKeepError(Exception):
method __init__ (line 7) | def __init__(self, extra_body=''):
method __str__ (line 10) | def __str__(self):
class ItemNotFound (line 19) | class ItemNotFound(GKeepError):
method __init__ (line 20) | def __init__(self, text):
class LoginError (line 24) | class LoginError(GKeepError):
class UnavailableLoginError (line 28) | class UnavailableLoginError(GKeepError):
class InvalidColor (line 32) | class InvalidColor(GKeepError):
method __init__ (line 33) | def __init__(self, invalid_color):
function catch (line 41) | def catch(fn):
FILE: google_keep_tasks/items.py
function search_item (line 8) | def search_item(items, text):
function items (line 16) | def items():
function add_item (line 37) | def add_item(ctx, check, duplicate, id, text):
function edit_item (line 69) | def edit_item(ctx, check, new_text, id, text):
function delete_item (line 98) | def delete_item(ctx, id, text):
function delete_item (line 118) | def delete_item(ctx, id, text):
FILE: google_keep_tasks/labels.py
function format_label (line 10) | def format_label(label):
function format_label_with_timestaps (line 14) | def format_label_with_timestaps(label):
function labels (line 23) | def labels():
function list_labels (line 39) | def list_labels(ctx, timestamps):
function add_label (line 58) | def add_label(ctx, title):
function edit_label (line 81) | def edit_label(ctx, old_title, title):
function edit_label (line 107) | def edit_label(ctx, title):
FILE: google_keep_tasks/management.py
function cli (line 15) | def cli(ctx, debug):
FILE: google_keep_tasks/notes.py
function get_color (line 23) | def get_color(color):
function get_click_color (line 34) | def get_click_color(ctx, param, value):
function find_or_create_label (line 38) | def find_or_create_label(keep, label_name):
function add_labels (line 45) | def add_labels(keep, note, labels):
function get_labels (line 52) | def get_labels(keep, labels):
function comma_separated (line 56) | def comma_separated(ctx, param, value):
function query_params (line 60) | def query_params(keep, **kwargs):
function print_note (line 75) | def print_note(note):
function get_note_instance (line 92) | def get_note_instance(keep, id=None, **kwargs):
function notes (line 102) | def notes():
function add_note (line 123) | def add_note(ctx, color, labels, title, text):
function search_notes (line 160) | def search_notes(ctx, **kwargs):
function get_note (line 180) | def get_note(ctx, **kwargs):
function edit_note (line 219) | def edit_note(ctx, title, text, color, labels, archived, pinned, filter_...
function delete_note (line 253) | def delete_note(ctx, **kwargs):
FILE: google_keep_tasks/utils.py
function pretty_date (line 3) | def pretty_date(time=False):
FILE: setup.py
function get_url (line 71) | def get_url(ir):
function find_package_data (line 87) | def find_package_data(where='.', package='',
function frange (line 234) | def frange(x, y, jump):
function read_file (line 270) | def read_file(file):
Condensed preview — 34 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (71K chars).
[
{
"path": ".bumpversion.cfg",
"chars": 113,
"preview": "[bumpversion]\ncurrent_version = 1.0.1\ncommit = True\ntag = True\n\n[bumpversion:file:google_keep_tasks/__init__.py]\n"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 332,
"preview": "* django-code-generator version:\n* Python version:\n* Operating System:\n\n### Description\n\nDescribe what you were trying t"
},
{
"path": ".github/workflows/pip-rating.yml",
"chars": 399,
"preview": "name: Pip-rating\n\non:\n push:\n branches:\n - develop\n schedule:\n - cron: '0 0 * * SUN'\n\njobs:\n build:\n ru"
},
{
"path": ".github/workflows/publish.yml",
"chars": 872,
"preview": "name: Publish\n\non: [push]\n\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v2\n "
},
{
"path": ".gitignore",
"chars": 47,
"preview": "/.idea\n/auth.txt\n__pycache__\n/dist\n/*.egg-info\n"
},
{
"path": "AUTHORS.rst",
"chars": 149,
"preview": "=======\nCredits\n=======\n\nDevelopment Lead\n----------------\n\n* Nekmo <contacto@nekmo.com>\n\nContributors\n------------\n\nNon"
},
{
"path": "CONTRIBUTING.rst",
"chars": 3201,
"preview": ".. highlight:: shell\n\n============\nContributing\n============\n\nContributions are welcome, and they are greatly appreciate"
},
{
"path": "HISTORY.rst",
"chars": 921,
"preview": "=======\nHistory\n=======\n\n1.0.0 (2020-10-23)\n------------------\n\n* Issue #32: Use entrypoints in setup\n* Issue #31: Attri"
},
{
"path": "MANIFEST.in",
"chars": 108,
"preview": "include common-requirements.txt\ninclude py2-requirements.txt\ninclude py3-requirements.txt\ninclude README.rst"
},
{
"path": "Makefile",
"chars": 2247,
"preview": ".PHONY: clean clean-test clean-pyc clean-build docs help\n.DEFAULT_GOAL := help\ndefine BROWSER_PYSCRIPT\nimport os, webbro"
},
{
"path": "README.rst",
"chars": 1860,
"preview": ".. image:: https://raw.githubusercontent.com/Nekmo/gkeep/master/logo.jpg\n :width: 100%\n\n|\n\n.. image:: https://raw.git"
},
{
"path": "docs/Makefile",
"chars": 7453,
"preview": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS =\nSPHINXBUILD "
},
{
"path": "docs/authors.rst",
"chars": 28,
"preview": ".. include:: ../AUTHORS.rst\n"
},
{
"path": "docs/conf.py",
"chars": 9673,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# Google Keep CLI documentation build configuration file, created by\n# s"
},
{
"path": "docs/contributing.rst",
"chars": 33,
"preview": ".. include:: ../CONTRIBUTING.rst\n"
},
{
"path": "docs/history.rst",
"chars": 28,
"preview": ".. include:: ../HISTORY.rst\n"
},
{
"path": "docs/index.rst",
"chars": 471,
"preview": "Welcome to gkeep's documentation!\n=================================\nGoogle Keep Command Line Interface (CLI). Create and"
},
{
"path": "docs/installation.rst",
"chars": 1450,
"preview": ".. highlight:: console\n\n============\nInstallation\n============\n\n\nStable release\n--------------\n\nTo install gkeep, run th"
},
{
"path": "docs/readme.rst",
"chars": 27,
"preview": ".. include:: ../README.rst\n"
},
{
"path": "docs/usage.rst",
"chars": 273,
"preview": "\nUsage\n#####\n\n.. click:: google_keep_tasks.management:notes\n :prog: gkeep notes\n :show-nested:\n\n.. click:: google_ke"
},
{
"path": "google_keep_tasks/__init__.py",
"chars": 22,
"preview": "\n__version__ = '1.0.1'"
},
{
"path": "google_keep_tasks/_compat.py",
"chars": 96,
"preview": "\ntry:\n from json import JSONDecodeError\nexcept ImportError:\n JSONDecodeError = ValueError\n"
},
{
"path": "google_keep_tasks/auth.py",
"chars": 3525,
"preview": "import json\nimport os\n\nimport click\nimport gkeepapi\nfrom click import Abort\nfrom gkeepapi.exception import LoginExceptio"
},
{
"path": "google_keep_tasks/cli.py",
"chars": 1554,
"preview": "import click\n\n\ndef choices_prompt(text, choices, default_choice):\n choices_descriptions = [' [{}]{}'.format(choice[0"
},
{
"path": "google_keep_tasks/exceptions.py",
"chars": 1247,
"preview": "import sys\n\n\nclass GKeepError(Exception):\n body = ''\n\n def __init__(self, extra_body=''):\n self.extra_body "
},
{
"path": "google_keep_tasks/items.py",
"chars": 4051,
"preview": "import click\n\nfrom google_keep_tasks.cli import GkeepGroup\nfrom google_keep_tasks.exceptions import ItemNotFound\n# from "
},
{
"path": "google_keep_tasks/labels.py",
"chars": 3367,
"preview": "import sys\n\nimport click\nfrom gkeepapi.exception import LabelException\n\nfrom google_keep_tasks.cli import GkeepGroup\nfro"
},
{
"path": "google_keep_tasks/management.py",
"chars": 598,
"preview": "import sys\n\nimport click\n\nfrom google_keep_tasks.auth import GoogleKeep\nfrom google_keep_tasks.cli import GkeepGroup\nfro"
},
{
"path": "google_keep_tasks/notes.py",
"chars": 10629,
"preview": "# -*- coding: utf-8 -*-\nimport click\nimport gkeepapi\nimport sys\n\nfrom google_keep_tasks.cli import GkeepGroup\nfrom googl"
},
{
"path": "google_keep_tasks/utils.py",
"chars": 1306,
"preview": "\n\ndef pretty_date(time=False):\n \"\"\"\n Get a datetime object or a int() Epoch timestamp and return a\n pretty stri"
},
{
"path": "requirements-dev.txt",
"chars": 45,
"preview": "-r requirements.txt\nbumpversion\nsphinx-click\n"
},
{
"path": "requirements.txt",
"chars": 22,
"preview": "gkeepapi>=0.10.6\nclick"
},
{
"path": "scripts/gkeep",
"chars": 160,
"preview": "#!/usr/bin/env python\nfrom google_keep_tasks.exceptions import catch\nfrom google_keep_tasks.management import cli\n\n\nif _"
},
{
"path": "setup.py",
"chars": 10290,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"Google Keep Command Line Interface (CLI)\n\"\"\"\nfrom setuptools import set"
}
]
About this extraction
This page contains the full source code of the Nekmo/gkeep GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 34 files (65.0 KB), approximately 17.5k tokens, and a symbol index with 61 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.