Skip to content

Instantly share code, notes, and snippets.

@caleb531
Last active July 20, 2017 03:21
Show Gist options
  • Save caleb531/a9edf5d863600b28244b2c1bfccf2b55 to your computer and use it in GitHub Desktop.
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
#!/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()
#!/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