Skip to content

Commit

Permalink
Handle binary files and utf8
Browse files Browse the repository at this point in the history
 * Binary files are now explicitly detected if we're unable to read them
   as utf-8 text files. In this case the last file will be used.

 * As a side affect utf-8 related things are now correctly handled
   across python 2/3.

Thank you to @unbrice as this work was based off of GH-25.
  • Loading branch information
evanpurkhiser committed Dec 26, 2015
1 parent aff2a77 commit f08e714
Showing 1 changed file with 34 additions and 14 deletions.
48 changes: 34 additions & 14 deletions bin/dots
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@

from __future__ import print_function

import codecs
import sys
import os
import shutil
Expand Down Expand Up @@ -337,6 +338,7 @@ class ConfigFile(object):
self.path = relative_path
self.config = config
self.compiled = None
self.compiled_binary = None

def name(self):
"""Get the name of the file"""
Expand Down Expand Up @@ -418,11 +420,21 @@ class ConfigFile(object):
self.compiled = None
compiling_files = []

binary = False

# Read all file paths into arrays
# This may be memory inefficient and may have to be changed later
for path in self.real_paths():
with open(path, 'r') as file:
contents = file.readlines()
with open(path, 'rb') as file:
contents_binary = file.read()

# Attempt to read the lines as uft-8 strings. Files content that
# can't be decoded as utf-8 will be considered a binary file.
try:
contents = codecs.decode(contents_binary, 'utf-8').splitlines(True)
except UnicodeDecodeError:
binary = True
contents = ["<binary file: {}>\n".format(path)]

# Replace front and back empty lines with None
contents = self.trim_file_whitespace(contents)
Expand All @@ -442,6 +454,15 @@ class ConfigFile(object):
self.compiled_file = ""
return self


# If we have multiple binary files use the file in the last group. This
# will be the last file read into contents_binary
if binary:
self.compiled = ''.join(contents)
self.compiled_binary = contents_binary

return self

compiled = compiling_files[0]

# Handle merging the file_data into one file. This will look for default
Expand Down Expand Up @@ -470,18 +491,19 @@ class ConfigFile(object):

# Remove unused append point identifiers
self.compiled = [l for l in compiled if not l.strip().startswith(AP_IDENTIFIER)]
self.compiled_binary = ''.join(self.compiled).encode('utf-8')

return self

def diff(self, git_args=[], against=INSTALL_DIR):
"""Display a diff of the compiled file contest against the currently
installed version of this file. This uses git as an external command to
do the diffing"""
if self.compiled is None: self.compile()
if self.compiled_binary is None: self.compile()

# Create a named temporary file to allow git to diff
with tempfile.NamedTemporaryFile('w+') as file:
file.writelines(self.compiled)
with tempfile.NamedTemporaryFile('wb+') as file:
file.write(self.compiled_binary)
file.flush()

git_diff(git_args, os.path.join(against, self.path), file.name)
Expand All @@ -507,7 +529,7 @@ class ConfigFile(object):

def install(self, to, exec_install_scripts=False):
"""Install this file to a location"""
if not self.compiled: self.compile()
if not self.compiled_binary: self.compile()

# Ensure directory exists for file
path = os.path.join(to, self.path)
Expand All @@ -518,19 +540,17 @@ class ConfigFile(object):

# Test that the file hasn't changed
if os.path.exists(path):
compiled = ''.join(self.compiled)
compiled = hashlib.md5(compiled).digest()
compiled_hash = hashlib.md5(self.compiled_binary).digest()

with open(path, 'r') as installed_file:
installed = installed_file.read()
installed = hashlib.md5(installed).digest()
with open(path, 'rb') as installed_file:
installed_hash = hashlib.md5(installed_file.read()).digest()

if installed == compiled and os.stat(path).st_mode == self.mode():
if installed_hash == compiled_hash and os.stat(path).st_mode == self.mode():
return

# Write the file
with open(path, 'w') as file:
file.writelines(self.compiled)
with open(path, 'wb') as file:
file.write(self.compiled_binary)

# Set the file's mode
os.chmod(path, self.mode())
Expand Down

0 comments on commit f08e714

Please sign in to comment.