├── .coveragerc
├── .gitignore
├── .readthedocs.yaml
├── .travis.yml
├── CONTRIBUTING.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── docs
├── Makefile
├── commands
│ ├── identifier.rst
│ ├── load.rst
│ ├── payload.rst
│ ├── print.rst
│ ├── save.rst
│ ├── smartposter.rst
│ ├── text.rst
│ ├── typename.rst
│ └── uri.rst
├── conf.py
├── contributing.rst
├── images
│ ├── ndeftool.ico
│ └── ndeftool.png
├── index.rst
├── license.rst
├── ndeftool.rst
├── requirements.in
└── requirements.txt
├── pytest.ini
├── requirements-dev.txt
├── requirements-pypi.txt
├── setup.cfg
├── setup.py
├── src
└── ndeftool
│ ├── __init__.py
│ ├── cli.py
│ └── commands
│ ├── IDentifier.py
│ ├── Load.py
│ ├── PayLoad.py
│ ├── Print.py
│ ├── SMartPoster.py
│ ├── Save.py
│ ├── TeXT.py
│ ├── TypeName.py
│ ├── URI.py
│ └── __init__.py
├── tests
├── test_identifier.py
├── test_load.py
├── test_main.py
├── test_payload.py
├── test_print.py
├── test_save.py
├── test_smartposter.py
├── test_text.py
├── test_typename.py
└── test_uri.py
└── tox.ini
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | source = ndeftool
4 |
5 | [paths]
6 | source =
7 | src/ndeftool
8 | .tox/*/lib/python*/site-packages/ndeftool
9 | .tox/pypy/site-packages/ndeftool
10 |
11 | [report]
12 | show_missing = True
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.pyc
3 | *.egg-info
4 | .tox
5 | .cache
6 | .coverage*
7 | __pycache__/
8 | docs/_build/
9 | htmlcov
10 | dist
11 | python-2
12 | python-3
13 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | build:
4 | os: ubuntu-22.04
5 | tools:
6 | python: "3.11"
7 |
8 | sphinx:
9 | configuration: docs/conf.py
10 |
11 | python:
12 | install:
13 | - requirements: docs/requirements.txt
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: xenial
2 | sudo: false
3 |
4 | cache:
5 | directories:
6 | - $HOME/.cache/pip
7 |
8 | language: python
9 |
10 |
11 | matrix:
12 | include:
13 | - python: "2.7"
14 | env: TOXENV=py2
15 | - python: "3.5"
16 | env: TOXENV=py3
17 | - python: "3.6"
18 | env: TOXENV=py3
19 | - python: "3.7"
20 | env: TOXENV=py3
21 | - python: "3.8"
22 | env: TOXENV=py3
23 |
24 | # Meta
25 | - python: "3.8"
26 | env: TOXENV=flake8
27 | - python: "3.8"
28 | env: TOXENV=manifest
29 | - python: "3.8"
30 | env: TOXENV=docs
31 | - python: "3.8"
32 | env: TOXENV=readme
33 |
34 |
35 | install:
36 | - pip install tox
37 |
38 |
39 | script:
40 | - tox
41 |
42 |
43 | before_install:
44 | - pip install codecov
45 |
46 |
47 | after_success:
48 | - tox -e coverage-report
49 | - codecov
50 |
51 |
--------------------------------------------------------------------------------
/CONTRIBUTING.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | ============
4 | Contributing
5 | ============
6 |
7 | Thank you for considering contributing to **ndeftool**. There are many
8 | ways to help and any help is welcome.
9 |
10 |
11 | Reporting issues
12 | ================
13 |
14 | - Under which versions of Python does this happen? This is especially
15 | important if your issue is encoding related.
16 |
17 | - Under which versions of **ndeftool** does this happen? Check if this
18 | issue is fixed in the repository.
19 |
20 |
21 | Submitting patches
22 | ==================
23 |
24 | - Include tests if your patch is supposed to solve a bug, and explain
25 | clearly under which circumstances the bug happens. Make sure the
26 | test fails without your patch.
27 |
28 | - Include or update tests and documentation if your patch is supposed
29 | to add a new feature. Note that documentation is in two places, the
30 | code itself for rendering help pages and in the docs folder for the
31 | online documentation.
32 |
33 | - Follow `PEP 8 `_ and
34 | `PEP 257 `_.
35 |
36 |
37 | Development tips
38 | ================
39 |
40 | - `Fork `_ the
41 | repository and clone it locally::
42 |
43 | git clone git@github.com:your-username/ndeftool.git
44 | cd ndeftool
45 |
46 | - Create virtual environments for Python 2 an Python 3, setup the
47 | ndeftool package in develop mode, and install required development
48 | packages::
49 |
50 | virtualenv python-2
51 | python3 -m venv python-3
52 | source python-2/bin/activate
53 | python setup.py develop
54 | pip install -r requirements-dev.txt
55 | source python-3/bin/activate
56 | python setup.py develop
57 | pip install -r requirements-dev.txt
58 |
59 | - Verify that all tests pass and the documentation is build::
60 |
61 | tox
62 |
63 | - Preferably develop in the Python 3 virtual environment. Running
64 | ``tox`` ensures tests are run with both the Python 2 and Python 3
65 | interpreter but it takes some time to complete. Alternatively switch
66 | back and forth between versions and just run the tests::
67 |
68 | source python-2/bin/activate
69 | py.test
70 | source python-3/bin/activate
71 | py.test
72 |
73 | - Test coverage should be close to 100 percent. A great help is the
74 | HTML output produced by coverage.py::
75 |
76 | py.test --cov ndeftool --cov-report html
77 | firefox htmlcov/index.html
78 |
79 | - The documentation can be created and viewed loacally::
80 |
81 | (cd docs && make html)
82 | firefox docs/_build/html/index.html
83 |
84 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2017 by Stephen Tiedemann.
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include *.txt *.rst *.ini LICENSE .coveragerc docs/Makefile
2 | recursive-include src *.py
3 | recursive-include tests *.py
4 | recursive-include docs *.ico
5 | recursive-include docs *.png
6 | recursive-include docs *.py
7 | recursive-include docs *.rst
8 | recursive-include docs *.txt
9 | prune docs/_build
10 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | =====================================
2 | Create, modify and print NDEF Records
3 | =====================================
4 |
5 | .. image:: https://img.shields.io/pypi/v/ndeftool
6 | :target: https://pypi.python.org/pypi/ndeftool
7 | :alt: Python Package
8 |
9 | .. image:: https://img.shields.io/readthedocs/ndeftool
10 | :target: http://ndeftool.readthedocs.io
11 | :alt: Documentation
12 |
13 | .. image:: https://img.shields.io/codecov/c/github/nfcpy/ndeftool
14 | :target: https://codecov.io/gh/nfcpy/ndeftool
15 | :alt: Code Coverage
16 |
17 | The ``ndeftool`` is a command line utility to create or inspect NFC
18 | Data Exchange Format (NDEF) records and messages, released under the
19 | `ISC `_ license.
20 |
21 | .. code-block:: shell
22 |
23 | $ ndeftool text "Hello World" id "r1" uri "http://nfcpy.org" save -k "message.ndef" print
24 | Saving 2 records to message.ndef.
25 | NDEF Text Record ID 'r1' Text 'Hello World' Language 'en' Encoding 'UTF-8'
26 | NDEF Uri Record ID '' Resource 'http://nfcpy.org'
27 |
28 | The ``ndeftool`` documentation can be found on `Read the Docs
29 | `_ and the code on `GitHub
30 | `_.
31 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS = -v
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 |
15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
16 |
17 | help:
18 | @echo "Please use \`make ' where is one of"
19 | @echo " html to make standalone HTML files"
20 | @echo " dirhtml to make HTML files named index.html in directories"
21 | @echo " singlehtml to make a single large HTML file"
22 | @echo " pickle to make pickle files"
23 | @echo " json to make JSON files"
24 | @echo " htmlhelp to make HTML files and a HTML help project"
25 | @echo " qthelp to make HTML files and a qthelp project"
26 | @echo " devhelp to make HTML files and a Devhelp project"
27 | @echo " epub to make an epub"
28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
29 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
30 | @echo " text to make text files"
31 | @echo " man to make manual pages"
32 | @echo " changes to make an overview of all changed/added/deprecated items"
33 | @echo " linkcheck to check all external links for integrity"
34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
35 |
36 | clean:
37 | -rm -rf $(BUILDDIR)/*
38 |
39 | html:
40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
41 | @echo
42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
43 |
44 | dirhtml:
45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
48 |
49 | singlehtml:
50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
51 | @echo
52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
53 |
54 | pickle:
55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
56 | @echo
57 | @echo "Build finished; now you can process the pickle files."
58 |
59 | json:
60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
61 | @echo
62 | @echo "Build finished; now you can process the JSON files."
63 |
64 | htmlhelp:
65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
66 | @echo
67 | @echo "Build finished; now you can run HTML Help Workshop with the" \
68 | ".hhp project file in $(BUILDDIR)/htmlhelp."
69 |
70 | qthelp:
71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
72 | @echo
73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/nfcpy.qhcp"
76 | @echo "To view the help file:"
77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/nfcpy.qhc"
78 |
79 | devhelp:
80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
81 | @echo
82 | @echo "Build finished."
83 | @echo "To view the help file:"
84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/nfcpy"
85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/nfcpy"
86 | @echo "# devhelp"
87 |
88 | epub:
89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
90 | @echo
91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
92 |
93 | latex:
94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
95 | @echo
96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
98 | "(use \`make latexpdf' here to do that automatically)."
99 |
100 | latexpdf:
101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
102 | @echo "Running LaTeX files through pdflatex..."
103 | make -C $(BUILDDIR)/latex all-pdf
104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
105 |
106 | text:
107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
108 | @echo
109 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
110 |
111 | man:
112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
113 | @echo
114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
115 |
116 | changes:
117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
118 | @echo
119 | @echo "The overview file is in $(BUILDDIR)/changes."
120 |
121 | linkcheck:
122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
123 | @echo
124 | @echo "Link check complete; look for any errors in the above output " \
125 | "or in $(BUILDDIR)/linkcheck/output.txt."
126 |
127 | doctest:
128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
129 | @echo "Testing of doctests in the sources finished, look at the " \
130 | "results in $(BUILDDIR)/doctest/output.txt."
131 |
--------------------------------------------------------------------------------
/docs/commands/identifier.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | .. _identifier:
4 |
5 | identifier
6 | ==========
7 |
8 | Change the identifier of the last record.
9 |
10 | Synopsis
11 | --------
12 |
13 | .. code::
14 |
15 | ndeftool identifier [OPTIONS] NAME
16 | ndeftool id [OPTIONS] NAME
17 |
18 | Description
19 | -----------
20 |
21 | The **identifier** command either changes the current last record's name (NDEF
22 | Record ID) or, if the current message does not have any records, creates a
23 | record with unknown record type and the given record name.
24 |
25 | Options
26 | -------
27 |
28 | .. option:: --help
29 |
30 | Show this message and exit.
31 |
32 | Examples
33 | --------
34 |
35 | Create a record with `unknown` type and set the identifier.
36 |
37 | .. command-output:: ndeftool identifier 'record identifier' print
38 |
39 | Create two text records with specific identifiers.
40 |
41 | .. command-output:: ndeftool text 'first' id 'r1' text 'second' id 'r2' print
42 |
43 |
--------------------------------------------------------------------------------
/docs/commands/load.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | .. _load:
4 |
5 | load
6 | ====
7 |
8 | Load records or payloads from disk.
9 |
10 | Synopsis
11 | --------
12 |
13 | .. code::
14 |
15 | ndeftool load [OPTIONS] PATH
16 | ndeftool l [OPTIONS] PATH
17 |
18 | Description
19 | -----------
20 |
21 | The **load** command reads records or payloads from disk files or
22 | standard input. The files for reading are determined by the pattern
23 | specified by PATH, which in the simplest form is an existing file
24 | name. Other forms may include `*`, `?` and character ranges expressed
25 | by `[]`. A single `-` may be used to read from standard input. Note
26 | that patterns containg wildcards may need to be escaped to avoid shell
27 | expansion.
28 |
29 | The default mode of operation is to load files containing NDEF
30 | records. In `--pack` mode the files are loaded into the payload of
31 | NDEF records with record type (NDEF Record TNF and TYPE) set to the
32 | mimetype discovered from the payload and record name (NDEF Record ID)
33 | set to the filename.
34 |
35 | Options
36 | -------
37 |
38 | .. option:: -p, --pack
39 |
40 | Pack files as payload into mimetype records.
41 |
42 | .. option:: --help
43 |
44 | Show this message and exit.
45 |
46 | Examples
47 | --------
48 |
49 | Pack text from standard input and pack as record.
50 |
51 | .. command-output:: echo -n "Hello World" | ndeftool load --pack - print
52 | :shell:
53 |
54 | Read text from file and pack as record.
55 |
56 | .. command-output:: echo -n "Hello World" > /tmp/hello && ndeftool load --pack /tmp/hello print
57 | :shell:
58 |
59 | Read with path containing wildcard characters.
60 |
61 | .. command-output:: ndeftool load --pack 'i?d*.rst' print
62 |
63 | Read and pack multiple files.
64 |
65 | .. command-output:: ndeftool load --pack '../se?up.*' print
66 |
--------------------------------------------------------------------------------
/docs/commands/payload.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | .. _payload:
4 |
5 | payload
6 | =======
7 |
8 | Change the payload of the last record.
9 |
10 | Synopsis
11 | --------
12 |
13 | .. code::
14 |
15 | ndeftool payload [OPTIONS] DATA
16 | ndeftool pl [OPTIONS] DATA
17 |
18 | Description
19 | -----------
20 |
21 | The **payload** command either changes the current last record's data (NDEF
22 | Record PAYLOAD) or, if the current message does not have any records, creates a
23 | record with the given record data. The changed record is verified to
24 | successfully encode and decode unless disabled with `-x`.
25 |
26 | The data string may contain hexadecimal bytes using `\xNN` notation where
27 | each N is a nibble from [0-F].
28 |
29 | Options
30 | -------
31 |
32 | .. option:: -x, --no-check
33 |
34 | Do not check decoding after type name change.
35 |
36 | .. option:: --help
37 |
38 | Show this message and exit.
39 |
40 | Examples
41 | --------
42 |
43 | Create a plain text payload record.
44 |
45 | .. command-output:: ndeftool payload 'Hello World' typename 'text/plain' print
46 |
47 | Create an NFC Forum Text record with language code and content.
48 |
49 | .. command-output:: ndeftool payload '\x02enHello World' typename 'urn:nfc:wkt:T' print -l
50 |
51 | Create a record with a payload that does not match the record type. The first
52 | command creates an NFC Forum Text Record with language code identifier and text
53 | content. The second command then replaces the payload with just the text and
54 | would make decoding fail.
55 |
56 | .. command-output:: ndeftool text 'Hello World' payload -x 'Hello World' print -l
57 |
--------------------------------------------------------------------------------
/docs/commands/print.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | .. print:
4 |
5 | print
6 | =====
7 |
8 | Print records as human readable.
9 |
10 | Synopsis
11 | --------
12 |
13 | .. code::
14 |
15 | ndeftool print [OPTIONS]
16 | ndeftool p [OPTIONS]
17 |
18 | Description
19 | -----------
20 |
21 | The **print** command outputs a formatted representation of all current NDEF
22 | Records. By default this is the one line str() representation for each
23 | record. The `--long` format produces multiple indented lines per record in an
24 | attempt to provide a more readable output. Printing consumes all records so that
25 | no more data is send to stdout or given to the next command. This can be changed
26 | with the `--keep` flag.
27 |
28 | When given as the first command **print** attempts to decode an NDEF message
29 | from standard input and process the generated list of records.
30 |
31 | Options
32 | -------
33 |
34 | .. option:: -l, --long
35 |
36 | Output in a long print format.
37 |
38 | .. option:: -k, --keep
39 |
40 | Keep records for next command.
41 |
42 | .. option:: --help
43 |
44 | Show this message and exit.
45 |
46 | Examples
47 | --------
48 |
49 | Print records in short format.
50 |
51 | .. command-output:: ndeftool text "Hello World" print
52 |
53 | Print records in long format.
54 |
55 | .. command-output:: ndeftool text "Hello World" print --long
56 |
57 | Print records in both short and long format.
58 |
59 | .. command-output:: ndeftool text "Hello World" print --keep print --long
60 |
--------------------------------------------------------------------------------
/docs/commands/save.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | .. _save:
4 |
5 | save
6 | ====
7 |
8 | Save records or payloads to disk.
9 |
10 | Synopsis
11 | --------
12 |
13 | .. code::
14 |
15 | ndeftool save [OPTIONS] PATH
16 | ndeftool s [OPTIONS] PATH
17 |
18 | Description
19 | -----------
20 |
21 | The **save** command writes the current records to disk. The records to write
22 | can be restricted to the subset selected with `--skip`, `--count`, `--head` and
23 | `--tail` applied in that order. The default mode is to save all selected records
24 | as one NDEF message into a single file given by PATH. In `--burst` mode each
25 | record is written as one NDEF message into a separate file under the directory
26 | given by PATH. The file names are three digit numbers created from the record
27 | index. In `--unpack` mode the payload of each record is written to a separate
28 | file under directory PATH with the file name set to the record name (NDEF Record
29 | ID). Records without name are not written unless `--unpack` and `--burst` are
30 | both set.
31 |
32 | The **save** command does not replace existing files or directories unless this is
33 | requested with `--force`.
34 |
35 | The **save** command consumes records from the internal message pipe. This can
36 | be prevented with `--keep`, all records are then forwarded to the next command
37 | or written to standard output. When **save** is the first command it creates the
38 | pipe by reading from standard input.
39 |
40 | Options
41 | -------
42 |
43 | .. option:: --skip N
44 |
45 | Skip the first N records.
46 |
47 | .. option:: --count N
48 |
49 | Skip the first N records.
50 |
51 | .. option:: --head N
52 |
53 | Save the first N records.
54 |
55 | .. option:: --tail N
56 |
57 | Save the last N records.
58 |
59 | .. option:: -b, --burst
60 |
61 | Save single record files in directory.
62 |
63 | .. option:: -u, --unpack
64 |
65 | Unpack records to files in directory.
66 |
67 | .. option:: -f, --force
68 |
69 | Replace existing file or directory.
70 |
71 | .. option:: -k, --keep
72 |
73 | Forward records to next command.
74 |
75 | .. option:: --help
76 |
77 | Show this message and exit.
78 |
79 | Examples
80 | --------
81 |
82 | Create an NFC Forum Text Record and save it to to a file in the /tmp directory,
83 | overwriting the file if it exists.
84 |
85 | .. command-output:: ndeftool text "Hello World" save --force /tmp/hello.ndef
86 |
87 | Same as above but the with three NDEF Text Records.
88 |
89 | .. command-output:: ndeftool text One text Two text Three save --force /tmp/text.ndef
90 |
91 | Out of three records the second is saved using the `--skip` and `--count`
92 | options and the others are forwarded to print.
93 |
94 | .. command-output:: ndeftool txt aa txt bb txt cc save -f --skip 1 --count 1 /tmp/text.ndef print
95 |
96 | Out of three records the second is saved using the `--head` and `--tail` options
97 | and the others are forwarded to print.
98 |
99 | .. command-output:: ndeftool txt aa txt bb txt cc save -f --head 2 --tail 1 /tmp/text.ndef print
100 |
101 | Save each record to a separate file with auto-numbered file name plus `.ndef`
102 | extension.
103 |
104 | .. command-output:: ndeftool txt aa txt bb txt cc save -f --burst /tmp/text/
105 |
106 | Unpack record payloads to separate files using the record identifier as the file
107 | name.
108 |
109 | .. command-output:: ndeftool txt aa id 1.txt txt bb id 2.txt txt cc id 3.txt save -f --unpack /tmp/text/
110 |
--------------------------------------------------------------------------------
/docs/commands/smartposter.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | .. _smartposter:
4 |
5 | smartposter
6 | ===========
7 |
8 | Create an NFC Forum Smart Poster Record.
9 |
10 | Synopsis
11 | --------
12 |
13 | .. code::
14 |
15 | ndeftool smartposter [OPTIONS] RESOURCE
16 | ndeftool smp [OPTIONS] RESOURCE
17 |
18 | Description
19 | -----------
20 |
21 | The **smartposter** command creates an NFC Forum Smart Poster Record for the
22 | resource identifier. A smart poster record combines the uniform resource
23 | identifier with additional data such as titles and icons for representation and
24 | processing instructions for the reader application.
25 |
26 | A smart poster record should have title text for the desired languages, added
27 | with repetitive `-t` options. An English title text may also be added with
28 | `-T`. The recommended action set with `-a` tells the reader application to
29 | either run the default action for the URI, save it for later or open for
30 | editing.
31 |
32 | A smart poster may also provide a collection of icons for graphical
33 | representation. An icon file is added with the `-i` option that may be given
34 | more than once. The icon type is determined from the file content and must be an
35 | image or video mime type.
36 |
37 | Options
38 | -------
39 |
40 | .. option:: -T TEXT
41 |
42 | Smartposter title for language code 'en'.
43 |
44 | .. option:: -t LANG TEXT
45 |
46 | Smartposter title for a given language code.
47 |
48 | .. option:: -a [exec|save|edit]
49 |
50 | Recommended action for handling the resource.
51 |
52 | .. option:: -i FILENAME
53 |
54 | Icon file for a graphical representation.
55 |
56 | .. option:: --help
57 |
58 | Show this message and exit.
59 |
60 | Examples
61 | --------
62 |
63 | An NFC Forum Smart Poster Record with just a link, nothing more useful than a URI Record.
64 |
65 | .. command-output:: ndeftool smartposter http://nfcpy.org print
66 |
67 | Same as above but with an English title.
68 |
69 | .. command-output:: ndeftool smartposter -T 'nfcpy project' http://nfcpy.org print
70 |
71 | Titles for other languages must be given with a language code.
72 |
73 | .. command-output:: ndeftool smartposter -t de 'Google Deutschland' https://www.google.de print
74 |
75 | An emergency call number should be called immediately.
76 |
77 | .. command-output:: ndeftool smartposter -T 'EMERGENCY CALL 911' -a exec 'tel:911' print -l
78 |
79 | Add an icon file to a smart poster.
80 |
81 | .. command-output:: ndeftool smp -i images/ndeftool.png https://github.com/nfcpy/ndeftool print -l
82 |
--------------------------------------------------------------------------------
/docs/commands/text.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | .. _text:
4 |
5 | text
6 | ====
7 |
8 | Create an NFC Forum Text Record.
9 |
10 | Synopsis
11 | --------
12 |
13 | .. code::
14 |
15 | ndeftool text [OPTIONS] TEXT
16 | ndeftool txt [OPTIONS] TEXT
17 |
18 | Description
19 | -----------
20 |
21 | The **text** command creates an NFC Forum Text Record with the given input
22 | text. The text language defaults to English (language code `en`) and can be set
23 | with `--language` followed by the IANA language code. The text content is
24 | encoded as UTF-8 or UTF-16 depending on `--encoding`. The default encoding is
25 | UTF-8.
26 |
27 | Options
28 | -------
29 |
30 | .. option:: -l, --language TEXT
31 |
32 | Set the IANA language code.
33 |
34 | .. option:: --encoding [UTF-8|UTF-16]
35 |
36 | Set the encoding (default UTF-8).
37 |
38 | .. option:: --help
39 |
40 | Show this message and exit.
41 |
42 | Examples
43 | --------
44 |
45 | Create an NFC Forum Text Record with the default language `en` and encoding `UTF-8`.
46 |
47 | .. command-output:: ndeftool text 'created with the nfcpy ndeftool' print
48 |
49 | Create one text record with English text and one record with German text.
50 |
51 | .. command-output:: ndeftool text --language en 'English' text --language de 'Deutsch' print
52 |
53 | Create a text record with UTF-16 encoding.
54 |
55 | .. command-output:: ndeftool text --encoding UTF-16 'text encoded in UTF-16' | xxd -g 1
56 | :shell:
57 |
--------------------------------------------------------------------------------
/docs/commands/typename.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | .. _typename:
4 |
5 | typename
6 | ========
7 |
8 | Change the type name of the last record.
9 |
10 | Synopsis
11 | --------
12 |
13 | .. code::
14 |
15 | ndeftool typename [OPTIONS] TYPE
16 | ndeftool tn [OPTIONS] TYPE
17 |
18 | Description
19 | -----------
20 |
21 | The **typename** command either changes the current last record's type (NDEF
22 | Record TNF and TYPE) or, if the current message does not have any records,
23 | creates a record with the given record type. The changed record is verified to
24 | successfully encode and decode unless disabled with `-x`.
25 |
26 | Options
27 | -------
28 |
29 | .. option:: -x, --no-check
30 |
31 | Do not check decoding after type name change.
32 |
33 | .. option:: --help
34 |
35 | Show this message and exit.
36 |
37 | Examples
38 | --------
39 |
40 | Create a record with `text/plain` mime type and no payload.
41 |
42 | .. command-output:: ndeftool typename 'text/plain' print
43 |
44 | Create a plain text record and add some payload.
45 |
46 | .. command-output:: ndeftool typename 'text/plain' payload 'Hello World' print
47 |
48 | Create a record with a payload that does not match the record type.
49 |
50 | .. command-output:: ndeftool payload 'Hello World' typename -x 'urn:nfc:wkt:T' print -l
51 |
--------------------------------------------------------------------------------
/docs/commands/uri.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | .. _uri:
4 |
5 | uri
6 | ===
7 |
8 | Create an NFC Forum URI Record.
9 |
10 | Synopsis
11 | --------
12 |
13 | .. code::
14 |
15 | ndeftool uri [OPTIONS] RESOURCE
16 |
17 | Description
18 | -----------
19 |
20 | The **uri** command creates an NFC Forum URI Record with the given resource
21 | identifier. Note that this is actually an Internationalized Resource Identifier
22 | (IRI).
23 |
24 | Options
25 | -------
26 |
27 | .. option:: --help
28 |
29 | Show this message and exit.
30 |
31 | Examples
32 | --------
33 |
34 | Create a URI record that links to `http://nfcpy.org`.
35 |
36 | .. command-output:: ndeftool uri 'http://nfcpy.org' print
37 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # nfcpy documentation build configuration file, created by
4 | # sphinx-quickstart on Mon Sep 19 18:10:55 2011.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import codecs
15 | import datetime
16 | import sys, os, re
17 |
18 | try:
19 | import sphinx_rtd_theme
20 | except ImportError:
21 | sphinx_rtd_theme = None
22 |
23 |
24 | def read(*parts):
25 | """
26 | Build an absolute path from *parts* and and return the contents of the
27 | resulting file. Assume UTF-8 encoding.
28 | """
29 | here = os.path.abspath(os.path.dirname(__file__))
30 | with codecs.open(os.path.join(here, *parts), "rb", "utf-8") as f:
31 | return f.read()
32 |
33 |
34 | def find_version(*file_paths):
35 | """
36 | Build a path from *file_paths* and search for a ``__version__``
37 | string inside.
38 | """
39 | version_file = read(*file_paths)
40 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
41 | version_file, re.M)
42 | if version_match:
43 | return version_match.group(1)
44 | raise RuntimeError("Unable to find version string.")
45 |
46 |
47 | # If extensions (or modules to document with autodoc) are in another directory,
48 | # add these directories to sys.path here. If the directory is relative to the
49 | # documentation root, use os.path.abspath to make it absolute, like shown here.
50 | #sys.path.insert(0, os.path.abspath('.'))
51 |
52 | # -- General configuration ------------------------------------------------
53 |
54 | # If your documentation needs a minimal Sphinx version, state it here.
55 | needs_sphinx = '1.0'
56 |
57 | # Add any Sphinx extension module names here, as strings. They can be
58 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
59 | # ones.
60 | extensions = [
61 | 'sphinxcontrib.programoutput',
62 | 'sphinx.ext.autodoc',
63 | 'sphinx.ext.doctest',
64 | 'sphinx.ext.intersphinx',
65 | 'sphinx.ext.todo',
66 | ]
67 | intersphinx_mapping = {
68 | 'python': ('https://docs.python.org/3', None)
69 | }
70 | autodoc_member_order = 'bysource'
71 | autodoc_default_setup = {
72 | 'members': True,
73 | 'show-inheritance': True
74 | }
75 |
76 | # Add any paths that contain templates here, relative to this directory.
77 | templates_path = ['_templates']
78 |
79 | # The suffix of source filenames.
80 | source_suffix = '.rst'
81 |
82 | # The encoding of source files.
83 | source_encoding = 'utf-8-sig'
84 |
85 | # The master toctree document.
86 | master_doc = 'index'
87 |
88 | # General information about the project.
89 | project = u'ndeftool'
90 | year = datetime.date.today().year
91 | copyright = u'2017{0}, Stephen Tiedemann'.format(
92 | u'-{0}'.format(year) if year > 2017 else u""
93 | )
94 |
95 | # A string of reStructuredText that will be included at the end of
96 | # every source file that is read. This is the right place to add
97 | # substitutions that should be available in every file.
98 | rst_epilog = """
99 | .. _NFC Forum: http://nfc-forum.org/
100 | """
101 |
102 | # A string of reStructuredText that will be included at the beginning
103 | # of every source file that is read.
104 | rst_prolog = """
105 | """
106 |
107 | # The version info for the project you're documenting, acts as replacement for
108 | # |version| and |release|, also used in various other places throughout the
109 | # built documents.
110 | #
111 | # The full version, including alpha/beta/rc tags.
112 | release = find_version("../src/ndeftool/__init__.py")
113 | # The short X.Y version.
114 | version = release.rsplit(u".", 1)[0]
115 |
116 | # The language for content autogenerated by Sphinx. Refer to documentation
117 | # for a list of supported languages.
118 | language = 'en'
119 |
120 | # There are two options for replacing |today|: either, you set today to some
121 | # non-false value, then it is used:
122 | #today = ''
123 | # Else, today_fmt is used as the format for a strftime call.
124 | #today_fmt = '%B %d, %Y'
125 |
126 | # List of patterns, relative to source directory, that match files and
127 | # directories to ignore when looking for source files.
128 | exclude_patterns = ['_build']
129 |
130 | # The reST default role (used for this markup: `text`) to use for all
131 | # documents.
132 | default_role = 'py:obj'
133 |
134 | # If true, '()' will be appended to :func: etc. cross-reference text.
135 | add_function_parentheses = True
136 |
137 | # If true, the current module name will be prepended to all description
138 | # unit titles (such as .. function::).
139 | add_module_names = True
140 |
141 | # If true, sectionauthor and moduleauthor directives will be shown in the
142 | # output. They are ignored by default.
143 | show_authors = False
144 |
145 | # The name of the Pygments (syntax highlighting) style to use.
146 | pygments_style = 'sphinx'
147 |
148 | # A list of ignored prefixes for module index sorting.
149 | #modindex_common_prefix = []
150 |
151 | # If true, keep warnings as "system message" paragraphs in the built documents.
152 | #keep_warnings = False
153 |
154 |
155 | # -- Options for HTML output ---------------------------------------------------
156 |
157 | # The theme to use for HTML and HTML Help pages. See the documentation for
158 | # a list of builtin themes.
159 |
160 | if sphinx_rtd_theme:
161 | html_theme = "sphinx_rtd_theme"
162 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
163 | else:
164 | html_theme = "default"
165 |
166 | #html_title = ' '.join([project, version, "documentation"])
167 | #html_short_title = ' '.join([project, version])
168 | #html_last_updated_fmt = '%b %d, %Y'
169 | #html_show_sourcelink = True
170 |
171 | # Theme options are theme-specific and customize the look and feel of a theme
172 | # further. For a list of options available for each theme, see the
173 | # documentation.
174 | #html_theme_options = {}
175 |
176 | # Add any paths that contain custom themes here, relative to this directory.
177 | #html_theme_path = []
178 |
179 | # The name for this set of Sphinx documents. If None, it defaults to
180 | # " v documentation".
181 | #html_title = None
182 |
183 | # A shorter title for the navigation bar. Default is the same as html_title.
184 | #html_short_title = None
185 |
186 | # The name of an image file (relative to this directory) to place at the top
187 | # of the sidebar.
188 | html_logo = "images/ndeftool.png"
189 |
190 | # The name of an image file (within the static path) to use as favicon of the
191 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
192 | # pixels large.
193 | html_favicon = "images/ndeftool.ico"
194 |
195 | # Add any paths that contain custom static files (such as style sheets) here,
196 | # relative to this directory. They are copied after the builtin static files,
197 | # so a file named "default.css" will overwrite the builtin "default.css".
198 | # html_static_path = ['_static']
199 |
200 | # Add any extra paths that contain custom files (such as robots.txt or
201 | # .htaccess) here, relative to this directory. These files are copied
202 | # directly to the root of the documentation.
203 | #html_extra_path = []
204 |
205 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
206 | # using the given strftime format.
207 | #html_last_updated_fmt = '%b %d, %Y'
208 |
209 | # If true, SmartyPants will be used to convert quotes and dashes to
210 | # typographically correct entities.
211 | #html_use_smartypants = True
212 |
213 | # Custom sidebar templates, maps document names to template names.
214 | #html_sidebars = {}
215 |
216 | # Additional templates that should be rendered to pages, maps page names to
217 | # template names.
218 | #html_additional_pages = {}
219 |
220 | # If false, no module index is generated.
221 | #html_domain_indices = True
222 |
223 | # If false, no index is generated.
224 | #html_use_index = True
225 |
226 | # If true, the index is split into individual pages for each letter.
227 | #html_split_index = False
228 |
229 | # If true, links to the reST sources are added to the pages.
230 | #html_show_sourcelink = True
231 |
232 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
233 | #html_show_sphinx = True
234 |
235 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
236 | #html_show_copyright = True
237 |
238 | # If true, an OpenSearch description file will be output, and all pages will
239 | # contain a tag referring to it. The value of this option must be the
240 | # base URL from which the finished HTML is served.
241 | #html_use_opensearch = ''
242 |
243 | # This is the file name suffix for HTML files (e.g. ".xhtml").
244 | #html_file_suffix = None
245 |
246 | # Output file base name for HTML help builder.
247 | htmlhelp_basename = 'ndeftooldoc'
248 |
249 |
250 | # -- Options for LaTeX output ---------------------------------------------
251 |
252 | latex_elements = {
253 | # The paper size ('letterpaper' or 'a4paper').
254 | #'papersize': 'letterpaper',
255 |
256 | # The font size ('10pt', '11pt' or '12pt').
257 | #'pointsize': '10pt',
258 |
259 | # Additional stuff for the LaTeX preamble.
260 | #'preamble': '',
261 | }
262 |
263 | # Grouping the document tree into LaTeX files. List of tuples
264 | # (source start file, target name, title,
265 | # author, documentclass [howto, manual, or own class]).
266 | latex_documents = [
267 | ('index', 'ndeftool.tex', u'ndeftool documentation',
268 | u'Stephen Tiedemann', 'manual'),
269 | ]
270 |
271 | # The name of an image file (relative to this directory) to place at the top of
272 | # the title page.
273 | #latex_logo = None
274 |
275 | # For "manual" documents, if this is true, then toplevel headings are parts,
276 | # not chapters.
277 | #latex_use_parts = False
278 |
279 | # If true, show page references after internal links.
280 | #latex_show_pagerefs = False
281 |
282 | # If true, show URL addresses after external links.
283 | #latex_show_urls = False
284 |
285 | # Documents to append as an appendix to all manuals.
286 | #latex_appendices = []
287 |
288 | # If false, no module index is generated.
289 | #latex_domain_indices = True
290 |
291 |
292 | # -- Options for manual page output ---------------------------------------
293 |
294 | # One entry per manual page. List of tuples
295 | # (source start file, name, description, authors, manual section).
296 | man_pages = [
297 | ('index', 'ndeftool', u'ndeftool Documentation',
298 | [u'Stephen Tiedemann'], 1)
299 | ]
300 |
301 | # If true, show URL addresses after external links.
302 | #man_show_urls = False
303 |
304 | # -- Options for Texinfo output -------------------------------------------
305 |
306 | # Grouping the document tree into Texinfo files. List of tuples
307 | # (source start file, target name, title, author,
308 | # dir menu entry, description, category)
309 | texinfo_documents = [
310 | ('index', 'ndeftool', u'ndeftool Documentation', u'Stephen Tiedemann',
311 | 'ndeftool', 'Create or inspect NFC Data Exchange Format messages.',
312 | 'Miscellaneous'),
313 | ]
314 |
315 | # Documents to append as an appendix to all manuals.
316 | #texinfo_appendices = []
317 |
318 | # If false, no module index is generated.
319 | #texinfo_domain_indices = True
320 |
321 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
322 | #texinfo_show_urls = 'footnote'
323 |
324 | # If true, do not generate a @detailmenu in the "Top" node's menu.
325 | #texinfo_no_detailmenu = False
326 |
--------------------------------------------------------------------------------
/docs/contributing.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | .. include:: ../CONTRIBUTING.rst
4 |
--------------------------------------------------------------------------------
/docs/images/ndeftool.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nfcpy/ndeftool/3a526e42f57d107b99b81289fe854734d3957267/docs/images/ndeftool.ico
--------------------------------------------------------------------------------
/docs/images/ndeftool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nfcpy/ndeftool/3a526e42f57d107b99b81289fe854734d3957267/docs/images/ndeftool.png
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | ===================================
4 | NDEF Creation and Modification Tool
5 | ===================================
6 |
7 | .. _ISCL: http://choosealicense.com/licenses/isc/
8 | .. _GitHub: https://github.com/nfcpy/ndeftool
9 | .. _PyPI: https://pypi.python.org/pypi/ndeftool
10 |
11 | The **ndeftool** is a command line utility for creating, modifying and printing
12 | NFC Data Exchange Format (NDEF) Records. It is licensed under the `ISCL`_,
13 | hosted on `GitHub`_ and installable from `PyPI`_.
14 |
15 | .. code::
16 |
17 | $ pip install ndeftool
18 | $ ndeftool --help
19 |
20 | .. toctree::
21 | :maxdepth: 2
22 |
23 | ndeftool
24 | contributing
25 | license
26 |
--------------------------------------------------------------------------------
/docs/license.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | =======
4 | License
5 | =======
6 |
7 | The **ndeftool** is licensed under the Internet Systems Consortium ISC
8 | license. This is a permissive free software license functionally equivalent to
9 | the simplified BSD and the MIT license.
10 |
11 | License text
12 | ------------
13 |
14 | .. include:: ../LICENSE
15 |
--------------------------------------------------------------------------------
/docs/ndeftool.rst:
--------------------------------------------------------------------------------
1 | .. -*- mode: rst; fill-column: 80 -*-
2 |
3 | ========
4 | NDEFTOOL
5 | ========
6 |
7 | Create, modify or print NFC Data Exchange Format Records.
8 |
9 | Synopsis
10 | --------
11 |
12 | .. code::
13 |
14 | ndeftool [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...
15 |
16 | Description
17 | -----------
18 |
19 | The **ndeftool** provides a number of commands to create, modify or print NDEF
20 | records. Commands can be chained together to successively extend or modify the
21 | list of records that are generated. Except for **save** and **print**, all
22 | records forwarded by the last command are send to standard output as binary NDEF
23 | message data. The **save** and **print** commands implicitly consume all records
24 | unless the `--keep` option is set. They also, unlike all other commands, do not
25 | start an empty record list but read from standard input when given as the first
26 | command (the same behavior can be achieved for the **load** command by setting
27 | the file path to `-`).
28 |
29 | By default, NDEF records read from disk or standard input must be correctly
30 | formatted NDEF message data (for example, the first and the last record must
31 | have the message begin and end flag set). The `--relax` makes the decoder accept
32 | correctable data errors and the `--ignore` option will additionally skip records
33 | with uncorrectable errors.
34 |
35 |
36 | Options
37 | -------
38 |
39 | .. option:: --version
40 |
41 | Show the version and exit.
42 |
43 | .. option:: --relax
44 |
45 | Ignore some errors when decoding.
46 |
47 | .. option:: --ignore
48 |
49 | Ignore all errors when decoding.
50 |
51 | .. option:: --silent
52 |
53 | Suppress all progress information.
54 |
55 | .. option:: --debug
56 |
57 | Output debug progress information.
58 |
59 | .. option:: --help
60 |
61 | Show this message and exit.
62 |
63 |
64 | Commands
65 | --------
66 |
67 | .. toctree::
68 | :maxdepth: 1
69 |
70 | commands/load
71 | commands/save
72 | commands/print
73 |
74 | commands/identifier
75 | commands/typename
76 | commands/payload
77 |
78 | commands/text
79 | commands/uri
80 | commands/smartposter
81 |
82 |
83 | Examples
84 | --------
85 |
86 | Any NDEF Record can be constructed with the :ref:`payload`, :ref:`typename` and
87 | :ref:`identifier` commands.
88 |
89 | .. command-output:: ndeftool payload '\02ensample text' typename 'urn:nfc:wkt:T' id 'r1' print
90 |
91 | The same record can be created with the :ref:`text` command. Here the output
92 | goes to stdout and is then printed with a separate ndeftool process call.
93 |
94 | .. command-output:: ndeftool text 'sample text' id 'r1' | ndeftool print
95 | :shell:
96 |
97 | The :ref:`save` command writes the records to disk or for path name
98 | `-`. The following example creates an NDEF message with three NFC Forum Text
99 | Records, the first and last record with the message begin and message end flags
100 | set.
101 |
102 | .. command-output:: ndeftool text ONE text TWO text THREE save - | xxd -g 1
103 | :shell:
104 |
105 | The :ref:`save` command can be used to write intermediate results, here
106 | immediately after a text record has been created. Note that by writing to
107 | the result is a sequence of three individual NDEF messages of one
108 | record each. This would not be a proper NDEF message file.
109 |
110 | .. command-output:: ndeftool text ONE save - text TWO save - text THREE save - | xxd -g 1
111 | :shell:
112 |
113 | The :ref:`load` command reads records from disk or for path name `-`.
114 |
115 | .. command-output:: ndeftool text ONE text TWO text THREE | ndeftool load - print
116 | :shell:
117 |
118 | An empty NDEF Record can be created with an empty type name string. The first
119 | octet ``11010000b`` sets the Message Begin (MB) and Message End (ME) flags in
120 | the two most signifant bits. The Type Name Format (TNF) value 0 in the least
121 | significant three bits indicates that there is no type or payload associated
122 | with this record and thus the `TYPE LENGTH` and `PAYLOAD LENGTH` fields must be
123 | zero.
124 |
125 | .. command-output:: ndeftool typename '' | xxd -g 1
126 | :shell:
127 |
128 | The default decoding of an NDEF message requires correct data format. Data with
129 | minor format errors can be decoded with the `--relax` option. The following
130 | example creates two empty records with invalid MB and ME flags that do only
131 | decode with `--relax`.
132 |
133 | .. command-output:: python3 -c "import sys; sys.stdout.buffer.write(b'\x10\0\0\x10\0\0')" | ndeftool --relax print
134 | :shell:
135 |
136 | NDEF message data with uncorrectable errors can be skipped with the `--ignore`
137 | option. The payload length 1 in the second record is an invalid value for an
138 | empty record.
139 |
140 | .. command-output:: python3 -c "import sys; sys.stdout.buffer.write(b'\x10\0\0\x10\1\0')" | ndeftool --ignore print
141 | :shell:
142 |
--------------------------------------------------------------------------------
/docs/requirements.in:
--------------------------------------------------------------------------------
1 | #
2 | # Required packages for build on readthedocs.org
3 | #
4 | sphinx==7.2.5
5 | sphinx_rtd_theme==1.3.0
6 | readthedocs-sphinx-search==0.3.1
7 | sphinxcontrib-programoutput==0.17
8 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | #
2 | # This file is autogenerated by pip-compile with Python 3.11
3 | # by the following command:
4 | #
5 | # pip-compile docs/requirements.in
6 | #
7 | alabaster==0.7.13
8 | # via sphinx
9 | babel==2.12.1
10 | # via sphinx
11 | certifi==2023.7.22
12 | # via requests
13 | charset-normalizer==3.2.0
14 | # via requests
15 | docutils==0.18.1
16 | # via
17 | # sphinx
18 | # sphinx-rtd-theme
19 | idna==3.4
20 | # via requests
21 | imagesize==1.4.1
22 | # via sphinx
23 | jinja2==3.1.2
24 | # via sphinx
25 | markupsafe==2.1.3
26 | # via jinja2
27 | packaging==23.1
28 | # via sphinx
29 | pygments==2.16.1
30 | # via sphinx
31 | readthedocs-sphinx-search==0.3.1
32 | # via -r docs/requirements.in
33 | requests==2.31.0
34 | # via sphinx
35 | snowballstemmer==2.2.0
36 | # via sphinx
37 | sphinx==7.2.5
38 | # via
39 | # -r docs/requirements.in
40 | # sphinx-rtd-theme
41 | # sphinxcontrib-applehelp
42 | # sphinxcontrib-devhelp
43 | # sphinxcontrib-htmlhelp
44 | # sphinxcontrib-jquery
45 | # sphinxcontrib-programoutput
46 | # sphinxcontrib-qthelp
47 | # sphinxcontrib-serializinghtml
48 | sphinx-rtd-theme==1.3.0
49 | # via -r docs/requirements.in
50 | sphinxcontrib-applehelp==1.0.7
51 | # via sphinx
52 | sphinxcontrib-devhelp==1.0.5
53 | # via sphinx
54 | sphinxcontrib-htmlhelp==2.0.4
55 | # via sphinx
56 | sphinxcontrib-jquery==4.1
57 | # via sphinx-rtd-theme
58 | sphinxcontrib-jsmath==1.0.1
59 | # via sphinx
60 | sphinxcontrib-programoutput==0.17
61 | # via -r docs/requirements.in
62 | sphinxcontrib-qthelp==1.0.6
63 | # via sphinx
64 | sphinxcontrib-serializinghtml==1.1.9
65 | # via sphinx
66 | urllib3==2.0.4
67 | # via requests
68 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | minversion = 6.0
3 | addopts = -ra
4 | testpaths = tests
5 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | # This is an annoteted version because I tend to forget.
2 | #
3 | # After 'git clone':
4 | #
5 | # virtualenv -p python2 venv/python2 && source venv/python2/bin/activate
6 | # python setup.py develop && pip install -U pip -r requirements-dev.txt
7 | #
8 | # virtualenv -p python3 venv/python3 && source venv/python3/bin/activate
9 | # python setup.py develop && pip install -U pip -r requirements-dev.txt
10 | #
11 | # Before 'git push':
12 | #
13 | # pip install --upgrade -r requirements-dev.txt
14 | # tox -c tox.ini # or just tox
15 | #
16 | # Required packages and typical use:
17 | #
18 | pytest # run regression tests "py.test"
19 | pytest-cov # check code coverage "py.test --cov ndeftool --cov-report html"
20 | flake8 # syntax and style checks "flake8 src/ tests/"
21 | tox # run tests before push "tox -c tox.ini"
22 | sphinx # to create documentation "(cd docs && make html doctest)"
23 | sphinx-rtd-theme # with the final layout "firefox docs/_build/html/index.html"
24 | sphinxcontrib-programoutput
25 |
--------------------------------------------------------------------------------
/requirements-pypi.txt:
--------------------------------------------------------------------------------
1 | # Packages needed for upload to PyPI. Works with the index servers and
2 | # credentials in ~/.pypirc.
3 | #
4 | # python setup.py sdist bdist_wheel
5 | #
6 | # twine upload -r test dist/ndeftool-x.y.z*
7 | #
8 | # twine upload -r pypi dist/ndeftool-x.y.z*
9 | #
10 | setuptools
11 | wheel
12 | twine
13 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal = 1
3 |
4 | [metadata]
5 | license_files = LICENSE
6 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import codecs
4 | import os
5 | import re
6 |
7 | from setuptools import setup, find_packages
8 |
9 | ###############################################################################
10 |
11 | NAME = "ndeftool"
12 | PACKAGES = find_packages(where="src")
13 | META_PATH = os.path.join("src", "ndeftool", "__init__.py")
14 | KEYWORDS = ["ndef", "nfc"]
15 | CLASSIFIERS = [
16 | "Development Status :: 5 - Production/Stable",
17 | "Intended Audience :: Developers",
18 | "Natural Language :: English",
19 | "License :: OSI Approved :: ISC License (ISCL)",
20 | "Operating System :: OS Independent",
21 | "Programming Language :: Python",
22 | "Programming Language :: Python :: 2",
23 | "Programming Language :: Python :: 2.7",
24 | "Programming Language :: Python :: 3",
25 | "Programming Language :: Python :: 3.4",
26 | "Programming Language :: Python :: 3.5",
27 | "Programming Language :: Python :: Implementation :: CPython",
28 | "Programming Language :: Python :: Implementation :: PyPy",
29 | "Topic :: Software Development :: Libraries :: Python Modules",
30 | ]
31 | INSTALL_REQUIRES = ["ndeflib", "click", "python-magic"]
32 | CONSOLE_SCRIPTS = ['ndeftool=ndeftool.cli:main']
33 |
34 | ###############################################################################
35 |
36 | HERE = os.path.abspath(os.path.dirname(__file__))
37 |
38 |
39 | def read(*parts):
40 | """
41 | Build an absolute path from *parts* and and return the contents of the
42 | resulting file. Assume UTF-8 encoding.
43 | """
44 | with codecs.open(os.path.join(HERE, *parts), "rb", "utf-8") as f:
45 | return f.read()
46 |
47 |
48 | META_FILE = read(META_PATH)
49 |
50 |
51 | def find_meta(meta):
52 | """
53 | Extract __*meta*__ from META_FILE.
54 | """
55 | meta_match = re.search(
56 | r"^__{meta}__ = ['\"]([^'\"]*)['\"]".format(meta=meta),
57 | META_FILE, re.M
58 | )
59 | if meta_match:
60 | return meta_match.group(1)
61 | raise RuntimeError("Unable to find __{meta}__ string.".format(meta=meta))
62 |
63 |
64 | if __name__ == "__main__":
65 | setup(
66 | name=NAME,
67 | description=find_meta("description"),
68 | license=find_meta("license"),
69 | url=find_meta("uri"),
70 | version=find_meta("version"),
71 | author=find_meta("author"),
72 | author_email=find_meta("email"),
73 | maintainer=find_meta("author"),
74 | maintainer_email=find_meta("email"),
75 | keywords=KEYWORDS,
76 | long_description=read("README.rst"),
77 | packages=PACKAGES,
78 | package_dir={"": "src"},
79 | zip_safe=False,
80 | classifiers=CLASSIFIERS,
81 | install_requires=INSTALL_REQUIRES,
82 | entry_points={'console_scripts': CONSOLE_SCRIPTS},
83 | )
84 |
--------------------------------------------------------------------------------
/src/ndeftool/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # METADATA ####################################################################
4 |
5 | __version__ = "0.1.3"
6 |
7 | __title__ = "ndeftool"
8 | __description__ = "Create or inspect NFC Data Exchange Format messages."
9 | __uri__ = "https://github.com/nfcpy/ndeftool/"
10 |
11 | __author__ = "Stephen Tiedemann"
12 | __email__ = "stephen.tiedemann@gmail.com"
13 |
14 | __license__ = "ISC"
15 | __copyright__ = "Copyright (c) 2017 Stephen Tiedemann"
16 |
17 | ###############################################################################
18 |
--------------------------------------------------------------------------------
/src/ndeftool/cli.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from functools import wraps
4 | import os.path
5 | import click
6 | import ndef
7 |
8 | version_message = '%%(prog)s %%(version)s (ndeflib %s)' % ndef.__version__
9 | command_plugins = os.path.join(os.path.dirname(__file__), 'commands')
10 |
11 |
12 | def echo(*args, **kwargs):
13 | click.echo(*args, **kwargs)
14 |
15 |
16 | def info(*args):
17 | if click.get_current_context().meta['output-logmsg'] != 'silent':
18 | click.secho(*args, err=True, fg='green')
19 |
20 |
21 | def dmsg(*args):
22 | if click.get_current_context().meta['output-logmsg'] == 'debug':
23 | click.secho(*args, err=True, fg='yellow')
24 |
25 |
26 | def warn(*args):
27 | click.secho(*args, err=True, fg='red')
28 |
29 |
30 | class CommandGroup(click.Group):
31 | def list_commands(self, ctx):
32 | command_list = []
33 |
34 | # All commands are separate Python files within the
35 | # command_plugins folder.
36 | for filename in sorted(os.listdir(command_plugins)):
37 | basename, extension = os.path.splitext(filename)
38 | if extension == '.py' and basename != '__init__':
39 | command_list.append(basename)
40 |
41 | return command_list
42 |
43 | def get_command(self, ctx, name):
44 | name = name.lower() # all commands treated case insensitive
45 |
46 | # From list_commands() we get the command file names without
47 | # extension. We use the upper case letters to construct the
48 | # abbreviated name and import the module if the requested
49 | # command name matches either the lower case full or short
50 | # name.
51 | for cmd_name in self.list_commands(ctx):
52 | cmd_abbr = ''.join(x for x in cmd_name if 'A' <= x <= 'Z')
53 | if name in (cmd_name.lower(), cmd_abbr.lower()):
54 | module = 'ndeftool.commands.' + cmd_name
55 | return __import__(module, None, None, ['cmd']).cmd
56 |
57 | def format_commands(self, ctx, formatter):
58 | rows = []
59 |
60 | # From list_commands() we get the command file names without
61 | # extension. We use the upper case letters to construct the
62 | # abbreviated name and store lower case versions of short and
63 | # long command name.
64 | for cmd_name in self.list_commands(ctx):
65 | cmd_abbr = ''.join(x for x in cmd_name if 'A' <= x <= 'Z')
66 | cmd_help = self.get_command(ctx, cmd_name).short_help or ''
67 | rows.append((cmd_abbr.lower(), cmd_name.lower(), cmd_help))
68 |
69 | # We want the command list to be sorted by abbreviated command
70 | # name with the shortest names first.
71 | rows = sorted(rows, key=lambda x: '%02d %s' % (len(x[0]), x[0]))
72 | rows = [('%s, %s' % (a, n) if a != n else a, h) for a, n, h in rows]
73 |
74 | with formatter.section('Commands'):
75 | formatter.write_dl(rows)
76 |
77 |
78 | @click.command(cls=CommandGroup, chain=True)
79 | @click.version_option(message=version_message)
80 | @click.option('--relax', 'errors', flag_value='relax',
81 | help='Ignore some errors when decoding.')
82 | @click.option('--ignore', 'errors', flag_value='ignore',
83 | help='Ignore all errors when decoding.')
84 | @click.option('--silent', 'logmsg', flag_value='silent',
85 | help='Suppress all progress information.')
86 | @click.option('--debug', 'logmsg', flag_value='debug',
87 | help='Output debug progress information.')
88 | @click.pass_context
89 | def main(ctx, **kwargs):
90 | """Create or inspect NFC Data Exchange Format messages.
91 |
92 | The ndeftool provides a number of commands to create or inspect
93 | NDEF messages. All commands can be chained to an internal
94 | processing pipeline and the whole fit into a command shell
95 | pipeline.
96 |
97 | \b
98 | ndeftool load FILE1 load FILE2 save FILE3
99 | ndeftool load - load FILE2 < FILE1 > FILE3
100 | cat FILE1 | ndeftool load - load FILE2 | hexdump -Cv
101 |
102 | The ndeftool processing pipeline builds an NDEF message from left
103 | to right, each command adds some NDEF record(s) to the message
104 | until it is either send to standard output or consumed by an
105 | ndeftool command (unless the --keep option is given to a command
106 | that would otherwise consume the message).
107 |
108 | \b
109 | ndeftool text 'one' text 'two' print --keep > two_text_records.ndef
110 | ndeftool text 'one' text 'two' save --keep two_text_records.ndef print
111 |
112 | A new pipeline is started after ndeftool command that consumed the
113 | current message. This can be used to generate or inspect multiple
114 | messages.
115 |
116 | \b
117 | ndeftool text 'one' save text_1.ndef text 'two' save text_2.ndef
118 | ndeftool load text_1.ndef print load text_2.ndef print
119 |
120 | Each command has it's own help page: 'ndeftool --help'
121 | """
122 | ctx.meta['decode-errors'] = kwargs['errors'] or 'strict'
123 | ctx.meta['output-logmsg'] = kwargs['logmsg'] or 'normal'
124 |
125 |
126 | @main.result_callback()
127 | def process_commands(processors, **kwargs):
128 | message = None
129 | for processor in processors:
130 | message = processor(message)
131 | dmsg('records = ' + str(message))
132 | echo(b''.join(ndef.message_encoder(message)), nl=False)
133 |
134 |
135 | def command_processor(func):
136 | @wraps(func)
137 | def wrapper(*args, **kwargs):
138 | def processor(message):
139 | return func(message, *args, **kwargs)
140 | return processor
141 | return wrapper
142 |
--------------------------------------------------------------------------------
/src/ndeftool/commands/IDentifier.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import click
4 | import ndef
5 |
6 | from ndeftool.cli import command_processor, dmsg
7 |
8 |
9 | @click.command(short_help="Change the identifier of the last record.")
10 | @click.argument('name')
11 | @command_processor
12 | def cmd(message, **kwargs):
13 | """The *identifier* command either changes the current last record's
14 | name (NDEF Record ID) or, if the current message does not have any
15 | records, creates a record with unknown record type and the given
16 | record name.
17 |
18 | \b
19 | Examples:
20 | ndeftool identifier 'record identifier' print
21 | ndeftool text 'first' id 'r1' text 'second' id 'r2' print
22 |
23 | """
24 | dmsg(__name__ + ' ' + str(kwargs))
25 |
26 | if not message:
27 | message = [ndef.Record('unknown')]
28 |
29 | try:
30 | message[-1].name = kwargs['name'].encode('latin', 'replace')
31 | except ValueError as error:
32 | raise click.ClickException(str(error))
33 |
34 | return message
35 |
--------------------------------------------------------------------------------
/src/ndeftool/commands/Load.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import click
4 | import magic
5 | import glob
6 | import ndef
7 |
8 | from ndeftool.cli import command_processor, info, warn, dmsg
9 |
10 |
11 | @click.command(short_help="Load records or payloads from disk.")
12 | @click.argument('path', type=click.Path())
13 | @click.option('-p', '--pack', is_flag=True,
14 | help="Pack files as payload into mimetype records.")
15 | @command_processor
16 | @click.pass_context
17 | def cmd(ctx, message, **kwargs):
18 | """The *load* command reads records or payloads from disk files or
19 | standard input. The files to read are searched with the pattern
20 | specified by PATH which in the simplest form is an existing file
21 | name. Other forms may include '*', '?' and character ranges
22 | expressed by '[]'. A single '-' may be used to read from standard
23 | input. Note that patterns containg wildcards may need to be
24 | escaped to avoid shell expansion.
25 |
26 | The default mode of operation is to load files containing NDEF
27 | records. In '--pack' mode the files are loaded into the payload of
28 | NDEF records with record type (NDEF Record TNF and TYPE) set to
29 | the mimetype discovered from the payload and record name (NDEF
30 | Record ID) set to the filename.
31 |
32 | \b
33 | Examples:
34 | ndeftool load message.ndef print
35 | ndeftool load '*.ndef' print
36 | cat message.ndef | ndeftool load - print
37 | ndeftool load --pack /etc/hostname print
38 | ndeftool load --pack '/etc/cron.daily/*' print
39 |
40 | """
41 | dmsg(__name__ + ' ' + str(kwargs))
42 |
43 | if message is None:
44 | message = []
45 |
46 | if kwargs['path'] == '-':
47 | filenames = kwargs['path']
48 | else:
49 | filenames = sorted(glob.iglob(kwargs['path']))
50 |
51 | if len(filenames) == 0:
52 | info("No files selected by path '%s'." % kwargs['path'])
53 |
54 | for filename in filenames:
55 | try:
56 | f = click.open_file(filename, 'rb')
57 | except (OSError, IOError) as error:
58 | warn(str(error))
59 | else:
60 | if kwargs['pack']:
61 | message.append(pack_file(f))
62 | else:
63 | message.extend(load_file(f, ctx.meta['decode-errors']))
64 |
65 | return message
66 |
67 |
68 | def pack_file(f):
69 | record_data = f.read()
70 | record_type = magic.from_buffer(record_data, mime=1)
71 | record_name = getattr(f, 'name', '')[0:255]
72 | return ndef.Record(record_type, record_name, record_data)
73 |
74 |
75 | def load_file(f, decode_errors):
76 | fn = getattr(f, 'name', '')
77 | try:
78 | records = list(ndef.message_decoder(f.read(), decode_errors))
79 | info("loaded %d record(s) from %s" % (len(records), fn))
80 | return records
81 | except ndef.DecodeError as error:
82 | dmsg(str(error))
83 | errmsg = "%s does not contain a valid NDEF message." % fn
84 | raise click.ClickException(errmsg)
85 |
--------------------------------------------------------------------------------
/src/ndeftool/commands/PayLoad.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import click
4 | import ndef
5 |
6 | from ndeftool.cli import command_processor, dmsg
7 |
8 |
9 | @click.command(short_help='Change the payload of the last record.')
10 | @click.argument('data', type=click.UNPROCESSED)
11 | @click.option('-x', '--no-check', is_flag=True,
12 | help='Do not check decoding after data change.')
13 | @command_processor
14 | @click.pass_context
15 | def cmd(ctx, message, **kwargs):
16 | """The *payload* command either changes the current last record's
17 | data (NDEF Record PAYLOAD) or, if the current message does
18 | not have any records, creates a record with the given record
19 | data. The changed record is verified to successfully encode and
20 | decode unless disabled with -x.
21 |
22 | The data string may contain hexadecimal bytes using '\\xNN'
23 | notation where each N is a nibble from [0-F].
24 |
25 | \b
26 | Examples:
27 | ndeftool payload 'Hello World' typename 'text/plain' print
28 | ndeftool payload '\\x02enHello World' typename 'urn:nfc:wkt:T' print -l
29 |
30 | """
31 | dmsg(__name__ + ' ' + str(kwargs))
32 | dmsg(repr(kwargs['data']))
33 |
34 | if not message:
35 | message = [ndef.Record('unknown')]
36 |
37 | record_type = message[-1].type
38 | record_name = message[-1].name
39 | record_data = eval(repr(kwargs['data'].encode()).replace('\\\\', '\\'))
40 |
41 | record = ndef.Record(record_type, record_name, record_data)
42 |
43 | if not kwargs['no_check']:
44 | octets = b''.join(ndef.message_encoder([record]))
45 | errors = ctx.meta['decode-errors']
46 | try:
47 | record = next(ndef.message_decoder(octets, errors))
48 | except ndef.DecodeError as error:
49 | raise click.ClickException(str(error))
50 |
51 | message[-1] = record
52 | return message
53 |
--------------------------------------------------------------------------------
/src/ndeftool/commands/Print.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import click
4 | import ndef
5 |
6 | from ndeftool.cli import command_processor, dmsg, info, echo
7 |
8 | LONG_FORMAT = " {:10} {}"
9 |
10 |
11 | @click.command(short_help="Print records as human readable.")
12 | @click.option('-l', '--long', is_flag=True, help="use a long print format")
13 | @click.option('-k', '--keep', is_flag=True, help="keep message in pipeline")
14 | @command_processor
15 | @click.pass_context
16 | def cmd(ctx, message, **kwargs):
17 | """The *print* command outputs a formatted representation of all
18 | current NDEF Records. By default this is the one line str()
19 | representation for each record. The '--long' format produces
20 | multiple indented lines per record in an attempt to provide a more
21 | readable output. Printing consumes all records so that no more
22 | data is send to stdout or given to the next command. This can be
23 | changed with the '--keep' flag.
24 |
25 | When given as the first command *print* attempts to decode an NDEF
26 | message from standard input and then process the generated list of
27 | records.
28 |
29 | \b
30 | Examples:
31 | ndeftool text 'made with ndeftool' print
32 | ndeftool text 'say one' print text 'say two' print
33 | ndeftool text one print --keep text two print
34 | ndeftool text 'print from stdin' | ndeftool print
35 | ndeftool text 'before' | ndeftool print text 'after' print
36 |
37 | """
38 | dmsg(__name__ + ' ' + str(kwargs))
39 |
40 | if message is None:
41 | info("Reading data from standard input")
42 | octets = click.get_binary_stream('stdin').read()
43 | errors = ctx.meta['decode-errors']
44 | try:
45 | message = list(ndef.message_decoder(octets, errors))
46 | except ndef.DecodeError as error:
47 | raise click.ClickException(str(error))
48 |
49 | for index, record in enumerate(message):
50 | if not kwargs['long']:
51 | echo(str(record))
52 | continue
53 |
54 | if isinstance(record, ndef.TextRecord):
55 | echo("NFC Forum Text Record [record #{}]".format(index+1))
56 | echo(LONG_FORMAT.format("content", record.text))
57 | echo(LONG_FORMAT.format("language", record.language))
58 | echo(LONG_FORMAT.format("encoding", record.encoding))
59 | elif isinstance(record, ndef.UriRecord):
60 | echo("NFC Forum URI Record [record #{}]".format(index+1))
61 | echo(LONG_FORMAT.format("resource", record.iri))
62 | elif isinstance(record, ndef.SmartposterRecord):
63 | echo("NFC Forum Smart Poster Record [record #{}]".format(index+1))
64 | echo(LONG_FORMAT.format("resource", record.resource.iri))
65 | if record.action:
66 | echo(LONG_FORMAT.format("action", record.action))
67 | for lang, text in record.titles.items():
68 | echo(LONG_FORMAT.format("title_" + lang, text))
69 | for icon_type, icon_data in record.icons.items():
70 | echo(LONG_FORMAT.format(icon_type, "%d byte" % len(icon_data)))
71 | else:
72 | echo("Record [record #{}]".format(index+1))
73 | echo(LONG_FORMAT.format("type", record.type))
74 | echo(LONG_FORMAT.format("name", record.name))
75 | echo(LONG_FORMAT.format("data", bytes(record.data)))
76 |
77 | return message if kwargs['keep'] else []
78 |
--------------------------------------------------------------------------------
/src/ndeftool/commands/SMartPoster.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import magic
4 | import click
5 | import ndef
6 |
7 | from ndeftool.cli import command_processor, dmsg
8 |
9 |
10 | @click.command(short_help='Create an NFC Forum Smart Poster record.')
11 | @click.argument('resource')
12 | @click.option('-T', 'title',
13 | help='Smartposter title for language code \'en\'.')
14 | @click.option('-t', 'titles', nargs=2, multiple=True, metavar='LANG TEXT',
15 | help='Smartposter title for a given language code.')
16 | @click.option('-a', 'action', type=click.Choice(['exec', 'save', 'edit']),
17 | help='Recommended action for handling the resource.')
18 | @click.option('-i', 'icons', multiple=True, type=click.File('rb', lazy=True),
19 | help='Icon file for a graphical representation.')
20 | @command_processor
21 | def cmd(message, **kwargs):
22 | """The *smartposter* command creates an NFC Forum Smart Poster Record
23 | for the resource identifier. A smart poster record combines the
24 | uniform resource identifier with additional data such as titles
25 | and icons for representation and processing instructions for the
26 | reader application.
27 |
28 | A smart poster should have title text entries for the desired
29 | languages, added with repetitive '-t' options. An English title
30 | text may also be added with '-T'. The recommended action set with
31 | '-a' tells the reader application to either run the default action
32 | for the URI, save it for later or open for editing.
33 |
34 | A smart poster may also provide a collection of icons for
35 | graphical representation. An icon file is added with the '-i'
36 | option which may be used more than once. The icon type is
37 | determined from the file content and must be an 'image' or 'video'
38 | mime type.
39 |
40 | \b
41 | Examples:
42 | ndeftool smartposter http://nfcpy.org print
43 | ndeftool smartposter -T 'nfcpy project' http://nfcpy.org print
44 | ndeftool smartposter -t en 'nfcpy project' http://nfcpy.org print
45 | ndeftool smartposter -T 'EMERGENCY CALL 911' -a exec tel:911
46 | ndeftool smartposter -i nfcpy-logo-32x32.ico http://nfcpy.org
47 |
48 | """
49 | dmsg(__name__ + ' ' + str(kwargs))
50 |
51 | if message is None:
52 | message = []
53 |
54 | record = ndef.SmartposterRecord(kwargs['resource'])
55 | for lang, text in kwargs['titles']:
56 | record.set_title(text, lang)
57 | if kwargs['title']:
58 | record.set_title(kwargs['title'], 'en')
59 | if kwargs['action']:
60 | record.action = kwargs['action']
61 | for icon_file in kwargs['icons']:
62 | icon_data = icon_file.read()
63 | icon_type = magic.from_buffer(icon_data, mime=True)
64 | if icon_type.startswith('image/') or icon_type.startswith('video/'):
65 | record.add_icon(icon_type, icon_data)
66 | else:
67 | errmsg = "file %s is not a proper icon file" % icon_file.name
68 | raise click.ClickException(errmsg)
69 |
70 | message.append(record)
71 | return message
72 |
--------------------------------------------------------------------------------
/src/ndeftool/commands/Save.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os.path
4 | import shutil
5 | import click
6 | import ndef
7 |
8 | from ndeftool.cli import command_processor, dmsg, info, warn
9 |
10 |
11 | @click.command(short_help="Save records or payloads to disk.")
12 | @click.argument('path', type=click.Path(writable=True))
13 | @click.option('--skip', type=click.IntRange(min=0), default=0,
14 | metavar='N', help="Skip the first N records.")
15 | @click.option('--count', type=click.IntRange(min=1), default=None,
16 | metavar='N', help="Skip the first N records.")
17 | @click.option('--head', type=click.IntRange(min=1), default=None,
18 | metavar='N', help="Save the first N records.")
19 | @click.option('--tail', type=click.IntRange(min=1), default=None,
20 | metavar='N', help="Save the last N records.")
21 | @click.option('-b', '--burst', is_flag=True,
22 | help="Save single record files in directory.")
23 | @click.option('-u', '--unpack', is_flag=True,
24 | help="Unpack records to files in directory.")
25 | @click.option('-f', '--force', is_flag=True,
26 | help="Replace existing file or directory.")
27 | @click.option('-k', '--keep', is_flag=True,
28 | help="Forward records to next command.")
29 | @command_processor
30 | @click.pass_context
31 | def cmd(ctx, message, **kwargs):
32 | """The *save* command writes the current records to disk. The records
33 | to write can be restricted to the subset selected with '--skip',
34 | '--count', '--head' and '--tail' applied in that order. The
35 | default mode is to save all selected records as one NDEF message
36 | into a single file given by PATH. In '--burst' mode each record is
37 | written as one NDEF message into a separate file under the
38 | directory given by PATH. The file names are three digit numbers
39 | created from the record index. In '--unpack' mode the payload of
40 | each record is written to a separate file under directory PATH
41 | with the file name set to the record name (NDEF Record ID).
42 | Records without name are not written unless '--unpack' and
43 | '--burst' are both set.
44 |
45 | The *save* command does not replace existing files or directories
46 | unless this is requested with '--force'.
47 |
48 | The *save* command consumes records from the internal message
49 | pipe. This can be prevented with '--keep', all records are then
50 | forwarded to the next command or written to standard output. When
51 | *save* is the first command it creates the pipe by reading from
52 | standard input.
53 |
54 | \b
55 | Examples:
56 | ndeftool text 'Hello World' save text.ndef
57 | ndeftool text 'Hello World' | ndeftool save text.ndef
58 | ndeftool text 'One' save one.ndef text 'Two' save two.ndef
59 |
60 | """
61 | dmsg(__name__ + ' ' + str(kwargs))
62 |
63 | path = kwargs['path']
64 |
65 | if os.path.exists(path) and not kwargs['force']:
66 | errmsg = "path '%s' exists. Use '--force' to replace."
67 | raise click.ClickException(errmsg % path)
68 |
69 | if message is None:
70 | info("Reading data from standard input")
71 | octets = click.get_binary_stream('stdin').read()
72 | errors = ctx.meta['decode-errors']
73 | try:
74 | message = list(ndef.message_decoder(octets, errors))
75 | except ndef.DecodeError as error:
76 | raise click.ClickException(str(error))
77 |
78 | first = min(kwargs['skip'], len(message))
79 | count = min(kwargs['count'] or len(message), len(message))
80 | head = min(kwargs['head'] or count, count)
81 | tail = min(kwargs['tail'] or count, count)
82 | dmsg("first=%d count=%d head=%d tail=%d" % (first, count, head, tail))
83 | count = min(head, tail, len(message) - first)
84 | first = first + head - min(head, tail)
85 | dmsg("first=%d count=%d head=%d tail=%d" % (first, count, head, tail))
86 |
87 | if kwargs['burst'] or kwargs['unpack']:
88 | path = os.path.normpath(path)
89 | try:
90 | if os.path.isdir(path):
91 | shutil.rmtree(path)
92 | os.mkdir(path)
93 | except (OSError, IOError) as error:
94 | raise click.ClickException(str(error))
95 |
96 | for index, record in enumerate(message[first:first+count]):
97 | name = None
98 | if kwargs['unpack'] and record.name:
99 | name = record.name
100 | if kwargs['burst'] and not name:
101 | name = '%03d.ndef' % index
102 | if name:
103 | with click.open_file('%s/%s' % (path, name), 'wb') as f:
104 | info("Saving 1 record to {}.".format(f.name))
105 | if kwargs['unpack']:
106 | f.write(record.data)
107 | else:
108 | f.write(b''.join(ndef.message_encoder([record])))
109 | else:
110 | warn("Skipping 1 record without name")
111 | else:
112 | with click.open_file(path, 'wb') as f:
113 | filename = f.name if f.name != '-' else ''
114 |
115 | info("Saving {num} record{s} to {path}.".format(
116 | num=count, path=filename, s=('', 's')[count > 1]))
117 |
118 | f.write(b''.join(ndef.message_encoder(message[first:first+count])))
119 |
120 | if not kwargs['keep']:
121 | del message[first:first+count]
122 |
123 | return message
124 |
--------------------------------------------------------------------------------
/src/ndeftool/commands/TeXT.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import click
4 | import ndef
5 |
6 | from ndeftool.cli import command_processor, dmsg
7 |
8 |
9 | @click.command(short_help="Create an NFC Forum Text Record.")
10 | @click.argument('text')
11 | @click.option('-l', '--language', default='en',
12 | help="Set the IANA language code.")
13 | @click.option('--encoding', default='UTF-8',
14 | type=click.Choice(['UTF-8', 'UTF-16']),
15 | help="Set the encoding (default UTF-8).")
16 | @command_processor
17 | def cmd(message, **kwargs):
18 | """The *text* command creates an NFC Forum Text Record with the given
19 | input text. The text language defaults to 'en' and can be set
20 | with --language followed by the IANA language code.
21 |
22 | \b
23 | Examples:
24 | ndeftool text '' | xxd -g 1
25 | ndeftool text 'Created with the nfcpy ndeftool.' print
26 | ndeftool text 'first record' text 'second record' print
27 | ndeftool text -l en 'English' text -l de 'Deutsch' print
28 |
29 | """
30 | dmsg(__name__ + ' ' + str(kwargs))
31 |
32 | if message is None:
33 | message = []
34 |
35 | content = kwargs['text']
36 | language = kwargs['language']
37 | encoding = kwargs['encoding']
38 |
39 | record = ndef.TextRecord(content, language, encoding)
40 |
41 | message.append(record)
42 | return message
43 |
--------------------------------------------------------------------------------
/src/ndeftool/commands/TypeName.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import click
4 | import ndef
5 |
6 | from ndeftool.cli import command_processor, dmsg
7 |
8 |
9 | @click.command(short_help='Change the type name of the last record.')
10 | @click.argument('type')
11 | @click.option('-x', '--no-check', is_flag=True,
12 | help='Do not check decoding after type name change.')
13 | @command_processor
14 | @click.pass_context
15 | def cmd(ctx, message, **kwargs):
16 | """The *typename* command either changes the current last record's
17 | type (NDEF Record TNF and TYPE) or, if the current message does
18 | not have any records, creates a record with the given record
19 | type. The changed record is verified to successfully encode and
20 | decode unless disabled with -x.
21 |
22 | \b
23 | Examples:
24 | ndeftool typename 'text/plain' print
25 | ndeftool typename 'text/plain' payload 'Hello World' print
26 |
27 | """
28 | dmsg(__name__ + ' ' + str(kwargs))
29 |
30 | if not message:
31 | message = [ndef.Record()]
32 |
33 | record_type = kwargs['type'].encode('latin', 'replace')
34 | record_name = message[-1].name
35 | record_data = message[-1].data
36 |
37 | try:
38 | record = ndef.Record(record_type, record_name, record_data)
39 | except ValueError as error:
40 | raise click.ClickException(str(error))
41 |
42 | if not kwargs['no_check']:
43 | octets = b''.join(ndef.message_encoder([record]))
44 | errors = ctx.meta['decode-errors']
45 | try:
46 | record = next(ndef.message_decoder(octets, errors))
47 | except ndef.DecodeError as error:
48 | raise click.ClickException(str(error))
49 |
50 | message[-1] = record
51 | return message
52 |
--------------------------------------------------------------------------------
/src/ndeftool/commands/URI.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import click
4 | import ndef
5 |
6 | from ndeftool.cli import command_processor, dmsg
7 |
8 |
9 | @click.command(short_help="Create an NFC Forum URI Record.")
10 | @click.argument('resource')
11 | @command_processor
12 | def cmd(message, **kwargs):
13 | """The *uri* command creates an NFC Forum URI Record wit the given
14 | resource identifier. Note that this is actually an
15 | Internationalized Resource Identifier (IRI).
16 |
17 | \b
18 | Examples:
19 | ndeftool uri 'http://nfcpy.org' print
20 |
21 | """
22 | dmsg(__name__ + ' ' + str(kwargs))
23 |
24 | if message is None:
25 | message = []
26 |
27 | record = ndef.UriRecord(kwargs['resource'])
28 |
29 | message.append(record)
30 | return message
31 |
--------------------------------------------------------------------------------
/src/ndeftool/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nfcpy/ndeftool/3a526e42f57d107b99b81289fe854734d3957267/src/ndeftool/commands/__init__.py
--------------------------------------------------------------------------------
/tests/test_identifier.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, division
4 |
5 | import pytest
6 | from ndeftool.cli import main
7 |
8 |
9 | @pytest.fixture
10 | def runner():
11 | import click.testing
12 | return click.testing.CliRunner()
13 |
14 |
15 | @pytest.fixture
16 | def isolated_runner(runner):
17 | with runner.isolated_filesystem():
18 | yield runner
19 |
20 |
21 | def test_help_option_prints_usage(runner):
22 | result = runner.invoke(main, ['identifier', '--help'])
23 | assert result.exit_code == 0
24 | assert result.output.startswith('Usage: main identifier [OPTIONS] NAME')
25 |
26 |
27 | def test_abbreviated_command_name(runner):
28 | result = runner.invoke(main, ['id', '--help'])
29 | assert result.exit_code == 0
30 | assert result.output.startswith('Usage: main id [OPTIONS] NAME')
31 |
32 |
33 | def test_debug_option_prints_kwargs(runner):
34 | params = '--debug identifier name'.split()
35 | result = runner.invoke(main, params)
36 | assert result.exit_code == 0
37 | assert result.output.startswith("ndeftool.commands.IDentifier {")
38 |
39 |
40 | def test_identifier_as_first_command(runner):
41 | params = 'identifier name'.split()
42 | result = runner.invoke(main, params)
43 | assert result.exit_code == 0
44 | assert result.stdout_bytes == b'\xdd\x00\x00\x04name'
45 |
46 |
47 | def test_identifier_as_second_command(runner):
48 | params = 'typename text/plain identifier name'.split()
49 | result = runner.invoke(main, params)
50 | assert result.exit_code == 0
51 | assert result.stdout_bytes == b'\xda\n\x00\x04text/plainname'
52 |
53 |
54 | def test_very_long_name_raises_error(runner):
55 | result = runner.invoke(main, ['id', 256*'0'])
56 | assert result.exit_code == 1
57 | assert 'Error:' in result.output
58 |
--------------------------------------------------------------------------------
/tests/test_load.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, division
4 |
5 | import os
6 | import pytest
7 | from ndeftool.cli import main
8 |
9 |
10 | @pytest.fixture
11 | def runner():
12 | import click.testing
13 | return click.testing.CliRunner()
14 |
15 |
16 | @pytest.fixture
17 | def isolated_runner(runner):
18 | with runner.isolated_filesystem():
19 | yield runner
20 |
21 |
22 | def test_help_option_prints_usage(runner):
23 | result = runner.invoke(main, ['load', '--help'])
24 | assert result.exit_code == 0
25 | assert result.output.startswith('Usage: main load [OPTIONS] PATH')
26 |
27 |
28 | def test_abbreviated_command_name(runner):
29 | result = runner.invoke(main, ['l', '--help'])
30 | assert result.exit_code == 0
31 | assert result.output.startswith('Usage: main l [OPTIONS] PATH')
32 |
33 |
34 | def test_debug_option_prints_kwargs(runner):
35 | params = '--debug load -'.split()
36 | result = runner.invoke(main, params, input='')
37 | assert result.exit_code == 0
38 | assert result.output.startswith("ndeftool.commands.Load {")
39 |
40 |
41 | def test_pack_from_standard_input(runner):
42 | result = runner.invoke(main, ['load', '--pack', '-'], input=b"Hello World")
43 | assert result.exit_code == 0
44 | assert result.stdout_bytes == b'\xda\n\x0b\x07text/plainHello World'
45 |
46 |
47 | def test_pack_from_full_file_path(isolated_runner):
48 | open('hello.txt', 'w').write('Hello World')
49 | result = isolated_runner.invoke(main, ['load', '--pack', 'hello.txt'])
50 | assert result.exit_code == 0
51 | assert result.stdout_bytes == b'\xda\n\x0b\ttext/plainhello.txtHello World'
52 |
53 |
54 | def test_pack_from_glob_file_path(isolated_runner):
55 | open('hello.txt', 'w').write('Hello World')
56 | result = isolated_runner.invoke(main, ['load', '--pack', '*.txt'])
57 | assert result.exit_code == 0
58 | assert result.stdout_bytes == b'\xda\n\x0b\ttext/plainhello.txtHello World'
59 |
60 |
61 | def test_pack_glob_multiple_files(isolated_runner):
62 | open('hello1.txt', 'w').write('Hello World')
63 | open('hello2.txt', 'w').write('World Hello')
64 | result = isolated_runner.invoke(main, ['load', '--pack', 'hello?.txt'])
65 | assert result.exit_code == 0
66 | assert result.stdout_bytes == \
67 | b'\x9a\n\x0b\ntext/plainhello1.txtHello World' \
68 | b'Z\n\x0b\ntext/plainhello2.txtWorld Hello'
69 |
70 |
71 | def test_pack_pipe_multiple_files(isolated_runner):
72 | open('hello1.txt', 'w').write('Hello World')
73 | open('hello2.txt', 'w').write('World Hello')
74 | command = 'load --pack hello1.txt load --pack hello2.txt'
75 | result = isolated_runner.invoke(main, command.split())
76 | assert result.exit_code == 0
77 | assert result.stdout_bytes == \
78 | b'\x9a\n\x0b\ntext/plainhello1.txtHello World' \
79 | b'Z\n\x0b\ntext/plainhello2.txtWorld Hello'
80 |
81 |
82 | def test_load_from_standard_input(runner):
83 | octets = b'\xda\n\x0b\x07text/plainHello World'
84 | params = '--silent load -'
85 | result = runner.invoke(main, params.split(), input=octets)
86 | assert result.exit_code == 0
87 | assert result.stdout_bytes == octets
88 |
89 |
90 | def test_load_from_full_file_path(isolated_runner):
91 | octets = b'\xda\n\x0b\x09text/plainhello.txtHello World'
92 | open('hello.txt.ndef', 'wb').write(octets)
93 | params = '--silent load hello.txt.ndef'
94 | result = isolated_runner.invoke(main, params.split())
95 | assert result.exit_code == 0
96 | assert result.stdout_bytes == octets
97 |
98 |
99 | def test_load_from_glob_file_path(isolated_runner):
100 | octets = b'\xda\n\x0b\ttext/plainhello.txtHello World'
101 | open('hello.txt.ndef', 'wb').write(octets)
102 | params = '--silent load *.ndef'
103 | result = isolated_runner.invoke(main, params.split())
104 | assert result.exit_code == 0
105 | assert result.stdout_bytes == octets
106 |
107 |
108 | def test_pack_load_multiple_files(isolated_runner):
109 | octets1 = b'\xda\n\x0b\ntext/plainhello1.txtHello World'
110 | octets2 = b'\xda\n\x0b\ntext/plainhello2.txtWorld Hello'
111 | open('hello1.txt.ndef', 'wb').write(octets1)
112 | open('hello2.txt.ndef', 'wb').write(octets2)
113 | params = '--silent load hello?.txt.ndef'
114 | result = isolated_runner.invoke(main, params.split())
115 | assert result.exit_code == 0
116 | assert result.stdout_bytes == b'\x9a' + octets1[1:] + b'\x5a' + octets2[1:]
117 |
118 |
119 | def test_load_pipe_multiple_files(isolated_runner):
120 | octets1 = b'\xda\n\x0b\ntext/plainhello1.txtHello World'
121 | octets2 = b'\xda\n\x0b\ntext/plainhello2.txtWorld Hello'
122 | open('hello1.txt.ndef', 'wb').write(octets1)
123 | open('hello2.txt.ndef', 'wb').write(octets2)
124 | params = '--silent load hello1.txt.ndef load hello2.txt.ndef'
125 | result = isolated_runner.invoke(main, params.split())
126 | assert result.exit_code == 0
127 | assert result.stdout_bytes == b'\x9a' + octets1[1:] + b'\x5a' + octets2[1:]
128 |
129 |
130 | def test_load_strict_then_relax(isolated_runner):
131 | octets = b'\x1a\n\x0b\ttext/plainhello.txtHello World'
132 | open('hello.txt.ndef', 'wb').write(octets)
133 | params = '--silent load hello.txt.ndef'
134 | result = isolated_runner.invoke(main, params.split())
135 | assert result.exit_code == 1
136 | assert result.output.startswith(
137 | 'Error: hello.txt.ndef does not contain a valid NDEF message.')
138 | params = '--relax ' + params
139 | result = isolated_runner.invoke(main, params.split())
140 | assert result.exit_code == 0
141 | assert result.stdout_bytes == b'\xda' + octets[1:]
142 |
143 |
144 | def test_load_relax_then_ignore(isolated_runner):
145 | octets1 = b'\xda\n\xff\ntext/plainhello1.txtHello World'
146 | octets2 = b'\x1a\n\x0b\ntext/plainhello2.txtWorld Hello'
147 | open('hello1.txt.ndef', 'wb').write(octets1)
148 | open('hello2.txt.ndef', 'wb').write(octets2)
149 | params = '--relax --silent load hello1.txt.ndef load hello2.txt.ndef'
150 | result = isolated_runner.invoke(main, params.split())
151 | assert result.exit_code == 1
152 | assert result.output.startswith(
153 | 'Error: hello1.txt.ndef does not contain a valid NDEF message.')
154 | params = '--ignore --silent load hello1.txt.ndef load hello2.txt.ndef'
155 | result = isolated_runner.invoke(main, params.split())
156 | assert result.exit_code == 0
157 | assert result.stdout_bytes == b'\xda' + octets2[1:]
158 |
159 |
160 | def test_no_files_selected_by_path(isolated_runner):
161 | params = 'load hello.txt.ndef load *.txt.ndef'
162 | result = isolated_runner.invoke(main, params.split())
163 | assert result.exit_code == 0
164 | assert result.output.splitlines() == [
165 | "No files selected by path 'hello.txt.ndef'.",
166 | "No files selected by path '*.txt.ndef'.",
167 | ]
168 |
169 |
170 | def test_permission_denied_error(isolated_runner):
171 | open('hello.txt', 'w').write("Hello World")
172 | os.chmod('hello.txt', 0)
173 | params = 'load --pack *.txt'
174 | result = isolated_runner.invoke(main, params.split())
175 | assert result.exit_code == 0
176 | assert result.output == "[Errno 13] Permission denied: 'hello.txt'\n"
177 |
--------------------------------------------------------------------------------
/tests/test_main.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, division
4 |
5 | import pytest
6 | from ndeftool.cli import main
7 |
8 |
9 | @pytest.fixture
10 | def runner():
11 | import click.testing
12 | return click.testing.CliRunner()
13 |
14 |
15 | def test_no_command_prints_usage(runner):
16 | result = runner.invoke(main)
17 | assert result.exit_code == 0
18 | assert result.output.startswith(
19 | 'Usage: main [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...')
20 |
21 |
22 | def test_help_option_prints_usage(runner):
23 | result = runner.invoke(main, ['--help', 'unknown-command'])
24 | assert result.exit_code == 0
25 | assert result.output.startswith(
26 | 'Usage: main [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...')
27 |
28 |
29 | def test_unknown_command_prints_error(runner):
30 | result = runner.invoke(main, ['unknown-command'])
31 | assert result.exit_code == 2
32 | assert result.output.startswith(
33 | 'Usage: main [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...')
34 | assert "Error:" in result.output
35 |
--------------------------------------------------------------------------------
/tests/test_payload.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, division
4 |
5 | import pytest
6 | from ndeftool.cli import main
7 |
8 |
9 | @pytest.fixture
10 | def runner():
11 | import click.testing
12 | return click.testing.CliRunner()
13 |
14 |
15 | @pytest.fixture
16 | def isolated_runner(runner):
17 | with runner.isolated_filesystem():
18 | yield runner
19 |
20 |
21 | def test_help_option_prints_usage(runner):
22 | result = runner.invoke(main, ['payload', '--help'])
23 | assert result.exit_code == 0
24 | assert result.output.startswith('Usage: main payload [OPTIONS] DATA')
25 |
26 |
27 | def test_abbreviated_command_name(runner):
28 | result = runner.invoke(main, ['pl', '--help'])
29 | assert result.exit_code == 0
30 | assert result.output.startswith('Usage: main pl [OPTIONS] DATA')
31 |
32 |
33 | def test_debug_option_prints_kwargs(isolated_runner):
34 | params = '--debug payload DATA'.split()
35 | result = isolated_runner.invoke(main, params, input='')
36 | assert result.exit_code == 0
37 | assert result.output.startswith("ndeftool.commands.PayLoad {")
38 |
39 |
40 | def test_payload_as_first_command(runner):
41 | params = 'payload helloworld'.split()
42 | result = runner.invoke(main, params)
43 | assert result.exit_code == 0
44 | assert result.stdout_bytes == b'\xd5\x00\nhelloworld'
45 |
46 |
47 | def test_payload_as_second_command(runner):
48 | params = 'typename text/plain payload helloworld'.split()
49 | result = runner.invoke(main, params)
50 | assert result.exit_code == 0
51 | assert result.stdout_bytes == b'\xd2\n\ntext/plainhelloworld'
52 |
53 |
54 | def test_payload_makes_decode_error(runner):
55 | params = '--silent load - payload helloworld'.split()
56 | result = runner.invoke(main, params, input=b'\xd1\x01\x03T\x02en')
57 | assert result.exit_code == 1
58 | assert 'Error:' in result.output
59 |
60 |
61 | def test_payload_error_not_checked(runner):
62 | params = '--silent load - payload --no-check helloworld'.split()
63 | result = runner.invoke(main, params, input=b'\xd1\x01\x03T\x02en')
64 | assert result.exit_code == 0
65 | assert result.stdout_bytes == b'\xd1\x01\nThelloworld'
66 |
--------------------------------------------------------------------------------
/tests/test_print.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, division
4 |
5 | import sys
6 | import pytest
7 | from ndeftool.cli import main
8 |
9 |
10 | @pytest.fixture
11 | def runner():
12 | import click.testing
13 | return click.testing.CliRunner()
14 |
15 |
16 | @pytest.fixture
17 | def isolated_runner(runner):
18 | with runner.isolated_filesystem():
19 | yield runner
20 |
21 |
22 | def test_help_option_prints_usage(runner):
23 | result = runner.invoke(main, ['print', '--help'])
24 | assert result.exit_code == 0
25 | assert result.output.startswith('Usage: main print [OPTIONS]')
26 |
27 |
28 | def test_abbreviated_command_name(runner):
29 | result = runner.invoke(main, ['p', '--help'])
30 | assert result.exit_code == 0
31 | assert result.output.startswith('Usage: main p [OPTIONS]')
32 |
33 |
34 | def test_debug_option_prints_kwargs(isolated_runner):
35 | params = '--debug print'.split()
36 | result = isolated_runner.invoke(main, params, input='')
37 | assert result.exit_code == 0
38 | assert result.output.startswith("ndeftool.commands.Print {")
39 |
40 |
41 | def test_read_from_standard_input(runner):
42 | octets = b'\xda\n\x0b\x02text/plainR1Hello World'
43 | params = 'print'.split()
44 | result = runner.invoke(main, params, input=octets)
45 | assert result.exit_code == 0
46 | assert "NDEF Record TYPE 'text/plain' ID 'R1' PAYLOAD 11" in result.output
47 |
48 |
49 | def test_read_from_stdin_with_errors(runner):
50 | octets = b'\x1a\n\x0b\x02text/plainR1Hello World'
51 | params = 'print'.split()
52 | result = runner.invoke(main, params, input=octets)
53 | assert result.exit_code == 1
54 | assert "Error:" in result.output
55 | params = '--relax print'.split()
56 | result = runner.invoke(main, params, input=octets)
57 | assert result.exit_code == 0
58 | assert "NDEF Record TYPE 'text/plain' ID 'R1' PAYLOAD 11" in result.output
59 |
60 |
61 | def test_read_from_message_pipe(runner):
62 | octets = b'\xda\n\x0b\x02text/plainR1Hello World'
63 | params = '--silent print --keep print'.split()
64 | result = runner.invoke(main, params, input=octets)
65 | assert result.exit_code == 0
66 | assert len(result.output.splitlines()) == 2
67 | assert result.output.splitlines()[0].startswith("NDEF Record")
68 | assert result.output.splitlines()[1].startswith("NDEF Record")
69 |
70 |
71 | def test_long_print_generic_record(runner):
72 | octets = b'\xda\x09\x0a\x02some/mimeR1HelloWorld'
73 | params = '--silent print --long'.split()
74 | result = runner.invoke(main, params, input=octets)
75 | assert result.exit_code == 0
76 | output = result.output.splitlines()
77 | assert len(output) == 4
78 | assert output[0] == "Record [record #1]"
79 | assert output[1].split() == ["type", "some/mime"]
80 | assert output[2].split() == ["name", "R1"]
81 | if sys.version_info.major > 2:
82 | assert output[3].split() == ["data", "b'HelloWorld'"]
83 | else:
84 | assert output[3].split() == ["data", "HelloWorld"]
85 |
86 |
87 | def test_long_print_text_record(runner):
88 | octets = b'\xd1\x01\x06T\x02enone'
89 | params = '--silent print --long'.split()
90 | result = runner.invoke(main, params, input=octets)
91 | assert result.exit_code == 0
92 | output = result.output.splitlines()
93 | assert len(output) == 4
94 | assert output[0] == "NFC Forum Text Record [record #1]"
95 | assert output[1].split() == ["content", "one"]
96 | assert output[2].split() == ["language", "en"]
97 | assert output[3].split() == ["encoding", "UTF-8"]
98 |
99 |
100 | def test_long_print_uri_record(runner):
101 | octets = b'\xd1\x01\nU\x03nfcpy.org'
102 | params = '--silent print --long'.split()
103 | result = runner.invoke(main, params, input=octets)
104 | assert result.exit_code == 0
105 | output = result.output.splitlines()
106 | assert len(output) == 2
107 | assert output[0] == "NFC Forum URI Record [record #1]"
108 | assert output[1].split() == ["resource", "http://nfcpy.org"]
109 |
110 |
111 | def test_long_print_smartposter_1(runner):
112 | octets = b'\xd1\x02\x0eSp\xd1\x01\nU\x03nfcpy.org'
113 | params = '--silent print --long'.split()
114 | result = runner.invoke(main, params, input=octets)
115 | assert result.exit_code == 0
116 | output = result.output.splitlines()
117 | assert len(output) == 2
118 | assert output[0] == "NFC Forum Smart Poster Record [record #1]"
119 | assert output[1].split() == ["resource", "http://nfcpy.org"]
120 |
121 |
122 | def test_long_print_smartposter_2(runner):
123 | octets = b'\xd1\x02\x1aSp\x91\x01\nU\x03nfcpy.orgQ\x01\x08T\x02enTitle'
124 | params = '--silent print --long'.split()
125 | result = runner.invoke(main, params, input=octets)
126 | assert result.exit_code == 0
127 | output = result.output.splitlines()
128 | assert len(output) == 3
129 | assert output[0] == "NFC Forum Smart Poster Record [record #1]"
130 | assert output[1].split() == ["resource", "http://nfcpy.org"]
131 | assert output[2].split() == ["title_en", "Title"]
132 |
133 |
134 | def test_long_print_smartposter_3(runner):
135 | octets = b'\xd1\x02\x15Sp\x91\x01\nU\x03nfcpy.orgQ\x03\x01act\x00'
136 | params = '--silent print --long'.split()
137 | result = runner.invoke(main, params, input=octets)
138 | assert result.exit_code == 0
139 | output = result.output.splitlines()
140 | print(result.output)
141 | assert len(output) == 3
142 | assert output[0] == "NFC Forum Smart Poster Record [record #1]"
143 | assert output[1].split() == ["resource", "http://nfcpy.org"]
144 | assert output[2].split() == ["action", "exec"]
145 |
146 |
147 | def test_long_print_smartposter_4(runner):
148 | octets = (
149 | b'\xd1\x02_Sp\x91\x01\nU\x03nfcpy.orgR\tEimage/png\x89PNG\r\n\x1a\n'
150 | b'\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00'
151 | b'\x00\x90wS\xde\x00\x00\x00\x0cIDAT\x08\xd7c\xf8\xff\xff?\x00\x05'
152 | b'\xfe\x02\xfe\xdc\xccY\xe7\x00\x00\x00\x00IEND\xaeB`\x82'
153 | )
154 | params = '--silent print --long'.split()
155 | result = runner.invoke(main, params, input=octets)
156 | assert result.exit_code == 0
157 | output = result.output.splitlines()
158 | print(result.output)
159 | assert len(output) == 3
160 | assert output[0] == "NFC Forum Smart Poster Record [record #1]"
161 | assert output[1].split() == ["resource", "http://nfcpy.org"]
162 | assert output[2].split() == ["image/png", "69", "byte"]
163 |
--------------------------------------------------------------------------------
/tests/test_save.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, division
4 |
5 | import os
6 | import pytest
7 | from ndeftool.cli import main
8 |
9 |
10 | @pytest.fixture
11 | def runner():
12 | import click.testing
13 | return click.testing.CliRunner()
14 |
15 |
16 | @pytest.fixture
17 | def isolated_runner(runner):
18 | with runner.isolated_filesystem():
19 | yield runner
20 |
21 |
22 | plain_text_records = [
23 | b'\x9a\n\x0b\x02text/plainR1Hello World',
24 | b'\x1a\n\x0b\x02text/plainR2Hello World',
25 | b'\x1a\n\x0b\x02text/plainR3Hello World',
26 | b'\x1a\n\x0b\x02text/plainR4Hello World',
27 | b'Z\n\x0b\x02text/plainR5Hello World']
28 |
29 |
30 | def test_help_option_prints_usage(runner):
31 | result = runner.invoke(main, ['save', '--help'])
32 | assert result.exit_code == 0
33 | assert result.output.startswith('Usage: main save [OPTIONS] PATH')
34 |
35 |
36 | def test_abbreviated_command_name(runner):
37 | result = runner.invoke(main, ['s', '--help'])
38 | assert result.exit_code == 0
39 | assert result.output.startswith('Usage: main s [OPTIONS] PATH')
40 |
41 |
42 | def test_debug_option_prints_kwargs(isolated_runner):
43 | params = '--debug save text.ndef'.split()
44 | result = isolated_runner.invoke(main, params, input='')
45 | assert result.exit_code == 0
46 | assert result.output.startswith("ndeftool.commands.Save {")
47 |
48 |
49 | def test_read_from_standard_input(isolated_runner):
50 | octets = b'\xda\n\x0b\x02text/plainR1Hello World'
51 | params = 'save hello.txt.ndef'.split()
52 | result = isolated_runner.invoke(main, params, input=octets)
53 | assert result.exit_code == 0
54 | assert open('hello.txt.ndef', 'rb').read() == octets
55 |
56 |
57 | def test_read_from_stdin_with_errors(isolated_runner):
58 | octets = b'\x9a\n\x0b\x02text/plainR1Hello World'
59 | params = 'save hello.txt.ndef'.split()
60 | result = isolated_runner.invoke(main, params, input=octets)
61 | assert result.exit_code == 1
62 | assert "Error:" in result.output
63 | params = '--relax save hello.txt.ndef'.split()
64 | result = isolated_runner.invoke(main, params, input=octets)
65 | assert result.exit_code == 0
66 | assert open('hello.txt.ndef', 'rb').read() == b'\xda' + octets[1:]
67 |
68 |
69 | def test_read_from_message_pipe(isolated_runner):
70 | octets = b'\xda\n\x0b\x02text/plainR1Hello World'
71 | params = 'save --keep hello1.ndef save hello2.ndef'.split()
72 | result = isolated_runner.invoke(main, params, input=octets)
73 | assert result.exit_code == 0
74 | assert open('hello1.ndef', 'rb').read() == octets
75 | assert open('hello2.ndef', 'rb').read() == octets
76 |
77 |
78 | def test_save_all_records_to_one_file(isolated_runner):
79 | octets = b''.join(plain_text_records)
80 | params = 'save hello.txt.ndef'.split()
81 | result = isolated_runner.invoke(main, params, input=octets)
82 | assert result.exit_code == 0
83 | assert open('hello.txt.ndef', 'rb').read() == octets
84 |
85 |
86 | def test_save_first_record_with_head(isolated_runner):
87 | octets = b''.join(plain_text_records)
88 | params = 'save --head 1 hello.txt.ndef'.split()
89 | result = isolated_runner.invoke(main, params, input=octets)
90 | assert result.exit_code == 0
91 | assert open('hello.txt.ndef', 'rb').read() == \
92 | b'\xda' + plain_text_records[0][1:]
93 |
94 |
95 | def test_save_first_record_with_count(isolated_runner):
96 | octets = b''.join(plain_text_records)
97 | params = 'save --count 1 hello.txt.ndef'.split()
98 | result = isolated_runner.invoke(main, params, input=octets)
99 | assert result.exit_code == 0
100 | assert open('hello.txt.ndef', 'rb').read() == \
101 | b'\xda' + plain_text_records[0][1:]
102 |
103 |
104 | def test_save_last_record_with_tail(isolated_runner):
105 | octets = b''.join(plain_text_records)
106 | params = 'save --tail 1 hello.txt.ndef'.split()
107 | result = isolated_runner.invoke(main, params, input=octets)
108 | assert result.exit_code == 0
109 | assert open('hello.txt.ndef', 'rb').read() == \
110 | b'\xda' + plain_text_records[-1][1:]
111 |
112 |
113 | def test_save_last_record_with_skip(isolated_runner):
114 | toskip = len(plain_text_records) - 1
115 | octets = b''.join(plain_text_records)
116 | params = 'save --skip {} hello.txt.ndef'.format(toskip).split()
117 | result = isolated_runner.invoke(main, params, input=octets)
118 | assert result.exit_code == 0
119 | assert open('hello.txt.ndef', 'rb').read() == \
120 | b'\xda' + plain_text_records[-1][1:]
121 |
122 |
123 | def test_save_skip_count_head_tail(isolated_runner):
124 | assert len(plain_text_records) == 5
125 | octets = b''.join(plain_text_records)
126 | params = 'save --skip 1 --count 3 --head 2 --tail 1 hello.txt.ndef'.split()
127 | result = isolated_runner.invoke(main, params, input=octets)
128 | assert result.exit_code == 0
129 | assert open('hello.txt.ndef', 'rb').read() == \
130 | b'\xda' + plain_text_records[2][1:]
131 |
132 |
133 | def test_save_do_not_overwrite_file(isolated_runner):
134 | open('hello.ndef', 'wb')
135 | octets = b''.join(plain_text_records)
136 | params = 'save hello.ndef'.split()
137 | result = isolated_runner.invoke(main, params, input=octets)
138 | assert result.exit_code == 1
139 | assert "Error: path 'hello.ndef' exists." in result.output
140 |
141 |
142 | def test_save_force_overwrite_file(isolated_runner):
143 | open('hello.ndef', 'wb')
144 | octets = b''.join(plain_text_records)
145 | params = 'save --force hello.ndef'.split()
146 | result = isolated_runner.invoke(main, params, input=octets)
147 | assert result.exit_code == 0
148 | assert open('hello.ndef', 'rb').read() == octets
149 |
150 |
151 | def test_burst_records_to_files(isolated_runner):
152 | octets = b''.join(plain_text_records)
153 | params = 'save --burst hello'.split()
154 | result = isolated_runner.invoke(main, params, input=octets)
155 | assert result.exit_code == 0
156 | for index in range(len(plain_text_records)):
157 | assert open('hello/%03d.ndef' % index, 'rb').read() == \
158 | b'\xda' + plain_text_records[index][1:]
159 |
160 |
161 | def test_unpack_records_to_files(isolated_runner):
162 | octets = b''.join(plain_text_records)
163 | params = 'save --unpack hello'.split()
164 | result = isolated_runner.invoke(main, params, input=octets)
165 | assert result.exit_code == 0
166 | for index in range(len(plain_text_records)):
167 | assert open('hello/R%d' % (index+1)).read() == "Hello World"
168 |
169 |
170 | def test_unpack_force_remove_dir(isolated_runner):
171 | os.mkdir('hello')
172 | for index in range(len(plain_text_records)):
173 | open('hello/R%d' % (index+1), 'w')
174 | octets = b''.join(plain_text_records)
175 | params = 'save --force --unpack hello'.split()
176 | result = isolated_runner.invoke(main, params, input=octets)
177 | assert result.exit_code == 0
178 | for index in range(len(plain_text_records)):
179 | assert open('hello/R%d' % (index+1)).read() == "Hello World"
180 |
181 |
182 | def test_unpack_record_without_name(isolated_runner):
183 | octets = b'\xda\n\x0b\x00text/plainHello World'
184 | params = 'save --unpack hello'.split()
185 | result = isolated_runner.invoke(main, params, input=octets)
186 | assert result.exit_code == 0
187 | assert "Skipping 1 record without name" in result.output
188 |
--------------------------------------------------------------------------------
/tests/test_smartposter.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, division
4 |
5 | import pytest
6 | from ndeftool.cli import main
7 |
8 |
9 | @pytest.fixture
10 | def runner():
11 | import click.testing
12 | return click.testing.CliRunner()
13 |
14 |
15 | @pytest.fixture
16 | def isolated_runner(runner):
17 | with runner.isolated_filesystem():
18 | yield runner
19 |
20 |
21 | def test_help_option_prints_usage(runner):
22 | result = runner.invoke(main, ['smartposter', '--help'])
23 | assert result.exit_code == 0
24 | assert result.output.startswith(
25 | 'Usage: main smartposter [OPTIONS] RESOURCE')
26 |
27 |
28 | def test_abbreviated_command_name(runner):
29 | result = runner.invoke(main, ['smp', '--help'])
30 | assert result.exit_code == 0
31 | assert result.output.startswith('Usage: main smp [OPTIONS] RESOURCE')
32 |
33 |
34 | def test_debug_option_prints_kwargs(runner):
35 | params = '--debug smartposter http://nfcpy.org'.split()
36 | result = runner.invoke(main, params)
37 | assert result.exit_code == 0
38 | assert result.output.startswith("ndeftool.commands.SMartPoster {")
39 |
40 |
41 | def test_smartposter_with_no_options(runner):
42 | params = 'smartposter http://nfcpy.org'.split()
43 | result = runner.invoke(main, params)
44 | assert result.exit_code == 0
45 | assert result.stdout_bytes == b'\xd1\x02\x0eSp\xd1\x01\nU\x03nfcpy.org'
46 |
47 |
48 | def test_two_smartposter_commands(runner):
49 | params = 'smartposter http://nfcpy.org smartposter tel:12'.split()
50 | result = runner.invoke(main, params)
51 | assert result.exit_code == 0
52 | assert result.stdout_bytes == \
53 | b'\x91\x02\x0eSp\xd1\x01\nU\x03nfcpy.orgQ\x02\x07Sp\xd1\x01\x03U\x0512'
54 |
55 |
56 | def test_smartposter_with_english_title(runner):
57 | params = 'smartposter -T Title http://nfcpy.org'.split()
58 | result = runner.invoke(main, params)
59 | assert result.exit_code == 0
60 | assert result.stdout_bytes == \
61 | b'\xd1\x02\x1aSp\x91\x01\nU\x03nfcpy.orgQ\x01\x08T\x02enTitle'
62 |
63 |
64 | def test_smartposter_with_german_title(runner):
65 | params = 'smartposter -t de Titel http://nfcpy.org'.split()
66 | result = runner.invoke(main, params)
67 | assert result.exit_code == 0
68 | assert result.stdout_bytes == \
69 | b'\xd1\x02\x1aSp\x91\x01\nU\x03nfcpy.orgQ\x01\x08T\x02deTitel'
70 |
71 |
72 | def test_smartposter_with_two_titles(runner):
73 | params = 'smartposter -T Title -t de Titel http://nfcpy.org'.split()
74 | result = runner.invoke(main, params)
75 | assert result.exit_code == 0
76 | assert result.stdout_bytes == \
77 | b'\xd1\x02&Sp\x91\x01\nU\x03nfcpy.org' \
78 | b'\x11\x01\x08T\x02deTitelQ\x01\x08T\x02enTitle'
79 |
80 |
81 | def test_smartposter_with_action_exec(runner):
82 | params = 'smartposter -a exec http://nfcpy.org'.split()
83 | result = runner.invoke(main, params)
84 | assert result.exit_code == 0
85 | assert result.stdout_bytes == \
86 | b'\xd1\x02\x15Sp\x91\x01\nU\x03nfcpy.orgQ\x03\x01act\x00'
87 |
88 |
89 | def test_smartposter_with_png_icon(isolated_runner):
90 | icon_1x1_png_data = bytearray.fromhex(
91 | '89504e470d0a1a0a0000000d494844520000000100000001080200000090'
92 | '7753de0000000c4944415408d763f8ffff3f0005fe02fedccc59e7000000'
93 | '0049454e44ae426082')
94 | open('1x1.png', 'wb').write(icon_1x1_png_data)
95 | params = 'smartposter -i 1x1.png http://nfcpy.org'.split()
96 | result = isolated_runner.invoke(main, params)
97 | assert result.exit_code == 0
98 | assert result.stdout_bytes == (
99 | b'\xd1\x02_Sp\x91\x01\nU\x03nfcpy.orgR\tEimage/png\x89PNG\r\n\x1a\n'
100 | b'\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00'
101 | b'\x00\x90wS\xde\x00\x00\x00\x0cIDAT\x08\xd7c\xf8\xff\xff?\x00\x05'
102 | b'\xfe\x02\xfe\xdc\xccY\xe7\x00\x00\x00\x00IEND\xaeB`\x82')
103 |
104 |
105 | def test_smartposter_with_invalid_icon(isolated_runner):
106 | open('1x1.png', 'w').write('this is not a png file')
107 | params = 'smartposter -i 1x1.png http://nfcpy.org'.split()
108 | result = isolated_runner.invoke(main, params)
109 | assert result.exit_code == 1
110 | assert "Error:" in result.output
111 |
--------------------------------------------------------------------------------
/tests/test_text.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, division
4 |
5 | import pytest
6 | from ndeftool.cli import main
7 |
8 |
9 | @pytest.fixture
10 | def runner():
11 | import click.testing
12 | return click.testing.CliRunner()
13 |
14 |
15 | @pytest.fixture
16 | def isolated_runner(runner):
17 | with runner.isolated_filesystem():
18 | yield runner
19 |
20 |
21 | def test_help_option_prints_usage(runner):
22 | result = runner.invoke(main, ['text', '--help'])
23 | assert result.exit_code == 0
24 | assert result.output.startswith('Usage: main text [OPTIONS] TEXT')
25 |
26 |
27 | def test_abbreviated_command_name(runner):
28 | result = runner.invoke(main, ['txt', '--help'])
29 | assert result.exit_code == 0
30 | assert result.output.startswith('Usage: main txt [OPTIONS] TEXT')
31 |
32 |
33 | def test_debug_option_prints_kwargs(runner):
34 | params = '--debug text sometext'.split()
35 | result = runner.invoke(main, params)
36 | assert result.exit_code == 0
37 | assert result.output.startswith("ndeftool.commands.TeXT {")
38 |
39 |
40 | def test_one_text_record_created(runner):
41 | params = 'text one'.split()
42 | result = runner.invoke(main, params)
43 | assert result.exit_code == 0
44 | assert result.stdout_bytes == b'\xd1\x01\x06T\x02enone'
45 |
46 |
47 | def test_two_text_records_created(runner):
48 | params = 'text one text two'.split()
49 | result = runner.invoke(main, params)
50 | assert result.exit_code == 0
51 | assert result.stdout_bytes == b'\x91\x01\x06T\x02enoneQ\x01\x06T\x02entwo'
52 |
53 |
54 | def test_default_text_language(runner):
55 | params = 'text English'.split()
56 | result = runner.invoke(main, params)
57 | assert result.exit_code == 0
58 | assert result.stdout_bytes == b'\xd1\x01\nT\x02enEnglish'
59 |
60 |
61 | def test_specific_text_language(runner):
62 | params = 'text --language de German'.split()
63 | result = runner.invoke(main, params)
64 | assert result.exit_code == 0
65 | assert result.stdout_bytes == b'\xd1\x01\tT\x02deGerman'
66 |
--------------------------------------------------------------------------------
/tests/test_typename.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, division
4 |
5 | import pytest
6 | from ndeftool.cli import main
7 |
8 |
9 | @pytest.fixture
10 | def runner():
11 | import click.testing
12 | return click.testing.CliRunner()
13 |
14 |
15 | @pytest.fixture
16 | def isolated_runner(runner):
17 | with runner.isolated_filesystem():
18 | yield runner
19 |
20 |
21 | def test_help_option_prints_usage(runner):
22 | result = runner.invoke(main, ['typename', '--help'])
23 | assert result.exit_code == 0
24 | assert result.output.startswith('Usage: main typename [OPTIONS] TYPE')
25 |
26 |
27 | def test_abbreviated_command_name(runner):
28 | result = runner.invoke(main, ['tn', '--help'])
29 | assert result.exit_code == 0
30 | assert result.output.startswith('Usage: main tn [OPTIONS] TYPE')
31 |
32 |
33 | def test_debug_option_prints_kwargs(isolated_runner):
34 | params = '--debug typename text/plain'.split()
35 | result = isolated_runner.invoke(main, params, input='')
36 | assert result.exit_code == 0
37 | assert result.output.startswith("ndeftool.commands.TypeName {")
38 |
39 |
40 | def test_typename_as_first_command(runner):
41 | params = 'typename text/plain'.split()
42 | result = runner.invoke(main, params)
43 | assert result.exit_code == 0
44 | assert result.stdout_bytes == b'\xd2\n\x00text/plain'
45 |
46 |
47 | def test_typename_as_second_command(runner):
48 | params = 'identifier name typename text/plain'.split()
49 | result = runner.invoke(main, params)
50 | assert result.exit_code == 0
51 | assert result.stdout_bytes == b'\xda\n\x00\x04text/plainname'
52 |
53 |
54 | def test_invalid_type_raises_error(runner):
55 | params = '--silent typename invalid-type'.split()
56 | result = runner.invoke(main, params)
57 | assert result.exit_code == 1
58 | assert 'Error:' in result.output
59 |
60 |
61 | def test_payload_type_error_raises_error(runner):
62 | params = '--silent payload helloworld typename urn:nfc:wkt:T'.split()
63 | result = runner.invoke(main, params)
64 | assert result.exit_code == 1
65 | assert 'Error:' in result.output
66 |
67 |
68 | def test_payload_type_error_not_checked(runner):
69 | params = 'payload helloworld typename --no-check urn:nfc:wkt:T'.split()
70 | result = runner.invoke(main, params)
71 | assert result.exit_code == 0
72 | assert result.stdout_bytes == b'\xd1\x01\nThelloworld'
73 |
--------------------------------------------------------------------------------
/tests/test_uri.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from __future__ import absolute_import, division
4 |
5 | import pytest
6 | from ndeftool.cli import main
7 |
8 |
9 | @pytest.fixture
10 | def runner():
11 | import click.testing
12 | return click.testing.CliRunner()
13 |
14 |
15 | @pytest.fixture
16 | def isolated_runner(runner):
17 | with runner.isolated_filesystem():
18 | yield runner
19 |
20 |
21 | def test_help_option_prints_usage(runner):
22 | result = runner.invoke(main, ['uri', '--help'])
23 | assert result.exit_code == 0
24 | assert result.output.startswith('Usage: main uri [OPTIONS] RESOURCE')
25 |
26 |
27 | def test_debug_option_prints_kwargs(runner):
28 | params = '--debug uri http://nfcpy.org'.split()
29 | result = runner.invoke(main, params)
30 | assert result.exit_code == 0
31 | assert result.output.startswith("ndeftool.commands.URI {")
32 |
33 |
34 | def test_one_uri_record_created(runner):
35 | params = 'uri http://nfcpy.org'.split()
36 | result = runner.invoke(main, params)
37 | assert result.exit_code == 0
38 | assert result.stdout_bytes == b'\xd1\x01\nU\x03nfcpy.org'
39 |
40 |
41 | def test_two_uri_records_created(runner):
42 | params = 'uri http://nfcpy.org uri tel:123'.split()
43 | result = runner.invoke(main, params)
44 | assert result.exit_code == 0
45 | assert result.stdout_bytes == b'\x91\x01\nU\x03nfcpy.orgQ\x01\x04U\x05123'
46 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = coverage-clean,py2,py3,flake8,manifest,docs,readme,coverage-report
3 |
4 |
5 | [testenv]
6 | deps = -rrequirements-dev.txt
7 | commands = coverage run --parallel -m pytest {posargs}
8 | passenv = http_proxy, HTTP_PROXY, https_proxy, HTTPS_PROXY, no_proxy, NO_PROXY
9 |
10 |
11 | [testenv:flake8]
12 | deps = flake8
13 | commands = flake8 src tests setup.py
14 |
15 |
16 | [testenv:docs]
17 | setenv =
18 | PYTHONHASHSEED = 313
19 | deps =
20 | -r docs/requirements.txt
21 | commands =
22 | sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
23 | sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
24 | python -m doctest README.rst
25 |
26 |
27 | [testenv:manifest]
28 | deps = check-manifest
29 | skip_install = true
30 | commands = check-manifest
31 |
32 |
33 | [testenv:readme]
34 | deps = readme_renderer
35 | skip_install = true
36 | commands = python setup.py check -r -s
37 |
38 |
39 | [testenv:coverage-clean]
40 | deps = coverage
41 | skip_install = true
42 | commands = coverage erase
43 |
44 |
45 | [testenv:coverage-report]
46 | deps = coverage
47 | skip_install = true
48 | commands =
49 | coverage combine
50 | coverage report
51 |
52 |
--------------------------------------------------------------------------------