diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml
new file mode 100644
index 0000000..cf5a036
--- /dev/null
+++ b/.github/workflows/artifacts.yml
@@ -0,0 +1,12 @@
+on: [status]
+
+jobs:
+ circleci_artifacts_redirector_job:
+ runs-on: ubuntu-latest
+ name: Run CircleCI artifacts redirector
+ steps:
+ - name: GitHub Action step
+ uses: larsoner/circleci-artifacts-redirector-action@master
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ artifact-path: 0/html/index.html
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
new file mode 100644
index 0000000..6db8b40
--- /dev/null
+++ b/.github/workflows/integration.yml
@@ -0,0 +1,54 @@
+name: continuous-integration
+
+on: [push, pull_request]
+
+jobs:
+
+ docs:
+
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: [3.6, 3.7, 3.8]
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v1
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ git submodule update --init
+ pip install -e .
+ pip install -r doc/requirements.txt
+
+ - name: Build docs
+ run: |
+ cd doc
+ make html
+
+ publish:
+
+ name: Publish to PyPi
+ needs: [docs]
+ if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout source
+ uses: actions/checkout@v2
+ - name: Set up Python 3.7
+ uses: actions/setup-python@v1
+ with:
+ python-version: 3.7
+ - name: Build package
+ run: |
+ pip install wheel
+ git submodule update --init
+ python setup.py sdist bdist_wheel
+ - name: Publish
+ uses: pypa/gh-action-pypi-publish@v1.1.0
+ with:
+ user: __token__
+ password: ${{ secrets.PYPI_KEY }}
diff --git a/.gitignore b/.gitignore
index 97dd07a..8045a79 100644
--- a/.gitignore
+++ b/.gitignore
@@ -105,3 +105,6 @@ venv.bak/
# Docs
_build/
+
+# VS code config
+.vscode
diff --git a/MANIFEST.in b/MANIFEST.in
index 443bb8b..e5583af 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1 +1,2 @@
graft doc/
+prune doc/_build
diff --git a/RELEASES.md b/RELEASES.md
index a978c2f..9242cbb 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -11,19 +11,19 @@ To create a new release of Sphinx-Copybutton, you need to do these things:
## To create the release
-To create a new release, [open an issue](https://github.com/choldgraf/sphinx-copybutton/issues/new) to keep
+To create a new release, [open an issue](https://github.com/ExecutableBookProject/sphinx-copybutton/issues/new) to keep
track of the to-do list for the release. Copy/paste the following markdown into the issue
and check off the boxes as you complete items:
```
-- [ ] Ensure that the [Sphinx-Copybutton version number](https://github.com/choldgraf/sphinx-copybutton/blob/master/jupyter_book/__init__.py)
+- [ ] Ensure that the [Sphinx-Copybutton version number](https://github.com/ExecutableBookProject/sphinx-copybutton/blob/master/jupyter_book/__init__.py)
is correct, and remove the `dev0` part of the version number.
Make a PR with the new number and merge into master.
- [ ] Create a new distribution for Sphinx-Copybutton by
[following the twine release instructions](https://twine.readthedocs.io/en/latest/#using-twine)
- [ ] Confirm that the new version of Sphinx-Copybutton [is posted to pypi](https://pypi.org/project/sphinx-copybutton/)
-- [ ] Bump the [Sphinx-Copybutton version number](https://github.com/choldgraf/sphinx-copybutton/blob/master/jupyter_book/__init__.py) to
+- [ ] Bump the [Sphinx-Copybutton version number](https://github.com/ExecutableBookProject/sphinx-copybutton/blob/master/jupyter_book/__init__.py) to
the next minor (or major) release and append `dev0` to the end.
- [ ] Celebrate! You've just released a new version of Sphinx-Copybutton!
```
diff --git a/doc/_static/test/TEST_COPYBUTTON.png b/doc/_static/test/TEST_COPYBUTTON.png
new file mode 100644
index 0000000..2787393
Binary files /dev/null and b/doc/_static/test/TEST_COPYBUTTON.png differ
diff --git a/doc/conf.py b/doc/conf.py
index 75d20c3..1cafea7 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -97,6 +97,14 @@
#
# html_sidebars = {}
+# CopyButton configuration
+copybutton_prompt_text = ">>> "
+# Switches for testing but shouldn't be activated in the live docs
+# copybutton_only_copy_prompt_lines = False
+# copybutton_remove_prompts = False
+# copybutton_image_path = "test/TEST_COPYBUTTON.png"
+# copybutton_selector = "div"
+
# -- Options for HTMLHelp output ---------------------------------------------
diff --git a/doc/index.rst b/doc/index.rst
index e43d0a6..0089596 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -10,10 +10,10 @@ Sphinx-copybutton
:target: https://pypi.org/project/sphinx_copybutton
:alt: PyPi page
-Sphinx-copybutton does one thing: add little "copy" button to the right
+Sphinx-copybutton does one thing: add a little "copy" button to the right
of your code blocks. That's it! It is a lightweight wrapper around the
excellent (and also lightweight) Javascript library
-`ClipboardJS `.
+`ClipboardJS `_.
**Here's an example**
@@ -26,17 +26,6 @@ And here's a code block, note the copy button to the right!
copy me!
-By default, ``sphinx-copybutton`` will remove Python prompts from
-each line that begins with them. For example, try copying the text
-below:
-
-.. code-block:: python
-
- >>> a = 2
- >>> print(a)
-
-The text that ``sphinx-copybutton`` uses can be configured as well. See
-:ref:`configure_copy_text` for more information.
If the code block overlaps to the right of the text area, you can just click
the button to get the whole thing.
@@ -45,6 +34,23 @@ the button to get the whole thing.
123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789
+You can configure ``sphinx-copybutton`` to detect *input prompts* in code
+cells, and then both remove these prompts before copying, as well as skip
+lines that *don't* start with prompts (in case they are output lines).
+
+For example, this site has been configured to strip Python prompts (">>> ").
+Try copy-pasting the code block below.
+
+.. code-block:: python
+
+ >>> a = 2
+ >>> print(a)
+ 2
+
+ >>> b = 'wow'
+ >>> print(b)
+ wow
+
Installation
============
@@ -54,7 +60,7 @@ You can install ``sphinx-copybutton`` with ``pip``:
pip install sphinx-copybutton
-`Here's a link to the sphinx-copybutton GitHub repository `_.
+`Here's a link to the sphinx-copybutton GitHub repository `_.
Usage
=====
@@ -70,8 +76,8 @@ extensions list. E.g.:
...
]
-When you build your site, your code blocks should now have little copy buttons to their
-right. Clicking the button will copy the code inside!
+When you build your site, your code blocks should now have little copy buttons
+to their right. Clicking the button will copy the code inside!
Customization
=============
@@ -86,58 +92,105 @@ Customize the CSS
To customize the display of the copy button, you can add your own CSS files
that overwrite the CSS in the
-`sphinx-copybutton CSS rules `_.
+`sphinx-copybutton CSS rules `_.
Just add these files to ``_static`` in your documentation folder, and it should
overwrite sphinx-copybutton's behavior.
.. _configure_copy_text:
-Customize the text that is removed during copying
--------------------------------------------------
+Strip and configure input prompts for code cells
+------------------------------------------------
+
+By default, ``sphinx-copybutton`` will copy the entire contents of a code
+block when the button is clicked. For many languages, it is common to
+include **input prompts** with your examples, along with the outputs from
+running the code.
+
+``sphinx-copybutton`` provides functionality to both
+strip input prompts, as well as *only* select lines that begin with a prompt.
+This allows users to click the button and *only* copy the input text,
+excluding the prompts and outputs.
+
+To define the prompt text that you'd like removed from copied text in your code
+blocks, use the following configuration value in your ``conf.py`` file:
+
+.. code-block:: python
+
+ copybutton_prompt_text = "myinputprompt"
+
+When this variable is set, ``sphinx-copybutton`` will remove the prompt from
+the beginning of any lines that start with the text you specify. In
+addition, *only* the lines that contain prompts will be copied if any are
+discovered. If no lines with prompts are found, then the full contents of
+the cell will be copied.
+
+For example, to exclude traditional Python prompts from your copied code,
+use the following configuration:
+
+.. code-block:: python
+
+ copybutton_prompt_text = ">>> "
+
+Configure whether *only* lines with prompts are copied
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, if sphinx-copybutton detects lines that begin with code prompts,
+it will *only* copy the text in those lines (after stripping the prompts).
+This assumes that the rest of the code block contains outputs that shouldn't
+be copied.
-By default, ``sphinx-copybutton`` will remove Python prompts (">>> ") from
-the beginning of each line. To change the text that is removed (or to remove
-no text at all), add the following configuration to your ``conf.py`` file:
+To disable this behavior, use the following configuration in ``conf.py``:
-.. code:: python
+.. code-block:: python
+
+ copybutton_only_copy_prompt_lines = False
+
+In this case, all lines of the code blocks will be copied after the prompts
+are stripped.
- copybutton_skip_text = "sometexttoskip"
+Configure whether the input prompts should be stripped
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Note that this text will only be removed from lines that *begin* with the text.
+By default, sphinx-copybutton will remove the prompt text from lines
+according to the value of ``copybutton_prompt_text``.
+
+To disable this behavior and copy the full text of lines with prompts
+(for example, if you'd like to copy *only* the lines with prompts, but not
+strip the prompts), use the following configuration in ``conf.py``:
+
+.. code-block:: python
+
+ copybutton_remove_prompts = False
Use a different copy button image
----------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-To use a different image for your copy buttons, the easiest thing to do is
-to add a small bit of javascript to your Sphinx build that points the image
-to something new. Follow these steps:
+To use a different image for your copy buttons, do the following:
-1. Create a new javascript file in your site's static folder (e.g., `_static/js/custom.js`).
- In it, put the following code:
+1. Place the image in the ``_static/`` folder of your site.
+2. Set the ``copybutton_image_path`` variable in your ``conf.py`` to be the
+ path to your image file, **relative to** ``_static/``.
- .. code-block:: javascript
- const updateCopyButtonImages = () => {
- const copybuttonimages = document.querySelectorAll('a.copybtn img')
- copybuttonimages.forEach((img, index) => {
- img.setAttribute('src', 'path-to-new-image.svg')
- })
- }
+Configure the CSS selector used to add copy buttons
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- runWhenDOMLoaded(updateCopyButtonImages)
+By default, ``sphinx-copybutton`` will add a copy button to all elements
+that match the following selection:
+.. code-block:: css
-2. Add this javascript file to your `conf.py` configuration like so:
+ div.highlight pre
- .. code-block:: python
+To change this selector, use the following configuration in ``conf.py``:
+
+.. code-block:: python
- def setup(app):
- app.add_javascript('js/custom.js');
+ copybutton_selector = "your.selector"
-This will replace the copybutton images each time the page loads!
+In this case, all elements that match ``your.selector`` will have a copy button
+added to them.
-**If you know of a better way to do this with sphinx, please don't hesitate to
-recommend something!**
Development
===========
@@ -145,7 +198,7 @@ Development
If you'd like to develop or make contributions for sphinx-copybutton, fork
the repository here:
-https://github.com/choldgraf/sphinx-copybutton
+https://github.com/ExecutableBookProject/sphinx-copybutton
pull to your computer and install locally with ``pip``::
diff --git a/setup.py b/setup.py
index 8dcf3db..b6a973f 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
import os
+from pathlib import Path
from setuptools import setup, find_packages
-from sphinx_copybutton import __version__
if (os.path.isdir('clipboard.js') and
not os.path.islink('sphinx_copybutton/_static/clipboard.min.js')):
@@ -18,21 +18,29 @@
with open('./README.md', 'r') as ff:
readme_text = ff.read()
+# Parse version
+init = Path(__file__).parent.joinpath("sphinx_copybutton", "__init__.py")
+for line in init.read_text().split("\n"):
+ if line.startswith("__version__ ="):
+ break
+version = line.split(" = ")[-1].strip('"')
+
setup(
name='sphinx-copybutton',
- version=__version__,
+ version=version,
description="Add a copy button to each of your code cells.",
long_description=readme_text,
long_description_content_type='text/markdown',
- author='Chris Holdgraf',
- author_email='choldgraf@berkeley.edu',
- url="https://github.com/choldgraf/sphinx-copybutton",
+ author='Executable Book Project',
+ url="https://github.com/ExecutableBookProject/sphinx-copybutton",
license='MIT License',
packages=find_packages(),
package_data={'sphinx_copybutton': ['_static/copybutton.css',
- '_static/copybutton.js',
+ '_static/copybutton.js_t',
'_static/copy-button.svg',
'_static/clipboard.min.js']},
- install_requires=["flit", "setuptools", "wheel", "sphinx"],
- classifiers=["License :: OSI Approved :: MIT License"]
+ classifiers=["License :: OSI Approved :: MIT License"],
+ install_requires=[
+ "sphinx>=1.8"
+ ]
)
diff --git a/sphinx_copybutton/__init__.py b/sphinx_copybutton/__init__.py
index a1b0edd..af10a84 100644
--- a/sphinx_copybutton/__init__.py
+++ b/sphinx_copybutton/__init__.py
@@ -1,27 +1,40 @@
"""A small sphinx extension to add "copy" buttons to code blocks."""
import os
+from sphinx.util import logging
-__version__ = "0.2.9dev0"
+__version__ = "0.2.11"
+
+logger = logging.getLogger(__name__)
def scb_static_path(app):
static_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '_static'))
app.config.html_static_path.append(static_path)
-def add_skip_text_js(app):
- skip_text = app.config['copybutton_skip_text']
- app.add_js_file(None, body="var copybuttonSkipText = '{}';".format(skip_text))
+def add_to_context(app, config):
+ # Update the global context
+ config.html_context.update({'copybutton_prompt_text': config.copybutton_prompt_text})
+ config.html_context.update({'copybutton_only_copy_prompt_lines': config.copybutton_only_copy_prompt_lines})
+ config.html_context.update({'copybutton_remove_prompts': config.copybutton_remove_prompts})
+ config.html_context.update({'copybutton_image_path': config.copybutton_image_path})
+ config.html_context.update({'copybutton_selector': config.copybutton_selector})
def setup(app):
- print('Adding copy buttons to code blocks...')
+ logger.verbose('Adding copy buttons to code blocks...')
# Add our static path
app.connect('builder-inited', scb_static_path)
- app.connect('builder-inited', add_skip_text_js)
# configuration for this tool
- app.add_config_value("copybutton_skip_text", ">>> ", "html")
+ app.add_config_value("copybutton_prompt_text", "", "html")
+ app.add_config_value("copybutton_only_copy_prompt_lines", True, "html")
+ app.add_config_value("copybutton_remove_prompts", True, "html")
+ app.add_config_value("copybutton_image_path", "copy-button.svg", "html")
+ app.add_config_value("copybutton_selector", "div.highlight pre", "html")
+
+ # Add configuration value to the template
+ app.connect("config-inited", add_to_context)
# Add relevant code to headers
- app.add_stylesheet('copybutton.css')
+ app.add_css_file('copybutton.css')
app.add_js_file('clipboard.min.js')
app.add_js_file("copybutton.js")
return {"version": __version__,
diff --git a/sphinx_copybutton/_static/copybutton.css b/sphinx_copybutton/_static/copybutton.css
index eb70931..75b17a8 100644
--- a/sphinx_copybutton/_static/copybutton.css
+++ b/sphinx_copybutton/_static/copybutton.css
@@ -6,7 +6,9 @@ a.copybtn {
width: 1em;
height: 1em;
opacity: .3;
- transition: opacity 0.5s;
+ transition: opacity 0.5s;
+ border: none;
+ user-select: none;
}
div.highlight {
@@ -15,6 +17,10 @@ div.highlight {
a.copybtn > img {
vertical-align: top;
+ margin: 0;
+ top: 0;
+ left: 0;
+ position: absolute;
}
.highlight:hover .copybtn {
diff --git a/sphinx_copybutton/_static/copybutton.js b/sphinx_copybutton/_static/copybutton.js_t
similarity index 64%
rename from sphinx_copybutton/_static/copybutton.js
rename to sphinx_copybutton/_static/copybutton.js_t
index a5a316a..1c1757d 100644
--- a/sphinx_copybutton/_static/copybutton.js
+++ b/sphinx_copybutton/_static/copybutton.js_t
@@ -64,13 +64,40 @@ const temporarilyChangeTooltip = (el, newText) => {
// should then grab the text and replace pieces of text that shouldn't be used in output
var copyTargetText = (trigger) => {
var target = document.querySelector(trigger.attributes['data-clipboard-target'].value);
- var textContent = target.textContent.split('\n');
- textContent.forEach((line, index) => {
- if (line.startsWith(copybuttonSkipText)) {
- textContent[index] = line.slice(copybuttonSkipText.length)
+ var textContent = target.innerText.split('\n');
+ var copybuttonPromptText = '{{ copybutton_prompt_text }}'; // Inserted from config
+ var onlyCopyPromptLines = {{ copybutton_only_copy_prompt_lines | lower }}; // Inserted from config
+ var removePrompts = {{ copybutton_remove_prompts | lower }}; // Inserted from config
+
+ // Text content line filtering based on prompts (if a prompt text is given)
+ if (copybuttonPromptText.length > 0) {
+ // If only copying prompt lines, remove all lines that don't start w/ prompt
+ if (onlyCopyPromptLines) {
+ linesWithPrompt = textContent.filter((line) => {
+ return line.startsWith(copybuttonPromptText) || (line.length == 0); // Keep newlines
+ });
+ // Check to make sure we have at least one non-empty line
+ var nonEmptyLines = linesWithPrompt.filter((line) => {return line.length > 0});
+ // If we detected lines w/ prompt, then overwrite textContent w/ those lines
+ if ((linesWithPrompt.length > 0) && (nonEmptyLines.length > 0)) {
+ textContent = linesWithPrompt;
+ }
+ }
+ // Remove the starting prompt from any remaining lines
+ if (removePrompts) {
+ textContent.forEach((line, index) => {
+ if (line.startsWith(copybuttonPromptText)) {
+ textContent[index] = line.slice(copybuttonPromptText.length);
+ }
+ });
}
- });
- return textContent.join('\n')
+ }
+ textContent = textContent.join('\n');
+ // Remove a trailing newline to avoid auto-running when pasting
+ if (textContent.endsWith("\n")) {
+ textContent = textContent.slice(0, -1)
+ }
+ return textContent
}
const addCopyButtonToCodeCells = () => {
@@ -82,7 +109,7 @@ const addCopyButtonToCodeCells = () => {
}
// Add copybuttons to all of our code cells
- const codeCells = document.querySelectorAll('div.highlight pre')
+ const codeCells = document.querySelectorAll('{{ copybutton_selector }}')
codeCells.forEach((codeCell, index) => {
const id = codeCellId(index)
codeCell.setAttribute('id', id)
@@ -90,7 +117,7 @@ const addCopyButtonToCodeCells = () => {
const clipboardButton = id =>
`
-
+
`
codeCell.insertAdjacentHTML('afterend', clipboardButton(id))
})