Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for conditional blocks in README.rst #6901

Closed
wants to merge 10 commits into from
62 changes: 62 additions & 0 deletions dev_tools/file_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright 2024 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Helper utilities for working with text files."""


def read_file_filtered(filename, begin_skip, end_skip):
"""Return lines from a file, skipping lines between markers.

The strings `begin_skip` and `end_skip` indicate the start & end markers.
They must be alone on separate lines of the input. Testing is done with
str.startwith(...), which allows `begin_skip` and `end_skip` to be only
the first parts of the markers. Matching is case-sensitive.

Important: the start & end markers must not be nested.

Args:
filename: the name or path string of the file to read and process.
begin_skip: beginning text of a line indicating start of skipping.
end_skip: beginning text of a line indicating end of skipping.
Returns:
the contents of `filename` as a string, minus lines bracketed by
`begin_stip` and `end_skip`, inclusive.
Raises:
ValueError: if the file contains unbalanced markers.
"""

with open(filename, encoding='utf-8') as input_file:
file_lines = input_file.readlines()

skip = False
content = ''
for line_num, line in enumerate(file_lines, start=1):
if line.startswith(begin_skip):
if skip:
raise ValueError(
f"[Line {line_num}] Encountered '{begin_skip}' while already skipping."
)
skip = True
elif line.startswith(end_skip):
if not skip:
raise ValueError(
f"[Line {line_num}] Encountered '{end_skip}'"
f" without a matching '{begin_skip}'."
)
skip = False
elif not skip:
content += line
if skip:
raise ValueError(f"Missing final {end_skip}.")
return content
107 changes: 107 additions & 0 deletions dev_tools/file_tools_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Copyright 2024 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import tempfile

import pytest

from dev_tools.file_tools import read_file_filtered

START_SKIP = '.. ▶︎─── start github-only'
END_SKIP = '.. ▶︎─── end github-only'


def output_from_read_file_filtered(content):
"""Call `read_file_filtered` using a temp file to store `content`."""
# On Windows, can't read from a temp file while it's open, so we can't use
# the context handler "with tempfile...".
tf = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False)
tf.write(content)
tf.close()
ex_raised = None
output = ''
try:
output = read_file_filtered(tf.name, START_SKIP, END_SKIP)
except Exception as ex:
os.unlink(tf.name)
ex_raised = ex
if ex_raised:
raise ex_raised
return output


def test_valid():
content = '''Cirq is great.

.. ▶︎─── start github-only
.. raw:: html

<p>
.. ▶︎─── end github-only

Cirq is still great.
'''
result = output_from_read_file_filtered(content)
assert result == 'Cirq is great.\n\n\nCirq is still great.\n'


def test_valid_multiple_regions():
content = '''Cirq is great.

.. ▶︎─── start github-only
.. raw:: html

<p>
.. ▶︎─── end github-only

Cirq is still great.

.. ▶︎─── start github-only --- some text here ---
.. raw:: html

<p>
.. ▶︎─── end github-only --- some more random text here ---
'''
result = output_from_read_file_filtered(content)
assert result == 'Cirq is great.\n\n\nCirq is still great.\n\n'


def test_exception_missing_begin():
content = '''Cirq is great.
.. ▶︎─── end github-only
Cirq is still great.
'''
with pytest.raises(Exception, match=r'^\[.*?\] Encountered .* without'):
_ = output_from_read_file_filtered(content)


def test_exception_two_begins():
content = '''Cirq is great.
.. ▶︎─── start github-only
Cirq is still great.
.. ▶︎─── start github-only
Yup, still great.
'''
with pytest.raises(Exception, match=r'while already skipping'):
_ = output_from_read_file_filtered(content)


def test_exception_missing_end():
content = '''Cirq is great.
.. ▶︎─── start github-only
Cirq is still great.
'''
with pytest.raises(Exception, match=r'^Missing final '):
_ = output_from_read_file_filtered(content)
17 changes: 10 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import io
import os
from setuptools import setup

# This reads the __version__ variable from cirq/_version.py
__version__ = ''

from dev_tools import modules
from dev_tools.file_tools import read_file_filtered
from dev_tools.requirements import explode

exec(open('cirq-core/cirq/_version.py').read())
Expand All @@ -28,16 +28,19 @@

description = (
'A framework for creating, editing, and invoking '
'Noisy Intermediate Scale Quantum (NISQ) circuits.'
'Noisy Intermediate-Scale Quantum (NISQ) circuits.'
)

# README file as long_description.
long_description = io.open('README.rst', encoding='utf-8').read()
# Read README.rst for the long_description, skipping parts that contain rST
# constructs that are disallowed by PyPI.
long_description = read_file_filtered(
"README.rst", ".. ▶︎─── start github-only", ".. ▶︎─── end github-only"
)

# If CIRQ_PRE_RELEASE_VERSION is set then we update the version to this value.
# It is assumed that it ends with one of `.devN`, `.aN`, `.bN`, `.rcN` and hence
# it will be a pre-release version on PyPi. See
# https://packaging.python.org/guides/distributing-packages-using-setuptools/#pre-release-versioning
# The value is assumed to end with one of `.devN`, `.aN`, `.bN`, `.rcN`, which
# means this will be a pre-release version put on PyPI. See
# https://packaging.python.org/specifications/version-specifiers/#pre-releases
# for more details.
if 'CIRQ_PRE_RELEASE_VERSION' in os.environ:
__version__ = os.environ['CIRQ_PRE_RELEASE_VERSION']
Expand Down