Skip to content

Commit

Permalink
Rework pyTransition README to intregrate actual example script
Browse files Browse the repository at this point in the history
We add a python script (code-to-readme.py, created by Claude.AI) which will replace
marked script sections with actual content of scripts.

This is driven by a configuration files, readme-examples-config.yaml which define
name of script file and section of the script to extract.

We need to run the following to update the README file:
python3 code-to-readme.py --readme README.md --config readme-examples-config.yaml

Documentation about this script usage and the config file format is in the script header

We extracted 4 scripts from the previous README and put them in the examples directory.
Some fixes were applied, but part of the scripts are still broken, we will fix in further
commit.
  • Loading branch information
greenscientist committed Dec 19, 2024
1 parent f1e84f0 commit 5dbfb10
Show file tree
Hide file tree
Showing 8 changed files with 455 additions and 21 deletions.
42 changes: 21 additions & 21 deletions pyTransition/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ This method allows users to send accessibility map parameters to the Transition

## Example
Users can fetch the nodes which are currently loaded in the Transition application using pyTransition as follows :
<!-- BEGIN basic_usage -->
```python
from pyTransition.transition import Transition

Expand All @@ -193,43 +194,46 @@ def get_transition_nodes():
# Process nodes however you want. Here, we are just printing the result
print(nodes)
```
<!-- END basic_usage -->

Fetching the paths can be done in the same way, by replacing *get_nodes()* with *get_paths()*.

Alternatively, if the user already knows their Transition API authentication token, they can create the instance with it directly, as follows :
<!-- BEGIN basic_usage_token -->
```python
from pyTransition.transition import Transition

def get_transition_nodes():
# Alternative version with token
def get_transition_nodes_with_token():
# Create Transition instance from authentication token
# The login information can be saved in a file to not have them displayed in the code
transition_instance = Transition("http://localhost:8080", None, None, token)

# Call the API
nodes = Transition.get_nodes()
nodes = transition_instance.get_nodes()

# Process nodes however you want. Here, we are just printing the result
print(nodes)
```
<!-- END basic_usage_token -->

Another example using pyTransition to get a new accessibility map :
<!-- BEGIN accessibility_map -->
```python
from pyTransition.transition import Transition
from datetime import time
import json

def get_transition_acessibility_map():
# Create Transition instance from connection credentials
transition_instance = Transition("http://localhost:8080", username, password)

# Get the scenarios. A scenario is needed to request an accessibility map
scenarios = transition_instance.get_scenarios()

print(scenarios)
# Get the ID of the scenario we want to use. Here, we use the first one
scenario_id = scenarios['collection'][0]['id']
scenario_id = scenarios[0]['id']

# Call the API
accessibility_map_data = transition_instance.request_accessibility_map(
coordinates=[45.5383, -73.4727],
coordinates=[-73.4727, 45.5383],
departure_or_arrival_choice="Departure",
departure_or_arrival_time=time(8,0), # Create a new time object representing 8:00
n_polygons=3,
Expand All @@ -248,15 +252,12 @@ def get_transition_acessibility_map():
# Process the map however you want. Here, we are saving it to a json file
with open("accessibility.json", 'w') as f:
f.write(json.dumps(accessibility_map_data))

```
<!-- END accessibility_map -->

Another example using pyTransition to get a new routes :
<!-- BEGIN routing -->
```python
from pyTransition.transition import Transition
from datetime import time
import json

def get_transition_routes():
# Create Transition instance from connection credentials
# The login information can be saved in a file to not have them displayed in the code
Expand All @@ -268,33 +269,33 @@ def get_transition_routes():
routing_modes = transition_instance.get_routing_modes()

# Get the ID of the scenario we want to use. Here, we use the first one
scenario_id = scenarios['collection'][0]['id']
# Get the modes you want to use. Here, we are usisng the first two ones
scenario_id = scenarios[0]['id']
# Get the modes you want to use. Here, we are using the first two ones
# You can print the modes to see which are available
modes = routing_modes[:2]


# Call the API
routing_data = transition_instance.request_routing_result(
modes=modes,
origin=[-73.4727, 45.5383],
destination=[-73.4499, 45.5176],
scenario_id=scenarioId,
scenario_id=scenario_id,
departure_or_arrival_choice=departureOrArrivalChoice,
departure_or_arrival_time=departureOrArrivalTime,
max_travel_time_minutes=maxParcoursTime,
min_waiting_time_minutes=minWaitTime,
max_transfer_time_minutes=maxTransferWaitTime,
max_access_time_minutes=maxAccessTimeOrigDest,
max_first_waiting_time_minutes=maxWaitTimeFisrstStopChoice,
max_first_waiting_time_minutes=maxWaitTimeFirstStopChoice,
with_geojson=True,
with_alternatives=True
)

# Process the data however you want.
# For example, we can get the geojson paths of each transit mode in a loop
# For each alternative, get the geojson associated
for key, value in routing_data.items():
# Get the number of alternative paths for the current mode
print(f"KEY:{key} VAL{value}")
geojsonPaths = value["pathsGeojson"]
mode = key
# For each alternative, get the geojson associated
Expand All @@ -306,6 +307,5 @@ def get_transition_routes():
# We can also save it to a json file
with open("routing.json", 'w') as f:
f.write(json.dumps(routing_data))


```
<!-- END routing -->
254 changes: 254 additions & 0 deletions pyTransition/code-to-readme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#!/usr/bin/env python3

# MIT License

# Copyright (c) 2024 Polytechnique Montréal

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""
Code to README Integration Tool
==============================
(This was done with claude.ai)
This script helps maintain code examples in README files by automatically extracting
and inserting code snippets from source files. It uses markers in both the README
and source files to determine where code should be inserted.
Usage
-----
```bash
python code_to_readme.py --readme README.md --config examples.yaml
```
Configuration File (examples.yaml)
--------------------------------
The configuration file should be in YAML format and specify which code blocks
should be inserted where. Example structure:
```yaml
examples:
- block_id: basic_example # Unique identifier for this code block
path: src/example.py # Path to the source file
markers: # Optional: if not provided, includes entire file
- start: "# START basic" # Start marker in source file
end: "# END basic" # End marker in source file
include_markers: false # Whether to include markers in output
language: python # Optional: override language detection
- block_id: full_file # Example without markers
path: src/util.py # Will include the entire file
```
README Format
------------
In your README.md, place markers where you want code to be inserted:
```markdown
<!-- BEGIN basic_example -->
```python
# Code will be automatically inserted here
```
<!-- END basic_example -->
```
Source File Format
-----------------
In your source files, mark the code sections you want to extract (if using markers):
```python
# Other code...
# START basic
def example():
print("This will be extracted")
# END basic
# More code...
```
Features
--------
- Extract whole files or specific marked sections
- Optional inclusion of marker comments
- Language auto-detection based on file extension
- Multiple code blocks per file
- Preserve README formatting outside of marked sections
"""

import re
from pathlib import Path
import argparse
import yaml
from dataclasses import dataclass
from typing import List, Dict, Optional, Tuple

@dataclass
class MarkerPair:
start: str
end: str
include_markers: bool = False

@dataclass
class CodeExample:
path: str
block_id: str
markers: Optional[List[MarkerPair]] = None
language: Optional[str] = None

class CodeToReadmeIntegrator:
def __init__(self, readme_path: str):
self.readme_path = Path(readme_path)

@staticmethod
def load_config(config_path: str) -> Dict[str, CodeExample]:
"""Load example files configuration from YAML file."""
with open(config_path, 'r') as f:
config = yaml.safe_load(f)

examples = {}
for item in config['examples']:
# Convert markers configuration to MarkerPair objects
markers = None
if 'markers' in item:
markers = [
MarkerPair(
start=m['start'],
end=m['end'],
include_markers=m.get('include_markers', False)
) for m in item['markers']
]

examples[item['block_id']] = CodeExample(
path=item['path'],
block_id=item['block_id'],
markers=markers,
language=item.get('language')
)
return examples

def detect_language(self, file_path: str, override: Optional[str] = None) -> str:
"""Detect language based on file extension or override."""
if override:
return override

extension_map = {
'.py': 'python',
'.js': 'javascript',
'.ts': 'typescript',
'.sh': 'bash',
'.java': 'java',
'.rb': 'ruby',
'.go': 'go',
'.rs': 'rust',
'.cpp': 'cpp',
'.c': 'c',
}
return extension_map.get(Path(file_path).suffix, 'text')

def extract_code(self, content: str, markers: Optional[List[MarkerPair]]) -> str:
"""Extract code between markers or return full content."""
if not markers:
return content.strip()

extracted = []
lines = content.split('\n')
capturing = False
current_snippet = []
current_marker = None

for line in lines:
# Check for start markers
for marker in markers:
if marker.start in line:
capturing = True
current_marker = marker
if marker.include_markers:
current_snippet.append(line)
break
elif marker.end in line:
if marker == current_marker:
if marker.include_markers:
current_snippet.append(line)
capturing = False
current_marker = None
if current_snippet:
extracted.append('\n'.join(current_snippet))
current_snippet = []
break
else: # no marker found in this line
if capturing:
current_snippet.append(line)

return '\n\n'.join(extracted).strip()

def update_readme(self, examples: Dict[str, CodeExample]) -> None:
"""Update README file with code examples."""
with open(self.readme_path, 'r') as f:
content = f.read()

for block_id, example in examples.items():
# Pattern to match content between markers including the markers
pattern = f"(<!-- BEGIN {block_id} -->).*(<!-- END {block_id} -->)"

try:
# Read the source file
with open(example.path, 'r') as f:
code = f.read()

# Extract relevant code if markers are specified
code = self.extract_code(code, example.markers)

# Detect language
language = self.detect_language(example.path, example.language)

# Create the replacement block
replacement = f"""<!-- BEGIN {block_id} -->
```{language}
{code}
```
<!-- END {block_id} -->"""

# Replace in README
content = re.sub(pattern, replacement, content, flags=re.DOTALL)

except FileNotFoundError:
print(f"Warning: Source file not found: {example.path}")
except Exception as e:
print(f"Error processing block {block_id}: {str(e)}")

# Write updated content
with open(self.readme_path, 'w') as f:
f.write(content)

def main():
parser = argparse.ArgumentParser(description='Integrate code files into README')
parser.add_argument('--readme', required=True, help='Path to README.md')
parser.add_argument('--config', required=True, help='Path to examples configuration YAML')

args = parser.parse_args()

integrator = CodeToReadmeIntegrator(args.readme)
examples = integrator.load_config(args.config)
integrator.update_readme(examples)

if __name__ == "__main__":
main()
9 changes: 9 additions & 0 deletions pyTransition/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Running the examples

To run the examples, first edit the test_credentials.py file with the right
username and password for your setup.

Then you ran run them while setting the PYTHONPATH:
```
PYTHONPATH=.. python3 routing.py
```
Loading

0 comments on commit 5dbfb10

Please sign in to comment.