├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── tests.yml
├── .gitignore
├── .hgignore
├── .hgtags
├── .readthedocs.yaml
├── LICENSE.txt
├── MANIFEST.in
├── README.rst
├── docs
├── Makefile
├── _static
│ └── sidebar.js
├── _templates
│ └── page.html
├── conf.py
├── index.rst
├── requirements.txt
└── spelling_wordlist.txt
├── pagesign.py
├── pyproject.toml
├── setup.cfg
└── test_pagesign.py
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve this library.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Environment**
27 | - OS, including version
28 | - Version of this library
29 |
30 | **Additional information**
31 | Add any other information about the problem here.
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | paths-ignore:
7 | - 'LICENSE.*'
8 | - 'README.*'
9 | - '.github/ISSUE-TEMPLATE/**'
10 | - 'docs/**'
11 | - '.hgignore'
12 | - '.gitignore'
13 |
14 | pull_request:
15 | branches: [ main ]
16 | paths-ignore:
17 | - 'LICENSE.*'
18 | - 'README.*'
19 | - '.github/ISSUE-TEMPLATE/**'
20 | - 'docs/**'
21 | - '.hgignore'
22 | - '.gitignore'
23 |
24 | schedule: # at 03:06 on day-of-month 6
25 | - cron: '6 3 6 * *'
26 |
27 | workflow_dispatch:
28 |
29 | jobs:
30 | build:
31 | runs-on: ${{ matrix.os }}
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | os: [ubuntu-latest, macos-latest, windows-latest]
36 | python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.9']
37 |
38 | steps:
39 | - uses: actions/checkout@v4
40 | - name: Set up Python ${{ matrix.python-version }}
41 | uses: actions/setup-python@v5
42 | with:
43 | python-version: ${{ matrix.python-version }}
44 | - name: Add Homebrew to PATH (Ubuntu)
45 | if: ${{ matrix.os == 'ubuntu-latest' }}
46 | run: |
47 | echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH
48 | - name: Set up age and minisign (POSIX)
49 | if: ${{ matrix.os != 'windows-latest' }}
50 | run: |
51 | brew install age minisign
52 | - name: Set up age and minisign (Windows)
53 | if: ${{ matrix.os == 'windows-latest' }}
54 | run: |
55 | choco install age.portable
56 | choco install minisign
57 | - name: Test with unittest
58 | run: |
59 | age --version
60 | minisign -v
61 | python test_pagesign.py
62 | - name: Test with coverage
63 | run: |
64 | pip install coverage
65 | coverage run --branch test_pagesign.py
66 | coverage xml
67 | - name: Upload coverage to Codecov
68 | uses: codecov/codecov-action@v4
69 | with:
70 | flags: unittests
71 | files: coverage.xml
72 | fail_ci_if_error: false
73 | token: ${{ secrets.CODECOV_TOKEN }}
74 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.pyo
3 | *.log
4 | __pycache__/
5 | build/
6 | docs/_build
7 | dist/
8 | *.egg-info/
9 | MANIFEST
10 |
--------------------------------------------------------------------------------
/.hgignore:
--------------------------------------------------------------------------------
1 | \.(pyc|pyo|log|coverage|json|dict-validwords)
2 | (build|dist|.*egg-info|htmlcov|__pycache__|\.mypy_cache)/
3 | ^MANIFEST$
4 |
--------------------------------------------------------------------------------
/.hgtags:
--------------------------------------------------------------------------------
1 | 2161640154bc4bbc2372e21c92a6c5c9dd67a7bf 0.1.0
2 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yaml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Set the version of Python and other tools you might need
9 | build:
10 | os: ubuntu-22.04
11 | tools:
12 | python: "3.11"
13 |
14 | # Build documentation in the docs/ directory with Sphinx
15 | sphinx:
16 | configuration: docs/conf.py
17 |
18 | # We recommend specifying your dependencies to enable reproducible builds:
19 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
20 | python:
21 | install:
22 | - requirements: docs/requirements.txt
23 |
24 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2021-2022 by Vinay Sajip.
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice,
10 | this list of conditions and the following disclaimer.
11 | * Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 | * The name(s) of the copyright holder(s) may not be used to endorse or
15 | promote products derived from this software without specific prior
16 | written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) "AS IS" AND ANY EXPRESS OR
19 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
20 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
21 | EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
22 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
27 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE.txt
2 | include README.rst
3 | include test_pagesign.py
4 |
5 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | |badge1| |badge2| |badge3|
2 |
3 | .. |badge1| image:: https://img.shields.io/github/actions/workflow/status/vsajip/pagesign/tests.yml
4 | :alt: GitHub test status
5 |
6 | .. |badge2| image:: https://img.shields.io/codecov/c/github/vsajip/pagesign
7 | :target: https://app.codecov.io/gh/vsajip/pagesign
8 | :alt: GitHub coverage status
9 |
10 | .. |badge3| image:: https://img.shields.io/pypi/v/pagesign
11 | :target: https://pypi.org/project/pagesign/
12 | :alt: PyPI package
13 |
14 |
15 | What is it?
16 | ===========
17 |
18 | `age `_ and `minisign
19 | `_ are modern command-line programs which
20 | respectively provide support for encryption/decryption and signing/verification of
21 | data. It is possible to provide programmatic access to their functionality by spawning
22 | separate processes to run them and then communicating with those processes from your
23 | program.
24 |
25 | This project, ``pagesign`` (for 'Python-age-sign'), implements a Python library which
26 | takes care of the internal details and allows its users to generate and manage keys,
27 | encrypt and decrypt data, and sign and verify messages using ``age`` and ``minisign``.
28 |
29 | This library does not install ``age`` or ``minisign`` for you: you will need to
30 | install them yourself (see `the documentation
31 | `_ for more
32 | information). It expects functionality found in age v1.0.0 or later, and minisign v0.8
33 | or later. Three programs are expected to be found on the PATH: ``age-keygen``, ``age``
34 | and ``minisign``. If any of them aren't found, this library won't work as expected.
35 |
36 | Installation
37 | ============
38 |
39 | Installing from PyPI
40 | --------------------
41 |
42 | You can install this package from the Python Package Index (pyPI) by running::
43 |
44 | pip install pagesign
45 |
46 |
47 | Installing from a source distribution archive
48 | ---------------------------------------------
49 | To install this package from a source distribution archive, do the following:
50 |
51 | 1. Extract all the files in the distribution archive to some directory on your
52 | system.
53 | 2. In that directory, run ``pip install .``, referencing a suitable ``pip`` (e.g. one
54 | from a specific venv which you want to install to).
55 | 3. Optionally, run ``python test_pagesign.py`` to ensure that the package is
56 | working as expected.
57 |
58 | Credits
59 | =======
60 |
61 | * The developers of ``age`` and ``minisign``.
62 |
63 | API Documentation
64 | =================
65 |
66 | https://docs.red-dove.com/pagesign/
67 |
68 | Change log
69 | ==========
70 |
71 | 0.1.1
72 | -----
73 |
74 | Released: Not yet.
75 |
76 | * Add the ``CryptException`` class and code to raise it when an operation fails.
77 |
78 | * Make a change so that ``clear_identities()`` now takes no arguments.
79 |
80 | * Add ``encrypt_mem()`` and ``decrypt_mem()`` functions to perform operations in
81 | memory.
82 |
83 | * Use a better algorithm for encryption and signing at the same time.
84 |
85 | 0.1.0
86 | -----
87 |
88 | Released: 2021-12-05
89 |
90 | * Initial release.
91 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
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 remote apidocs
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 | pdoc:
45 | mkdir -p $(BUILDDIR)/html/apidocs
46 | pdoc -o $(BUILDDIR)/html/apidocs --no-show-source --docformat google --logo https://www.red-dove.com/assets/img/rdclogo.gif ../pagesign.py
47 |
48 | apidocs:
49 | docfrag --venv local_tools --libs .. pagesign -f hovertip > hover.json
50 |
51 | remote:
52 | rsync -avz $(BUILDDIR)/html/* vopal:~/apps/rdc_docs/pagesign
53 |
54 | spelling:
55 | $(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)
56 |
57 | dirhtml:
58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
59 | @echo
60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
61 |
62 | singlehtml:
63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
64 | @echo
65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
66 |
67 | pickle:
68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
69 | @echo
70 | @echo "Build finished; now you can process the pickle files."
71 |
72 | json:
73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
74 | @echo
75 | @echo "Build finished; now you can process the JSON files."
76 |
77 | htmlhelp:
78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
79 | @echo
80 | @echo "Build finished; now you can run HTML Help Workshop with the" \
81 | ".hhp project file in $(BUILDDIR)/htmlhelp."
82 |
83 | qthelp:
84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
85 | @echo
86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Distlib.qhcp"
89 | @echo "To view the help file:"
90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Distlib.qhc"
91 |
92 | devhelp:
93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
94 | @echo
95 | @echo "Build finished."
96 | @echo "To view the help file:"
97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Distlib"
98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Distlib"
99 | @echo "# devhelp"
100 |
101 | epub:
102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
103 | @echo
104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
105 |
106 | latex:
107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
108 | @echo
109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
111 | "(use \`make latexpdf' here to do that automatically)."
112 |
113 | latexpdf:
114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
115 | @echo "Running LaTeX files through pdflatex..."
116 | make -C $(BUILDDIR)/latex all-pdf
117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
118 |
119 | text:
120 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
121 | @echo
122 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
123 |
124 | man:
125 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
126 | @echo
127 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
128 |
129 | changes:
130 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
131 | @echo
132 | @echo "The overview file is in $(BUILDDIR)/changes."
133 |
134 | linkcheck:
135 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
136 | @echo
137 | @echo "Link check complete; look for any errors in the above output " \
138 | "or in $(BUILDDIR)/linkcheck/output.txt."
139 |
140 | doctest:
141 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
142 | @echo "Testing of doctests in the sources finished, look at the " \
143 | "results in $(BUILDDIR)/doctest/output.txt."
144 |
--------------------------------------------------------------------------------
/docs/_static/sidebar.js:
--------------------------------------------------------------------------------
1 | /*
2 | * sidebar.js
3 | * ~~~~~~~~~~
4 | *
5 | * This script makes the Sphinx sidebar collapsible.
6 | *
7 | * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds in
8 | * .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton used to
9 | * collapse and expand the sidebar.
10 | *
11 | * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden and the
12 | * width of the sidebar and the margin-left of the document are decreased.
13 | * When the sidebar is expanded the opposite happens. This script saves a
14 | * per-browser/per-session cookie used to remember the position of the sidebar
15 | * among the pages. Once the browser is closed the cookie is deleted and the
16 | * position reset to the default (expanded).
17 | *
18 | * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
19 | * :license: BSD, see LICENSE for details.
20 | *
21 | */
22 |
23 | $(function() {
24 | // global elements used by the functions.
25 | // the 'sidebarbutton' element is defined as global after its
26 | // creation, in the add_sidebar_button function
27 | var bodywrapper = $('.bodywrapper');
28 | var sidebar = $('.sphinxsidebar');
29 | var sidebarwrapper = $('.sphinxsidebarwrapper');
30 |
31 | // original margin-left of the bodywrapper and width of the sidebar
32 | // with the sidebar expanded
33 | var bw_margin_expanded = bodywrapper.css('margin-left');
34 | var ssb_width_expanded = sidebar.width();
35 |
36 | // margin-left of the bodywrapper and width of the sidebar
37 | // with the sidebar collapsed
38 | var bw_margin_collapsed = '.8em';
39 | var ssb_width_collapsed = '.8em';
40 |
41 | // colors used by the current theme
42 | var dark_color = '#AAAAAA';
43 | var light_color = '#CCCCCC';
44 |
45 | function sidebar_is_collapsed() {
46 | return sidebarwrapper.is(':not(:visible)');
47 | }
48 |
49 | function toggle_sidebar() {
50 | if (sidebar_is_collapsed())
51 | expand_sidebar();
52 | else
53 | collapse_sidebar();
54 | }
55 |
56 | function collapse_sidebar() {
57 | sidebarwrapper.hide();
58 | sidebar.css('width', ssb_width_collapsed);
59 | bodywrapper.css('margin-left', bw_margin_collapsed);
60 | sidebarbutton.css({
61 | 'margin-left': '0',
62 | 'height': bodywrapper.height(),
63 | 'border-radius': '5px'
64 | });
65 | sidebarbutton.find('span').text('»');
66 | sidebarbutton.attr('title', _('Expand sidebar'));
67 | document.cookie = 'sidebar=collapsed';
68 | }
69 |
70 | function expand_sidebar() {
71 | bodywrapper.css('margin-left', bw_margin_expanded);
72 | sidebar.css('width', ssb_width_expanded);
73 | sidebarwrapper.show();
74 | sidebarbutton.css({
75 | 'margin-left': ssb_width_expanded-12,
76 | 'height': bodywrapper.height(),
77 | 'border-radius': '0 5px 5px 0'
78 | });
79 | sidebarbutton.find('span').text('«');
80 | sidebarbutton.attr('title', _('Collapse sidebar'));
81 | //sidebarwrapper.css({'padding-top':
82 | // Math.max(window.pageYOffset - sidebarwrapper.offset().top, 10)});
83 | document.cookie = 'sidebar=expanded';
84 | }
85 |
86 | function add_sidebar_button() {
87 | sidebarwrapper.css({
88 | 'float': 'left',
89 | 'margin-right': '0',
90 | 'width': ssb_width_expanded - 28
91 | });
92 | // create the button
93 | sidebar.append(
94 | ''
95 | );
96 | var sidebarbutton = $('#sidebarbutton');
97 | // find the height of the viewport to center the '<<' in the page
98 | var viewport_height;
99 | if (window.innerHeight)
100 | viewport_height = window.innerHeight;
101 | else
102 | viewport_height = $(window).height();
103 | var sidebar_offset = sidebar.offset().top;
104 | var sidebar_height = Math.max(bodywrapper.height(), sidebar.height());
105 | sidebarbutton.find('span').css({
106 | 'display': 'block',
107 | 'position': 'fixed',
108 | 'top': Math.min(viewport_height/2, sidebar_height/2 + sidebar_offset) - 10
109 | });
110 |
111 | sidebarbutton.click(toggle_sidebar);
112 | sidebarbutton.attr('title', _('Collapse sidebar'));
113 | sidebarbutton.css({
114 | 'border-radius': '0 5px 5px 0',
115 | 'color': '#444444',
116 | 'background-color': '#CCCCCC',
117 | 'font-size': '1.2em',
118 | 'cursor': 'pointer',
119 | 'height': sidebar_height,
120 | 'padding-top': '1px',
121 | 'padding-left': '1px',
122 | 'margin-left': ssb_width_expanded - 12
123 | });
124 |
125 | sidebarbutton.hover(
126 | function () {
127 | $(this).css('background-color', dark_color);
128 | },
129 | function () {
130 | $(this).css('background-color', light_color);
131 | }
132 | );
133 | }
134 |
135 | function set_position_from_cookie() {
136 | if (!document.cookie)
137 | return;
138 | var items = document.cookie.split(';');
139 | for(var k=0; k