This commit is contained in:
2016-09-10 20:35:42 +02:00
parent 5821f20967
commit d39fa4697a
48 changed files with 1876 additions and 503 deletions

456
scripts/config_gen.py Executable file
View File

@@ -0,0 +1,456 @@
#!/usr/bin/env python2
import sys
import os
import os.path
import re
import argparse
import datetime
import multiprocessing
import shlex
import shutil
import tempfile
import time
import subprocess
import glob
# Default flags for make
default_make_flags = ["-i", "-j" + str(multiprocessing.cpu_count())]
# Set YCM-Generator directory
# Always obtain the real path to the directory where 'config_gen.py' lives as,
# in some cases, it will be a symlink placed in '/usr/bin' (as is the case
# with the Arch Linux AUR package) and it won't
# be able to find the plugin directory.
ycm_generator_dir = os.path.dirname(os.path.realpath(__file__))
def main():
# parse command-line args
parser = argparse.ArgumentParser(description="Automatically generates config files for YouCompleteMe")
parser.add_argument("-v", "--verbose", action="store_true", help="Show output from build process")
parser.add_argument("-m", "--make", default="make", help="Use the specified executable for make.")
parser.add_argument("-c", "--compiler", help="Use the specified executable for clang. It should be the same version as the libclang used by YCM. The executable for clang++ will be inferred from this.")
parser.add_argument("-C", "--configure_opts", default="", help="Additional flags to pass to configure/cmake/etc. e.g. --configure_opts=\"--enable-FEATURE\"")
parser.add_argument("-F", "--format", choices=["ycm", "cc"], default="ycm", help="Format of output file (YouCompleteMe or color_coded). Default: ycm")
parser.add_argument("-M", "--make-flags", help="Flags to pass to make when fake-building. Default: -M=\"{}\"".format(" ".join(default_make_flags)))
parser.add_argument("-o", "--output", help="Save the config file as OUTPUT. Default: .ycm_extra_conf.py, or .color_coded if --format=cc.")
parser.add_argument("-x", "--language", choices=["c", "c++"], help="Only output flags for the given language. This defaults to whichever language has its compiler invoked the most.")
parser.add_argument("--out-of-tree", action="store_true", help="Build autotools projects out-of-tree. This is a no-op for other project types.")
parser.add_argument("--qt-version", choices=["4", "5"], default="5", help="Use the given Qt version for qmake. (Default: 5)")
parser.add_argument("-e", "--preserve-environment", action="store_true", help="Pass environment variables to build processes.")
parser.add_argument("PROJECT_DIR", help="The root directory of the project.")
args = vars(parser.parse_args())
project_dir = os.path.abspath(args["PROJECT_DIR"])
# verify that project_dir exists
if(not os.path.exists(project_dir)):
print("ERROR: '{}' does not exist".format(project_dir))
return 1
# verify the clang is installed, and infer the correct name for both the C and C++ compilers
try:
cc = args["compiler"] or "clang"
args["cc"] = subprocess.check_output(["which", cc]).strip()
except subprocess.CalledProcessError:
print("ERROR: Could not find clang at '{}'. Please make sure it is installed and is either in your path, or specified with --compiler.".format(cc))
return 1
try:
h, t = os.path.split(args["compiler"] or "clang")
cxx = os.path.join(h, t.replace("clang", "clang++"))
args["cxx"] = subprocess.check_output(["which", cxx]).strip()
except subprocess.CalledProcessError:
print("ERROR: Could not find clang++ at '{}'. Please make sure it is installed and specified appropriately.".format(cxx))
return 1
# sanity check - remove this after we add Windows support
if(sys.platform.startswith("win32")):
print("ERROR: Windows is not supported")
# prompt user to overwrite existing file (if necessary)
config_file = {
None: args["output"],
"cc": os.path.join(project_dir, ".color_coded"),
"ycm": os.path.join(project_dir, ".ycm_extra_conf.py"),
}[args["format"] if args["output"] is None else None]
if(os.path.exists(config_file)):
print("'{}' already exists. Overwrite? [y/N] ".format(config_file)),
response = sys.stdin.readline().strip().lower()
if(response != "y" and response != "yes"):
return 1
# command-line args to pass to fake_build() using kwargs
args["make_cmd"] = args.pop("make")
args["configure_opts"] = shlex.split(args["configure_opts"])
args["make_flags"] = default_make_flags if args["make_flags"] is None else shlex.split(args["make_flags"])
force_lang = args.pop("language")
output_format = args.pop("format")
del args["compiler"]
del args["output"]
del args["PROJECT_DIR"]
generate_conf = {
"ycm": generate_ycm_conf,
"cc": generate_cc_conf,
}[output_format]
# temporary files to hold build logs
with tempfile.NamedTemporaryFile(mode="rw") as c_build_log:
with tempfile.NamedTemporaryFile(mode="rw") as cxx_build_log:
# perform the actual compilation of flags
fake_build(project_dir, c_build_log.name, cxx_build_log.name, **args)
(c_count, c_skip, c_flags) = parse_flags(c_build_log)
(cxx_count, cxx_skip, cxx_flags) = parse_flags(cxx_build_log)
print("Collected {} relevant entries for C compilation ({} discarded).".format(c_count, c_skip))
print("Collected {} relevant entries for C++ compilation ({} discarded).".format(cxx_count, cxx_skip))
# select the language to compile for. If -x was used, zero all other options (so we don't need to repeat the error code)
if(force_lang == "c"):
cxx_count = 0
elif(force_lang == "c++"):
c_count = 0
if(c_count == 0 and cxx_count == 0):
print()
print("ERROR: No commands were logged to the build logs (C: {}, C++: {}).".format(c_build_log.name, cxx_build_log.name))
print("Your build system may not be compatible.")
c_build_log.delete = False
cxx_build_log.delete = False
return 3
elif(c_count > cxx_count):
lang, flags = ("c", c_flags)
else:
lang, flags = ("c++", cxx_flags)
generate_conf(["-x", lang] + flags, config_file)
print("Created {} config file with {} {} flags".format(output_format.upper(), len(flags), lang.upper()))
def fake_build(project_dir, c_build_log_path, cxx_build_log_path, verbose, make_cmd, cc, cxx, out_of_tree, configure_opts, make_flags, preserve_environment, qt_version):
'''Builds the project using the fake toolchain, to collect the compiler flags.
project_dir: the directory containing the source files
build_log_path: the file to log commands to
verbose: show the build process output
make_cmd: the path of the make executable
cc: the path of the clang executable
cxx: the path of the clang++ executable
out_of_tree: perform an out-of-tree build (autotools only)
configure_opts: additional flags for configure stage
make_flags: additional flags for make
preserve_environment: pass environment variables to build processes
qt_version: The Qt version to use when building with qmake.
'''
# TODO: add Windows support
assert(not sys.platform.startswith("win32"))
fake_path = os.path.join(ycm_generator_dir, "fake-toolchain", "Unix")
# environment variables and arguments for build process
started = time.time()
FNULL = open(os.devnull, "w")
proc_opts = {} if verbose else {
"stdin": FNULL,
"stdout": FNULL,
"stderr": FNULL
}
proc_opts["cwd"] = project_dir
if(preserve_environment):
env = os.environ
else:
# Preserve HOME, since Cmake needs it to find some packages and it's
# normally there anyway. See #26.
env = dict(map(lambda x: (x, os.environ[x]), ["HOME"]))
env["PATH"] = "{}:{}".format(fake_path, os.environ["PATH"])
env["CC"] = "clang"
env["CXX"] = "clang++"
env["YCM_CONFIG_GEN_CC_LOG"] = c_build_log_path
env["YCM_CONFIG_GEN_CXX_LOG"] = cxx_build_log_path
# used during configuration stage, so that cmake, etc. can verify what the compiler supports
env_config = env.copy()
env_config["YCM_CONFIG_GEN_CC_PASSTHROUGH"] = cc
env_config["YCM_CONFIG_GEN_CXX_PASSTHROUGH"] = cxx
# use -i (ignore errors), since the makefile may include scripts which
# depend upon the existence of various output files
make_args = [make_cmd] + make_flags
# Used for the qmake build system below
pro_files = glob.glob(os.path.join(project_dir, "*.pro"))
# sanity check - make sure the toolchain is available
assert os.path.exists(fake_path), "Could not find toolchain at '{}'".format(fake_path)
# helper function to display exact commands used
def run(cmd, *args, **kwargs):
print("$ " + " ".join(cmd))
subprocess.call(cmd, *args, **kwargs)
# execute the build system
if(os.path.exists(os.path.join(project_dir, "CMakeLists.txt"))):
# cmake
# run cmake in a temporary directory, then compile the project as usual
build_dir = tempfile.mkdtemp()
proc_opts["cwd"] = build_dir
# if the project was built in-tree, we need to hide the cache file so that cmake
# populates the build dir instead of just re-generating the existing files
cache_path = os.path.join(project_dir, "CMakeCache.txt")
if(os.path.exists(cache_path)):
fd, cache_tmp = tempfile.mkstemp()
os.close(fd)
shutil.move(cache_path, cache_tmp)
else:
cache_tmp = None
print("Running cmake in '{}'...".format(build_dir))
run(["cmake", project_dir] + configure_opts, env=env_config, **proc_opts)
print("\nRunning make...")
run(make_args, env=env, **proc_opts)
print("\nCleaning up...")
print("")
shutil.rmtree(build_dir)
if(cache_tmp):
shutil.move(cache_tmp, cache_path)
elif(os.path.exists(os.path.join(project_dir, "configure"))):
# autotools
# perform build in-tree, since not all projects handle out-of-tree builds correctly
if(out_of_tree):
build_dir = tempfile.mkdtemp()
proc_opts["cwd"] = build_dir
print("Configuring autotools in '{}'...".format(build_dir))
else:
print("Configuring autotools...")
run([os.path.join(project_dir, "configure")] + configure_opts, env=env_config, **proc_opts)
print("\nRunning make...")
run(make_args, env=env, **proc_opts)
print("\nCleaning up...")
if(out_of_tree):
print("")
shutil.rmtree(build_dir)
else:
run([make_cmd, "maintainer-clean"], env=env, **proc_opts)
elif(pro_files):
# qmake
# make sure there is only one .pro file
if len(pro_files) != 1:
print("ERROR: Found {} .pro files (expected one): {}.".format(
len(pro_files), ', '.join(pro_files)))
sys.exit(1)
# run qmake in a temporary directory, then compile the project as usual
build_dir = tempfile.mkdtemp()
proc_opts["cwd"] = build_dir
env_config["QT_SELECT"] = qt_version
env_config["QMAKESPEC"] = "unsupported/linux-clang" if qt_version == "4" else "linux-clang"
print("Running qmake in '{}' with Qt {}...".format(build_dir, qt_version))
run(["qmake"] + configure_opts + [pro_files[0]], env=env_config,
**proc_opts)
print("\nRunning make...")
run(make_args, env=env, **proc_opts)
print("\nCleaning up...")
print("")
shutil.rmtree(build_dir)
elif(any([os.path.exists(os.path.join(project_dir, x)) for x in ["GNUmakefile", "makefile", "Makefile"]])):
# make
# needs to be handled last, since other build systems can generate Makefiles
print("Preparing build directory...")
run([make_cmd, "clean"], env=env, **proc_opts)
print("\nRunning make...")
run(make_args, env=env, **proc_opts)
else:
print("ERROR: Unknown build system")
sys.exit(2)
print("Build completed in {} sec".format(round(time.time() - started, 2)))
print("")
def parse_flags(build_log):
'''Creates a list of compiler flags from the build log.
build_log: an iterator of lines
Returns: (line_count, skip_count, flags)
flags is a list, and the counts are integers
'''
# Used to ignore entries which result in temporary files, or don't fully
# compile the file
temp_output = re.compile("(-x assembler)|(-o ([a-zA-Z0-9._].tmp))|(/dev/null)")
skip_count = 0
# Flags we want:
# -includes (-i, -I)
# -defines (-D)
# -warnings (-Werror), but no assembler, etc. flags (-Wa,-option)
# -language (-std=gnu99) and standard library (-nostdlib)
# -word size (-m64)
flags_whitelist = ["-[iID].*", "-W[^,]*", "-std=[a-z0-9+]+", "-(no)?std(lib|inc)", "-m[0-9]+"]
flags_whitelist = re.compile("|".join(map("^{}$".format, flags_whitelist)))
flags = set()
line_count = 0
# macro definitions should be handled separately, so we can resolve duplicates
define_flags = dict()
define_regex = re.compile("-D([a-zA-Z0-9_]+)=(.*)")
# Used to only bundle filenames with applicable arguments
filename_flags = ["-o", "-I", "-isystem", "-include", "-imacros"]
# Process build log
for line in build_log:
if(temp_output.search(line)):
skip_count += 1
continue
line_count += 1
words = split_flags(line)
for (i, word) in enumerate(words):
if(word[0] != '-' or not flags_whitelist.match(word)):
continue
# handle macro definitions
m = define_regex.match(word)
if(m):
if(m.group(1) not in define_flags):
define_flags[m.group(1)] = [m.group(2)]
elif(m.group(2) not in define_flags[m.group(1)]):
define_flags[m.group(1)].append(m.group(2))
continue
# include arguments for this option, if there are any, as a tuple
if(i != len(words) - 1 and word in filename_flags and words[i + 1][0] != '-'):
flags.add((word, words[i + 1]))
else:
flags.add(word)
# Only specify one word size (the largest)
# (Different sizes are used for different files in the linux kernel.)
mRegex = re.compile("^-m[0-9]+$")
word_flags = list([f for f in flags if isinstance(f, basestring) and mRegex.match(f)])
if(len(word_flags) > 1):
for flag in word_flags:
flags.remove(flag)
flags.add(max(word_flags))
# Resolve duplicate macro definitions (always choose the last value for consistency)
for name, values in define_flags.iteritems():
if(len(values) > 1):
print("WARNING: {} distinct definitions of macro {} found".format(len(values), name))
values.sort()
flags.add("-D{}={}".format(name, values[0]))
return (line_count, skip_count, sorted(flags))
def generate_cc_conf(flags, config_file):
'''Generates the .color_coded file
flags: the list of flags
config_file: the path to save the configuration file at'''
with open(config_file, "w") as output:
for flag in flags:
if(isinstance(flag, basestring)):
output.write(flag + "\n")
else: # is tuple
for f in flag:
output.write(f + "\n")
def generate_ycm_conf(flags, config_file):
'''Generates the .ycm_extra_conf.py.
flags: the list of flags
config_file: the path to save the configuration file at'''
template_file = os.path.join(ycm_generator_dir, "template.py")
with open(template_file, "r") as template:
with open(config_file, "w") as output:
output.write("# Generated by YCM Generator at {}\n\n".format(str(datetime.datetime.today())))
for line in template:
if(line == " # INSERT FLAGS HERE\n"):
# insert generated code
for flag in flags:
if(isinstance(flag, basestring)):
output.write(" '{}',\n".format(flag))
else: # is tuple
output.write(" '{}', '{}',\n".format(*flag))
else:
# copy template
output.write(line)
def split_flags(line):
'''Helper method that splits a string into flags.
Flags are space-seperated, except for spaces enclosed in quotes.
Returns a list of flags'''
# Pass 1: split line using whitespace
words = line.strip().split()
# Pass 2: merge words so that the no. of quotes is balanced
res = []
for w in words:
if(len(res) > 0 and unbalanced_quotes(res[-1])):
res[-1] += " " + w
else:
res.append(w)
return res
def unbalanced_quotes(s):
'''Helper method that returns True if the no. of single or double quotes in s is odd.'''
single = 0
double = 0
for c in s:
if(c == "'"):
single += 1
elif(c == '"'):
double += 1
return (single % 2 == 1 or double % 2 == 1)
if(__name__ == "__main__"):
# Note that sys.exit() lets us use None and 0 interchangably
sys.exit(main())

View File

@@ -0,0 +1 @@
true

View File

@@ -0,0 +1 @@
true

14
scripts/fake-toolchain/Unix/cc Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
if [ ! -z "$YCM_CONFIG_GEN_CC_PASSTHROUGH" ]; then
# Cmake determines compiler properties by compiling a test file, so call clang for this case
$YCM_CONFIG_GEN_CC_PASSTHROUGH $@
elif [ "$1" = "-v" ] || [ "$1" = "--version" ]; then
# Needed to enable clang-specific options for certain build systems (e.g. linux)
$YCM_CONFIG_GEN_CC_PASSTHROUGH $@
else
echo "$@" >> $YCM_CONFIG_GEN_CC_LOG
fi

View File

@@ -0,0 +1 @@
cc

View File

@@ -0,0 +1 @@
cxx

14
scripts/fake-toolchain/Unix/cxx Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
if [ ! -z "$YCM_CONFIG_GEN_CC_PASSTHROUGH" ]; then
# Cmake determines compiler properties by compiling a test file, so call clang for this case
$YCM_CONFIG_GEN_CXX_PASSTHROUGH $@
elif [ "$1" = "-v" ] || [ "$1" = "--version" ]; then
# Needed to enable clang-specific options for certain build systems (e.g. linux)
$YCM_CONFIG_GEN_CC_PASSTHROUGH $@
else
echo "$@" >> $YCM_CONFIG_GEN_CXX_LOG
fi

View File

@@ -0,0 +1 @@
cxx

View File

@@ -0,0 +1 @@
cc

View File

@@ -0,0 +1 @@
cxx

View File

@@ -0,0 +1 @@
true

View File

@@ -0,0 +1 @@
true

View File

@@ -0,0 +1,4 @@
#!/bin/sh
# This script is needed because /bin/true does not exist on non-FHS-compliant distros. e.g. NixOS
exit 0

View File

@@ -35,5 +35,4 @@ cat << EOF
$f7 ▄█▄ $rst
$f7▄█████████▄$rst
$f7▀▀▀▀▀▀▀▀▀▀▀$rst
EOF

134
scripts/template.py Normal file
View File

@@ -0,0 +1,134 @@
# This file is NOT licensed under the GPLv3, which is the license for the rest
# of YouCompleteMe.
#
# Here's the license text for this file:
#
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# 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 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.
#
# For more information, please refer to <http://unlicense.org/>
import os
import ycm_core
flags = [
# INSERT FLAGS HERE
]
# Set this to the absolute path to the folder (NOT the file!) containing the
# compile_commands.json file to use that instead of 'flags'. See here for
# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html
#
# You can get CMake to generate this file for you by adding:
# set( CMAKE_EXPORT_COMPILE_COMMANDS 1 )
# to your CMakeLists.txt file.
#
# Most projects will NOT need to set this to anything; you can just change the
# 'flags' list of compilation flags. Notice that YCM itself uses that approach.
compilation_database_folder = ''
if os.path.exists( compilation_database_folder ):
database = ycm_core.CompilationDatabase( compilation_database_folder )
else:
database = None
SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ]
def DirectoryOfThisScript():
return os.path.dirname( os.path.abspath( __file__ ) )
def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
if not working_directory:
return list( flags )
new_flags = []
make_next_absolute = False
path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
for flag in flags:
new_flag = flag
if make_next_absolute:
make_next_absolute = False
if not flag.startswith( '/' ):
new_flag = os.path.join( working_directory, flag )
for path_flag in path_flags:
if flag == path_flag:
make_next_absolute = True
break
if flag.startswith( path_flag ):
path = flag[ len( path_flag ): ]
new_flag = path_flag + os.path.join( working_directory, path )
break
if new_flag:
new_flags.append( new_flag )
return new_flags
def IsHeaderFile( filename ):
extension = os.path.splitext( filename )[ 1 ]
return extension in [ '.h', '.hxx', '.hpp', '.hh' ]
def GetCompilationInfoForFile( filename ):
# The compilation_commands.json file generated by CMake does not have entries
# for header files. So we do our best by asking the db for flags for a
# corresponding source file, if any. If one exists, the flags for that file
# should be good enough.
if IsHeaderFile( filename ):
basename = os.path.splitext( filename )[ 0 ]
for extension in SOURCE_EXTENSIONS:
replacement_file = basename + extension
if os.path.exists( replacement_file ):
compilation_info = database.GetCompilationInfoForFile(
replacement_file )
if compilation_info.compiler_flags_:
return compilation_info
return None
return database.GetCompilationInfoForFile( filename )
def FlagsForFile( filename, **kwargs ):
if database:
# Bear in mind that compilation_info.compiler_flags_ does NOT return a
# python list, but a "list-like" StringVec object
compilation_info = GetCompilationInfoForFile( filename )
if not compilation_info:
return None
final_flags = MakeRelativePathsInFlagsAbsolute(
compilation_info.compiler_flags_,
compilation_info.compiler_working_dir_ )
else:
relative_to = DirectoryOfThisScript()
final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )
return {
'flags': final_flags,
'do_cache': True
}

206
scripts/watchfile Executable file
View File

@@ -0,0 +1,206 @@
#!/bin/bash
version=1.0.1
versionDate="2014-02-14"
function showHelp() {
echo "watchfile - monitor file(s)/command and perform action when changed
Possible ways of usage
----------------------------------------
Monitor FILE, when modified execute FILE
\$ watchfile [options] FILE
Monitor FILE, when modified execute CMND [PARAM_1 ..PARAM_N]
\$ watchfile [options] FILE CMND [PARAM_1 .. PARAM_N]
Monitor FILE_1 .. FILE_N, when modified execute CMND [PARAM_1 ..PARAM_N]
\$ watchfile [options] -i FILE_1 .. FILE_N -e CMND [PARAM_1 .. PARAM_N]
Monitor output of CMND1, when modified execute CMND2 [PARAM_1 .. PARAM_N]
\$ watchfile [options] -s \"CMND1\" -e CMND [PARAM_1 .. PARAM_N]
options:
-h, --help Show help
-d, --delay=N Specify the delay between each monitor update. Default 0.5.
--check-content If set it checks file content, instead of just the timestamp.
Has no effect with the -s flag set.
--no-clear If set, it doesn't clear the screen before executing CMND.
-v, --version Outputs version information
flags:
-s, Next argument specifies monitor command. Requires -e flag.
-i, Start listing files to monitor. Requires -e flag.
-e, Start listing command to execute. Requires -s or -i or flag.
Must be the last flag used (CMND can thus use flags as parameters)
Note: If CMND isn't found, and ./CMND is, it automatically uses this command.
Note: If the command uses ampersands (&, &&), these must be escaped (\&, \&\&).
Examples
----------------------------------------
Monitor executable foo.sh, and execute on change
$ watchfile foo.sh
Monitor python file foo.py, and execute it on change
$ watchfile foo.py python foo.py
As above, but monitor content (not just timestamp):
$ watchfile --check-content foo.py python foo.py
Compiling main.cpp file on change:
$ watchfile main.cpp g++ -Wall main.cpp -o main
Compiling main.cpp file on change, running when compilation succeedes:
$ watchfile main.cpp g++ -Wall main.cpp -o main \&\& ./main
Compiling project whenever source files changes, and running if it succeedes:
$ watchfile -s \"find . -name '*.cpp' -or -name '*.h' | xargs cat\" \\
-e make \&\& ./main
See: http://swarminglogic.com/jotting/2014_02_watchfile for more examples
Mainted at: https://gist.github.com/swarminglogic/8963507
Author: Roald Fernandez (github@swarminglogic.com)
Version: $version ($versionDate)
License: CC-zero (public domain)
"
exit $1
}
function parseParameters() {
tmp=$@
leftovers=""
while test $# -gt 0; do
case "$1" in
-h|--help)
showHelp 0
;;
--no-clear)
shift
flagNoClear=true
;;
--check-content)
shift
flagCheckContent=true
;;
-d)
shift
delay=$1
shift
;;
--delay*)
delay=`echo $1 | sed -e 's/^[^=]*=//g'`
shift
;;
-v|--version)
shift
echo "watchfile $version"
exit 0
;;
-s)
shift
flagS=true
watchcmnd=$1
shift
;;
-i)
shift
flagI=true
nI=0
for i in `seq 1 $#`; do
if [[ ${!i} == -* ]] ; then
break;
else
((++nI))
fi
done
watchfiles=${@:1:$nI}
shift $nI
;;
-e)
shift
flagE=true
execcmnd=${@:1}
break
;;
-*)
leftovers="$leftovers "$1
shift
;;
*)
leftovers="$leftovers "$1
shift
;;
esac
done
if [[ $flagE && (! $flagS) && (! $flagI) ]] ; then
echo "Error: If -e flag is set, -s or -i flags are required."
exit 1
elif [[ ($flagS || $flagI) && ! $flagE ]] ; then
echo "Error: If -s or -i flags are set, the -e flags is required."
exit 1
elif [[ $flagS && $flagI ]]; then
echo "Error: Both -s and -i flags cannot be used simultaneously."
exit 1
elif [[ (! $flagE) && (! $flagS) && (! $flagI) ]] ; then
set -- $leftovers
watchfiles=$1
if [ $# -gt 1 ]; then
execcmnd=${@:2}
else
execcmnd=$watchfiles
fi
fi
}
# Exit with help if no parameters
if [[ ! $@ ]] ; then showHelp 1; fi
# Defaults
delay=0.5
# Parse parameters into $watch and $execcmnd variables
parseParameters "$@"
# Sanitize executable
set -- $execcmnd
if [[ ! `which $1` ]] && [[ -x ./$1 ]] ; then
execcmnd=./$execcmnd
elif [[ ! `which $1` ]] && [[ ! -x ./$1 ]] ; then
echo "Error: No executable $1 or ./$1 found"
exit 1
fi
# Main monitoring loop.
if [[ -z $watchcmnd ]] ; then
if [[ ! $flagCheckContent ]] ; then
watchcmnd="stat -c %Y $watchfiles | md5sum"
else
watchcmnd="cat $watchfiles | md5sum"
fi
else
watchcmnd="$watchcmnd | md5sum"
fi
md5sum=`eval $watchcmnd`
md5sumNow=$md5sum
while [[ true ]]
do
# Loop until some files have changed
while [[ "$md5sumNow" = "$md5sum" ]]
do
sleep $delay
md5sumNow=`eval $watchcmnd`
done
# Execute the file, as it has changed.
if [[ ! $flagNoClear ]] ; then
clear
fi
eval $execcmnd
md5sum=$md5sumNow
done