Last active
July 20, 2017 03:21
-
-
Save caleb531/a9edf5d863600b28244b2c1bfccf2b55 to your computer and use it in GitHub Desktop.
Easily open the local Apache virtual host for the current directory in your default browser; written for MAMP on macOS but can be applied to any local Apache installation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import os | |
import os.path | |
import re | |
import sys | |
# Retrieve the name/args of a directive like DocumentRoot or <VirtualHost> | |
def get_vhost_directive(vhost_line): | |
matches = re.findall( | |
r'(\w+) ([\'\"]?)([^\'\"\s]+)\2(?: ([\'\"]?)([^\'\"\s]+)\4)*', | |
vhost_line) | |
if matches: | |
# Return directive name and arguments (excluding string delimeters) | |
return matches[0][0], tuple( | |
match for m, match in enumerate(matches[0][1:]) | |
if m % 2 == 1 and match != '') | |
else: | |
return None, None | |
# Get the HTTP port (80 or 443) for the given VirtualHost addr/port | |
def get_vhost_port(directive_args): | |
matches = re.search(r':(\d+)', directive_args[0]) | |
if matches: | |
return int(matches.group(1)) | |
else: | |
return 80 | |
# Get the protocol (http or https) for the given port number | |
def get_vhost_protocol(directive_args): | |
vhost_port = get_vhost_port(directive_args) | |
if vhost_port == 443: | |
return 'https' | |
else: | |
return 'http' | |
# Return a generator which iterates over vhost definitions in the given file | |
def get_vhost_defs(vhost_config_file): | |
for vhost_line in vhost_config_file: | |
directive_name, directive_args = get_vhost_directive(vhost_line) | |
if directive_name == 'VirtualHost': | |
vhost_protocol = get_vhost_protocol(directive_args) | |
elif directive_name == 'DocumentRoot': | |
vhost_doc_root = directive_args[0] | |
elif directive_name == 'ServerName': | |
vhost_server_name = directive_args[0] | |
elif directive_name == 'Alias': | |
vhost_alias_path = directive_args[0] | |
vhost_alias_doc_root = directive_args[1] | |
# Every alias is another document root to consider | |
yield { | |
'doc_root': vhost_alias_doc_root, | |
'server_name': vhost_server_name + vhost_alias_path, | |
'protocol': vhost_protocol | |
} | |
elif '</VirtualHost>' in vhost_line: | |
yield { | |
'doc_root': vhost_doc_root, | |
'server_name': vhost_server_name, | |
'protocol': vhost_protocol | |
} | |
vhost_protocol = None | |
vhost_alias_path = None | |
vhost_alias_doc_root = None | |
vhost_server_name = None | |
# A key function for sorting vhost defs, listing vhosts with longer document | |
# roots first, and listing HTTPS vhosts before HTTP vhosts (for document roots | |
# of the same length) | |
def sort_vhost_defs(vhost_def): | |
return (len(vhost_def['doc_root']), vhost_def['protocol']) | |
# Retrieves a list of vhosts matching the given directory | |
def get_dir_vhost_def(vhost_config_path, doc_root): | |
matching_vhost_defs = [] | |
with open(vhost_config_path, 'r') as vhost_config_file: | |
for vhost_def in get_vhost_defs(vhost_config_file): | |
# Find every vhost whose document root contains the given directory | |
if doc_root.startswith(vhost_def['doc_root']): | |
matching_vhost_defs.append(vhost_def) | |
if matching_vhost_defs: | |
# If two directories are ancestors of the target directory, the | |
# closer/deeper ancestor is returned | |
return max(matching_vhost_defs, key=sort_vhost_defs) | |
else: | |
raise RuntimeError('virtual host not found ({})'.format(doc_root)) | |
# Retrieve the directory where Jekyll builds the project | |
def get_jekyll_build_dir(doc_root): | |
with open('_config.yml', 'r') as jekyll_config_file: | |
jekyll_config = jekyll_config_file.read() | |
build_dir_matches = re.search( | |
r'([\'\"]?)destination\1:\s*([\'\"]?)(.+)\2', | |
jekyll_config) | |
if build_dir_matches: | |
build_dir = build_dir_matches.group(3) | |
print(build_dir) | |
else: | |
build_dir = '_site' | |
return os.path.join(doc_root, build_dir) | |
# Retrieve the directory where Brunch builds this project | |
def get_brunch_build_dir(doc_root): | |
with open('brunch-config.js', 'r') as brunch_config_file: | |
brunch_config = brunch_config_file.read() | |
build_dir_matches = re.search( | |
r'([\'\"]?)public\1:\s*([\'\"])(.+?)\2', | |
brunch_config) | |
if build_dir_matches: | |
build_dir = build_dir_matches.group(3) | |
else: | |
build_dir = 'public' | |
return os.path.join(doc_root, build_dir) | |
# Get the most appropriate document root from the current directory (the | |
# document root may be the current directory, a subdirectory or a parent | |
# directory depending on the type of project) | |
def get_doc_root(): | |
doc_root = os.getcwd() | |
# For Jekyll projects, assume document root is _site/ | |
if os.path.exists(os.path.join(doc_root, '_config.yml')): | |
doc_root = get_jekyll_build_dir(doc_root) | |
if not os.path.exists(doc_root): | |
raise RuntimeError('jekyll site directory does not exist') | |
# For Brunch projects, assume document root is public/ | |
elif os.path.exists(os.path.join(doc_root, 'brunch-config.js')): | |
doc_root = get_brunch_build_dir(doc_root) | |
if not os.path.exists(doc_root): | |
raise RuntimeError('brunch site directory does not exist') | |
return doc_root | |
# Retrieve URL for current working directory | |
def get_cwd_url(vhost_config_path): | |
doc_root = get_doc_root() | |
# Read directory's document root and server name from vhost configuration | |
dir_vhost_def = get_dir_vhost_def(vhost_config_path, doc_root) | |
cwd_url_path = doc_root[len(dir_vhost_def['doc_root']):] | |
cwd_url = '{protocol}://{hostname}{path}/'.format( | |
protocol=dir_vhost_def['protocol'], | |
hostname=dir_vhost_def['server_name'], | |
path=cwd_url_path) | |
return cwd_url | |
def main(): | |
mamp_path = os.path.join(os.sep, 'Applications', 'MAMP') | |
vhost_config_path = os.path.join( | |
mamp_path, 'conf', 'apache', | |
'extra', 'httpd-vhosts.conf') | |
try: | |
print(get_cwd_url(vhost_config_path), end='') | |
except RuntimeError as error: | |
# Gracefully exit program if ^C is invoked | |
print(error, end='', file=sys.stderr) | |
if __name__ == '__main__': | |
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Open the virtual host for the current directory in the default web browser | |
# Add this to your .bash_profile on macOS (.bashrc for Linux) | |
vho() { | |
local vhost_url="$("$(dirname "${BASH_SOURCE[0]}")"/get_vhost_url.py)" | |
echo "$vhost_url" | |
if [ -n "$vhost_url" ]; then | |
open "$vhost_url" | |
fi | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment