Compare commits

..

7 Commits

Author SHA1 Message Date
David Adam
1e68508b0c Authenticate connections to web_config service
- Require all requests to use a session path.
 - Use a redirect file to avoid exposing the URL on the command line, as
   it contains the session path.

Fix for CVE-2014-2914.
Closes #1438.
2014-08-07 18:53:31 +08:00
David Adam
2aac8e5dde Further fixes to universal variable server socket management
- Change fishd_path to std::string
- Warn, rather than exiting with an error, if the universal variable
  server path is not available, and provide more useful advice.
- Export the new __fishd_runtime_dir variable.
2014-08-07 18:53:16 +08:00
David Adam
209d8b7f2f Fix for CVE-2014-2905 - fishd restart required.
- Use a secure path for sockets (some code used under license from
   tmux).
 - Provide the secure path in the environment as $__fish_runtime_dir.
 - Link the new path to the old path to ease migration from earlier
   versions.

Closes #1359.

After installing fish built from or after this commit, you MUST
terminate all running fishd processes (`killall fishd`, `pkill fishd`
or similar). Distributors are encouraged to do this from within their
packaging scripts. fishd will restart automatically, and no data should
be lost.
2014-08-07 18:53:16 +08:00
David Adam
26663e042f Revert "Check effective credentials of socket peers"
This reverts commit aea9ad4965.

Just checking the credentials of the peer turns out to be
insufficient.
See https://github.com/fish-shell/fish-shell/issues/1436.
2014-08-07 18:52:27 +08:00
David Adam
55986120aa use mktemp(1) to generate temporary file names
Fix for CVE-2014-2906.

Closes a race condition in funced which would allow execution of
arbitrary code; closes a race condition in psub which would allow
alternation of the data stream.

Note that `psub -f` does not work (#1040); a fix should be committed
separately for ease of maintenance.
2014-04-27 12:23:24 +08:00
David Adam
aea9ad4965 Check effective credentials of socket peers
Fix for CVE-2014-2905.

Code for getpeereid() on non-BSD systems imported from the PostgreSQL
project under a BSD-style license.
2014-04-27 12:23:13 +08:00
Anders Bergh
216d32055d fish_config: Listen on both IPv6 and IPv4.
A subclass of TCPServer was created to deny any non-local connections and to
listen using an IPv6 socket.
2014-04-27 11:36:41 +08:00
2149 changed files with 137770 additions and 904594 deletions

View File

@@ -1,25 +0,0 @@
image: alpine/edge
packages:
- cmake
- ninja
- ncurses-dev
- pcre2-dev
- python3
- py-pip
sources:
- https://git.sr.ht/~faho/fish
tasks:
- build: |
pip3 install pexpect
cd fish
mkdir build || :
cd build
cmake -G Ninja .. \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INSTALL_DATADIR=share \
-DCMAKE_INSTALL_DOCDIR=share/doc/fish \
-DCMAKE_INSTALL_SYSCONFDIR=/etc
ninja
- test: |
cd fish/build
env ninja test

View File

@@ -1,22 +0,0 @@
image: archlinux
packages:
- cmake
- ninja
- python
- python-pexpect
sources:
- https://git.sr.ht/~faho/fish
tasks:
- build: |
cd fish
mkdir build || :
cd build
cmake -G Ninja .. \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INSTALL_DATADIR=share \
-DCMAKE_INSTALL_DOCDIR=share/doc/fish \
-DCMAKE_INSTALL_SYSCONFDIR=/etc
ninja
- test: |
cd fish/build
env ninja test

View File

@@ -1,26 +0,0 @@
image: freebsd/latest
packages:
- ncurses
- gcc
- gettext
- cmake
- gmake
- pcre2
- python
- py38-pexpect
sources:
- https://git.sr.ht/~faho/fish
tasks:
- build: |
cd fish
mkdir build || :
cd build
cmake .. \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INSTALL_DATADIR=share \
-DCMAKE_INSTALL_DOCDIR=share/doc/fish \
-DCMAKE_INSTALL_SYSCONFDIR=/etc
gmake -j2
- test: |
cd fish/build
gmake test

View File

@@ -1,106 +0,0 @@
env:
CIRRUS_CLONE_DEPTH: 1
CI: 1
linux_task:
matrix:
- name: alpine
container: &step
image: ghcr.io/fish-shell/fish-ci/alpine:latest
memory: 4GB
- name: bionic
container:
<<: *step
image: ghcr.io/fish-shell/fish-ci/bionic:latest
- name: bionic-asan-clang
container:
<<: *step
image: ghcr.io/fish-shell/fish-ci/bionic-asan-clang:latest
- name: bionic-tsan
container:
<<: *step
image: ghcr.io/fish-shell/fish-ci/bionic-tsan:latest
- name: bionic-tsan-clang
container:
<<: *step
image: ghcr.io/fish-shell/fish-ci/bionic-tsan-clang:latest
- name: centos7
container:
<<: *step
image: ghcr.io/fish-shell/fish-ci/centos7:latest
- name: centos8
container:
<<: *step
image: ghcr.io/fish-shell/fish-ci/centos8:latest
- name: focal-32bit
container:
<<: *step
image: ghcr.io/fish-shell/fish-ci/focal-32bit:latest
- name: xenial
container:
<<: *step
image: ghcr.io/fish-shell/fish-ci/xenial:latest
tests_script:
# cirrus at times gives us 32 procs and 2 GB of RAM
# Unrestriced parallelism results in OOM
- lscpu || true
- (cat /proc/meminfo | grep MemTotal) || true
- mkdir build && cd build
- cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCTEST_PARALLEL_LEVEL=6 ..
- ninja -j 6 fish fish_tests
- ninja fish_run_tests
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
linux_arm_task:
matrix:
- name: focal-arm64
arm_container:
image: ghcr.io/fish-shell/fish-ci/focal-arm64
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
- name: jammy-armv7-32bit
arm_container:
image: ghcr.io/fish-shell/fish-ci/jammy-armv7-32bit
tests_script:
# cirrus at times gives us 32 procs and 2 GB of RAM
# Unrestriced parallelism results in OOM
- lscpu || true
- (cat /proc/meminfo | grep MemTotal) || true
- mkdir build && cd build
- cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCTEST_PARALLEL_LEVEL=6 ..
- ninja -j 6 fish fish_tests
- file ./fish
- ninja fish_run_tests
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'
freebsd_task:
matrix:
- name: FreeBSD 14
freebsd_instance:
image_family: freebsd-14-0-snap
- name: FreeBSD 13
freebsd_instance:
image: freebsd-13-0-release-amd64
- name: FreeBSD 12.3
freebsd_instance:
image: freebsd-12-3-release-amd64
tests_script:
- pkg install -y cmake devel/pcre2 devel/ninja misc/py-pexpect git
# BSDs have the following behavior: root may open or access files even if
# the mode bits would otherwise disallow it. For example root may open()
# a file with write privileges even if the file has mode 400. This breaks
# our tests for e.g. cd and path. So create a new unprivileged user to run tests.
- pw user add -n fish-user -s /bin/csh -d /home/fish-user
- mkdir -p /home/fish-user
- chown -R fish-user /home/fish-user
- mkdir build && cd build
- chown -R fish-user ..
- sudo -u fish-user -s whoami
- sudo -u fish-user -s cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCTEST_PARALLEL_LEVEL=1 ..
- sudo -u fish-user -s ninja -j 6 fish fish_tests
- sudo -u fish-user -s ninja fish_run_tests
only_if: $CIRRUS_REPO_OWNER == 'fish-shell'

View File

@@ -1,16 +0,0 @@
# Use the Google style with these modifications:
#
# 1) lines can be up to 100 chars long rather than 80, and
# 2) use a four space indent rather than two spaces.
#
BasedOnStyle: Google
ColumnLimit: 100
IndentWidth: 4
# Place config.h first always.
IncludeCategories:
- Regex: '^"config.h"'
Priority: -1
# We don't want OCLint pragmas to be reformatted.
CommentPragmas: '^!OCLINT'

View File

@@ -1,17 +0,0 @@
---
Checks: 'clang-diagnostic-*,clang-analyzer-*,-clang-analyzer-valist.Uninitialized,cert-*,performance-*,portability-*,-modernize-use-auto,modernize-loop-convert,modernize-use-bool-literals,modernize-use-using,hicpp-uppercase-literal-suffix,readability-make-member-function-const,readability-redundant-string-init,readability-inconsistent-declaration-parameter-name,readability-redundant-access-specifiers,-performance-noexcept-move-constructor,-cert-dcl21-cpp,-cert-dcl37-c,-cert-dcl50-cpp,-cert-dcl51-cpp,-cert-str34-c,-cert-env33-c,misc-static-assert,readability-use-anyofallof,readability-simplify-*,readability-redundant-*,modernize-redundant-void-arg,modernize-make-shared,modernize-make-unique,modernize-loop-convert,'
WarningsAsErrors: ''
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
FormatStyle: File
CheckOptions:
- key: cert-dcl16-c.NewSuffixes
value: 'L;LL;LU;LLU'
- key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField
value: '0'
- key: modernize-loop-convert.MinConfidence
value: 'risky'
- key: modernize-use-auto.RemoveStars
value: '1'
...

View File

@@ -1,28 +0,0 @@
root = true
[*]
indent_size = 4
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100
[{Makefile,*.in}]
indent_style = tab
[*.{md,rst}]
trim_trailing_whitespace = false
[*.{sh,ac}]
indent_size = 2
[Dockerfile]
indent_size = 2
[share/{completions,functions}/**.fish]
max_line_length = none
[{COMMIT_EDITMSG,git-revise-todo}]
max_line_length = 80

31
.gitattributes vendored
View File

@@ -1,33 +1,4 @@
# normalize newlines
* text=auto
*.fish text
*.bat eol=crlf
# let git show off diff hunk headers, help git diff -L:
# https://git-scm.com/docs/gitattributes
*.cpp diff=cpp
*.h diff=cpp
*.py diff=py
# add a [diff "fish"] to git config with pattern
*.fish diff=fish
# omit from git archive
.gitattributes export-ignore
.gitignore export-ignore
/build_tools/make_tarball.sh export-ignore
/debian export-ignore
/debian/* export-ignore
/.github export-ignore
/.github/* export-ignore
/.builds export-ignore
/.builds/* export-ignore
/build_tools export-ignore
# for linguist; let github identify our project as C++ instead of C due to pcre2
pcre2/** linguist-vendored
angular.js linguist-vendored
angular-*.js linguist-vendored
doc_src/** linguist-documentation
*.fish linguist-language=fish
src/*.h linguist-language=c++
src/builtins/*.h linguist-language=c++
share/completions/*.fish linguist-documentation

View File

@@ -1,16 +0,0 @@
<!--
Please tell us which fish version you are using by executing the following:
fish --version
echo $version
Please tell us which operating system and terminal you are using. The output of `uname -a` and `echo $TERM` may be helpful in this regard although other commands might be relevant in your specific situation.
Please tell us if you tried fish without third-party customizations by executing this command and whether it affected the behavior you are reporting:
sh -c 'env HOME=$(mktemp -d) fish'
Tell us how to reproduce the problem. Including an asciinema.org recording is useful for problems that involve the visual display of fish output such as its prompt.
-->
**YOUR TEXT HERE**

View File

@@ -1,11 +0,0 @@
## Description
Talk about your changes here.
Fixes issue #
## TODOs:
<!-- Just check off what what we know been done so far. We can help you with this stuff. -->
- [ ] Changes to fish usage are reflected in user documentation/manpages.
- [ ] Tests have been added for regressions fixed
- [ ] User-visible changes noted in CHANGELOG.rst

View File

@@ -1,25 +0,0 @@
name: 'Lock threads'
on:
schedule:
- cron: '0 18 * * 1'
# │ │ │ │ │
# min 0-59 ┘ │ │ │ └ weekday 0-6
# hour 0-23 ┘ │ └ month 1-12
# └ day 1-31
permissions:
contents: read
jobs:
lock:
permissions:
issues: write # for dessant/lock-threads to lock issues
pull-requests: write # for dessant/lock-threads to lock PRs
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: '365'
pr-lock-inactive-days: '365'
issue-exclude-labels: 'question, needs more info'

View File

@@ -1,143 +0,0 @@
name: make test
on: [push, pull_request]
env:
CTEST_PARALLEL_LEVEL: "1"
CMAKE_BUILD_PARALLEL_LEVEL: "4"
permissions:
contents: read
jobs:
ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo apt install gettext libncurses5-dev libpcre2-dev python3-pip tmux
sudo pip3 install pexpect
# Generate a locale that uses a comma as decimal separator.
sudo locale-gen fr_FR.UTF-8
- name: cmake
env:
# Some warnings upgraded to errors to match Open Build Service platforms
CXXFLAGS: "-Werror=address -Werror=return-type"
run: |
mkdir build && cd build
cmake ..
- name: make
run: |
make
- name: make test
run: |
make test
ubuntu-32bit-fetched-pcre2:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo apt update
sudo apt install gettext lib32ncurses5-dev python3-pip g++-multilib tmux
sudo pip3 install pexpect
- name: cmake
env:
CXXFLAGS: "-m32 -Werror=address -Werror=return-type"
CFLAGS: "-m32"
run: |
mkdir build && cd build
cmake -DFISH_USE_SYSTEM_PCRE2=OFF ..
- name: make
run: |
make
- name: make test
run: |
make test
ubuntu-asan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo apt install gettext libncurses5-dev libpcre2-dev python3-pip tmux
sudo pip3 install pexpect
- name: cmake
env:
CC: clang
CXX: clang++
CXXFLAGS: "-fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address -DFISH_CI_SAN"
run: |
mkdir build && cd build
cmake ..
- name: make
run: |
make
- name: make test
env:
FISH_CI_SAN: 1
ASAN_OPTIONS: check_initialization_order=1:detect_stack_use_after_return=1:detect_leaks=1
UBSAN_OPTIONS: print_stacktrace=1:report_error_type=1
# use_tls=0 is a workaround for LSAN crashing with "Tracer caught signal 11" (SIGSEGV),
# which seems to be an issue with TLS support in newer glibc versions under virtualized
# environments. Follow https://github.com/google/sanitizers/issues/1342 and
# https://github.com/google/sanitizers/issues/1409 to track this issue.
LSAN_OPTIONS: verbosity=0:log_threads=0:use_tls=0
run: |
make test
ubuntu-threadsan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo apt install gettext libncurses5-dev libpcre2-dev python3-pip tmux
sudo pip3 install pexpect
- name: cmake
env:
FISH_CI_SAN: 1
CC: clang
CXX: clang++
CXXFLAGS: "-fsanitize=thread"
run: |
mkdir build && cd build
cmake ..
- name: make
run: |
make
- name: make test
run: |
make test
macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo pip3 install pexpect
brew install tmux
- name: cmake
run: |
mkdir build && cd build
cmake -DWITH_GETTEXT=NO ..
- name: make
run: |
make
- name: make test
run: |
make test

119
.gitignore vendored
View File

@@ -1,91 +1,34 @@
# Note that some of the patterns below should be in an individual's
# ~/.config/git/ignore file. For example, ".DS_Store" from people working on
# MacOS.
# File extensions that should never be checked in regardless of which project
# directory they reside in.
*.DS_Store
*.a
*.app
*.d
*.dll
*.dylib
*.exe
*.gch
*.la
*.lai
*.lib
*.lo
*.log
*.new
*.o
*.obj
*.orig
!tests/*.out
*.out
*.pch
*.slo
*.so
*.xccheckout
*bak
*~
*~HEAD
.AppleDouble
.LSOverride
.Trash-*
._*
Desktop.ini
Thumbs.db
ehthumbs.db
messages.pot
.directory
.fuse_hidden*
# Directories that only contain transitory files from building and testing.
/doc/
/share/man/
/share/doc/
/test/
/user_doc/
# File names that can appear in the project root that represent artifacts from
# building and testing.
/FISH-BUILD-VERSION-FILE
/command_list.txt
/command_list_toc.txt
/compile_commands.json
/doc.h
/fish
/fish.pc
/fish_indent
/fish_key_reader
/fish_tests
/lexicon.txt
/lexicon_filter
/toc.txt
/version
fish-build-version-witness.txt
__pycache__
# File names that can appear below the project root that represent artifacts
# from building and testing.
/doc_src/commands.hdr
/doc_src/index.hdr
/po/*.gmo
/share/__fish_build_paths.fish
/share/pkgconfig
/tests/*.tmp.*
# xcode
## Build generated
*.moved-aside
*.xccheckout
*.xcscmblueprin
.vscode
/DerivedData/
/build/
/tags
xcuserdata/
Doxyfile.help
Makefile
autom4te.cache/
build/
command_list.txt
confdefs.h
config.h
config.h.in
config.log
config.status
configure
doc.h
doc_src/commands.hdr
doc_src/index.hdr
po/*.gmo
fish
fish.spec
fish_indent
fish_pager
fish_tests
fishd
mimedb
seq
set_color
share/config.fish
share/man/
toc.txt
user_doc/
xcuserdata
tests/*tmp.*
tests/foo.txt

95
.oclint
View File

@@ -1,95 +0,0 @@
rules:
rule-configurations:
#
# This is the default value (as of the time I wrote this) but I'm making
# it explicit since it needs to agree with the value used by clang-format.
# Thus, if we ever change the fish style to allow longer or shorter lines
# this should be changed (as well as the corresponding .clang-format file).
#
- key: LONG_LINE
value: 100
#
# The default limit for the length of variable names is 20. Long names are
# problematic but twenty chars results in way too many errors. So increase
# the limit to something more reasonable.
#
- key: LONG_VARIABLE_NAME
value: 30
#
# This allows us to avoid peppering our code with inline comments such as
#
# scoped_lock locker(m_lock); //!OCLINT(side-effect)
#
# Specifically, this config key tells oclint that the named classes have
# RAII behavior so the local vars are actually used.
#
- key: RAII_CUSTOM_CLASSES
value: scoped_lock scoped_buffer_t builtin_commandline_scoped_transient_t scoped_push
# We're slightly more persmissive regarding the total number of lines in a
# function. Default is 50.
- key: LONG_METHOD
value: 60
# We're slightly more persmissive regarding the number of non-comment
# lines in a function. Default is 30.
- key: NCSS_METHOD
value: 40
# We're willing to allow slighly more linearly independent paths through a
# function. Most of our code has a lot of `switch` blocks or consecutive
# `if` tests that are straightforward to interpret but which increase this
# metric. Default is 10.
- key: CYCLOMATIC_COMPLEXITY
value: 14
# We're willing to allow slighly more execution paths through a function.
# Default is 200.
- key: NPATH_COMPLEXITY
value: 300
disable-rules:
#
# A few instances of "useless parentheses" errors are meaningful. Mostly
# in the context of the `return` statement. Unfortunately the vast
# majority would result in removing parentheses that decreases
# readability. So we're going to ignore this warning and rely on humans to
# notice when the parentheses are truly not needed.
#
# Also, some macro expansions, such as FD_SET(), trigger this warning and
# we don't want to suppress each of those individually.
#
- UselessParentheses
#
# OCLint wants variable names to be at least three characters in length.
# Which would be fine if it supported a reasonable set of exceptions
# (e.g., "i", "j", "k") and allowed adding additional exceptions to match
# conventions employed by a project. Since it doesn't, and thus generates
# a lot of really annoying warnings, we're going to disable this rule.
#
- ShortVariableName
#
# This rule flags perfectly reasonable conditions like `if (!some_condition)`
# and is therefore just noise. Disable this rule.
#
- InvertedLogic
#
# The idea behind the "double negative" rule is sound since constructs
# like "!!(var & flag)" should be written as "static_cast<bool>(var &
# flag)". Unfortunately this rule has way too many false positives;
# especially in the context of assert statements. So disable this rule.
#
- DoubleNegative
#
# Avoiding bitwise operators in a conditional is a good idea with one
# exception: testing whether a bit flag is set. Which happens to be the
# only time you'll see something like `if (j->flags & JOB_CONSTRUCTED)`
# in fish source.
#
- BitwiseOperatorInConditional
#
# I don't think I've ever seen a case where assigning a value to a
# parameter inside the function body was unclear, let along dangerous or
# an error. This rule is therefore just noise. Disable this rule.
#
- ParameterReassignment

View File

@@ -1,55 +0,0 @@
# This is a very basic `make` wrapper around the CMake build toolchain.
#
# Supported arguments:
# PREFIX: sets the installation prefix
# GENERATOR: explicitly specifies the CMake generator to use
# By default, bmake will try to cd into ./obj before anything else. Don't do that.
.OBJDIR: ./
CMAKE?=cmake
# Before anything else, test for CMake, which is the only requirement to be able to run
# this Makefile CMake will perform the remaining dependency tests on its own.
.BEGIN:
@which $(CMAKE) >/dev/null 2>/dev/null || \
(echo 'Please install CMake and then re-run the `make` command!' 1>&2 && false)
# Prefer to use ninja, if it is installed
_GENERATOR!=which ninja 2>/dev/null >/dev/null && echo Ninja || echo "Unix Makefiles"
GENERATOR?=$(_GENERATOR)
.if $(GENERATOR) == "Ninja"
BUILDFILE=build.ninja
.else
BUILDFILE=Makefile
.endif
PREFIX?=/usr/local
.PHONY: build/fish
build/fish: build/$(BUILDFILE)
$(CMAKE) --build build
# Don't split the mkdir into its own rule because that would cause CMake to regenerate the build
# files after each build (because it adds the mdate of the build directory into the out-of-date
# calculation tree). GNUmake supports order-only dependencies, BSDmake does not seem to.
build/$(BUILDFILE):
mkdir -p build
cd build; $(CMAKE) .. -G "$(GENERATOR)" -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_EXPORT_COMPILE_COMMANDS=1
.PHONY: install
install: build/fish
$(CMAKE) --build build --target install
.PHONY: clean
clean:
rm -rf build
.PHONY: test
test: build/fish
$(CMAKE) --build build --target test
.PHONY: run
run: build/fish
build/fish

3
CHANGELOG Normal file
View File

@@ -0,0 +1,3 @@
24-01-2012 Jan Kanis
* Added a changelog file
* removed unescaping if the 'commandline' builtin is called without the -o (tokenise) flag

File diff suppressed because it is too large Load Diff

View File

@@ -1,240 +0,0 @@
cmake_minimum_required(VERSION 3.5)
if(POLICY CMP0066)
cmake_policy(SET CMP0066 OLD)
endif()
if(POLICY CMP0067)
cmake_policy(SET CMP0067 NEW)
endif()
include(cmake/Mac.cmake)
project(fish)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# We are C++11.
set(CMAKE_CXX_STANDARD 11)
set(DEFAULT_BUILD_TYPE "RelWithDebInfo")
# Generate Xcode schemas (but not for tests).
set(CMAKE_XCODE_GENERATE_SCHEME 1)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "Setting build type to default '${DEFAULT_BUILD_TYPE}'")
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}")
endif()
# Error out when linking statically, it doesn't work.
if (CMAKE_EXE_LINKER_FLAGS MATCHES ".*-static.*")
message(FATAL_ERROR "Fish does not support static linking")
endif()
# Force colored warnings in Ninja's output, if the compiler has -fdiagnostics-color support.
# Rationale in https://github.com/ninja-build/ninja/issues/814
if (CMAKE_GENERATOR STREQUAL "Ninja" AND
((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) OR
(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.5) OR
(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)))
add_compile_options(-fdiagnostics-color=always)
endif()
# Enable a whole bunch of warnings, but turn off:
# - comment because we use a bunch of those, and they're not really all that harmful.
# - address, because that occurs for our mkostemp check (weak-linking requires us to compare `&mkostemp == nullptr`).
add_compile_options(-Wall -Wextra -Wno-comment -Wno-address)
if ((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang"))
add_compile_options(-Wunused-template -Wunused-local-typedef -Wunused-macros)
endif()
# Disable exception handling.
add_compile_options(-fno-exceptions)
# Undefine NDEBUG to keep assert() in release builds.
add_definitions(-UNDEBUG)
# Enable large files on GNU.
add_definitions(-D_LARGEFILE_SOURCE
-D_LARGEFILE64_SOURCE
-D_FILE_OFFSET_BITS=64
-D_ATFILE_SOURCE)
# Hide the CMake Rules directories in Xcode projects.
source_group("CMake Rules" REGULAR_EXPRESSION "^$")
# Put source and header files at top level under targets.
source_group("Source Files" REGULAR_EXPRESSION ".*\\.cpp")
source_group("Header Files" REGULAR_EXPRESSION ".*\\.h")
source_group("Builtins" "builtins/")
# Support folders.
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# Work around issue where archive-built libs go in the wrong place.
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
set(FISH_IN_TREE_BUILD TRUE)
else()
set(FISH_IN_TREE_BUILD FALSE)
endif()
# NetBSD does weird things with finding libraries,
# making the tests fail by failing to find pcre.
#
# Keep the rpath used to build.
if(CMAKE_SYSTEM_NAME STREQUAL NetBSD)
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
endif()
# List of sources for builtin functions.
set(FISH_BUILTIN_SRCS
src/builtin.cpp src/builtins/abbr.cpp src/builtins/argparse.cpp
src/builtins/bg.cpp src/builtins/bind.cpp src/builtins/block.cpp
src/builtins/builtin.cpp src/builtins/cd.cpp src/builtins/command.cpp
src/builtins/commandline.cpp src/builtins/complete.cpp src/builtins/contains.cpp
src/builtins/disown.cpp src/builtins/echo.cpp src/builtins/emit.cpp
src/builtins/eval.cpp src/builtins/exit.cpp src/builtins/fg.cpp
src/builtins/function.cpp src/builtins/functions.cpp src/builtins/history.cpp
src/builtins/jobs.cpp src/builtins/math.cpp src/builtins/printf.cpp src/builtins/path.cpp
src/builtins/pwd.cpp src/builtins/random.cpp src/builtins/read.cpp
src/builtins/realpath.cpp src/builtins/return.cpp src/builtins/set.cpp
src/builtins/set_color.cpp src/builtins/source.cpp src/builtins/status.cpp
src/builtins/string.cpp src/builtins/test.cpp src/builtins/type.cpp src/builtins/ulimit.cpp
src/builtins/wait.cpp)
# List of other sources.
set(FISH_SRCS
src/ast.cpp src/abbrs.cpp src/autoload.cpp src/color.cpp src/common.cpp src/complete.cpp
src/env.cpp src/env_dispatch.cpp src/env_universal_common.cpp src/event.cpp
src/exec.cpp src/expand.cpp src/fallback.cpp src/fd_monitor.cpp src/fish_version.cpp
src/flog.cpp src/function.cpp src/future_feature_flags.cpp src/highlight.cpp
src/history.cpp src/history_file.cpp src/input.cpp src/input_common.cpp
src/io.cpp src/iothread.cpp src/job_group.cpp src/kill.cpp
src/null_terminated_array.cpp src/operation_context.cpp src/output.cpp
src/pager.cpp src/parse_execution.cpp src/parse_tree.cpp src/parse_util.cpp
src/parser.cpp src/parser_keywords.cpp src/path.cpp src/postfork.cpp
src/proc.cpp src/re.cpp src/reader.cpp src/redirection.cpp src/screen.cpp
src/signal.cpp src/termsize.cpp src/timer.cpp src/tinyexpr.cpp
src/tokenizer.cpp src/topic_monitor.cpp src/trace.cpp src/utf8.cpp src/util.cpp
src/wait_handle.cpp src/wcstringutil.cpp src/wgetopt.cpp src/wildcard.cpp
src/wutil.cpp src/fds.cpp
)
# Header files are just globbed.
file(GLOB FISH_HEADERS src/*.h)
# Set up config.h
include(cmake/ConfigureChecks.cmake)
include(cmake/gettext.cmake)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config_cmake.h.in
${CMAKE_CURRENT_BINARY_DIR}/config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Set up standard directories.
include(GNUInstallDirs)
add_definitions(-D_UNICODE=1
-DLOCALEDIR="${CMAKE_INSTALL_FULL_LOCALEDIR}"
-DPREFIX=L"${CMAKE_INSTALL_PREFIX}"
-DDATADIR=L"${CMAKE_INSTALL_FULL_DATADIR}"
-DSYSCONFDIR=L"${CMAKE_INSTALL_FULL_SYSCONFDIR}"
-DBINDIR=L"${CMAKE_INSTALL_FULL_BINDIR}"
-DDOCDIR=L"${CMAKE_INSTALL_FULL_DOCDIR}")
# Set up the machinery around FISH-BUILD-VERSION-FILE
# This defines the FBVF variable.
include(Version)
# Let fish pick up when we're running out of the build directory without installing
get_filename_component(REAL_CMAKE_BINARY_DIR "${CMAKE_BINARY_DIR}" REALPATH)
get_filename_component(REAL_CMAKE_SOURCE_DIR "${CMAKE_SOURCE_DIR}" REALPATH)
add_definitions(-DCMAKE_BINARY_DIR="${REAL_CMAKE_BINARY_DIR}")
add_definitions(-DCMAKE_SOURCE_DIR="${REAL_CMAKE_SOURCE_DIR}")
# Teach fish_version.o to rebuild when FBVF changes.
# The standard C++ include detection machinery misses this.
set_source_files_properties(src/fish_version.cpp
PROPERTIES OBJECT_DEPENDS
${CMAKE_CURRENT_BINARY_DIR}/${FBVF})
# Enable thread-safe errno on Solaris (#5611)
add_definitions(-D_REENTRANT)
# Set up PCRE2
include(cmake/PCRE2.cmake)
# Define a function to link dependencies.
function(FISH_LINK_DEPS_AND_SIGN target)
target_link_libraries(${target} fishlib)
codesign_on_mac(${target})
endfunction(FISH_LINK_DEPS_AND_SIGN)
# Define libfish.a.
add_library(fishlib STATIC ${FISH_SRCS} ${FISH_BUILTIN_SRCS})
target_sources(fishlib PRIVATE ${FISH_HEADERS})
target_link_libraries(fishlib
${CURSES_LIBRARY} ${CURSES_EXTRA_LIBRARY} Threads::Threads ${CMAKE_DL_LIBS}
${PCRE2_LIB} ${Intl_LIBRARIES} ${ATOMIC_LIBRARY})
target_include_directories(fishlib PRIVATE
${CURSES_INCLUDE_DIRS})
# Define fish.
add_executable(fish src/fish.cpp)
fish_link_deps_and_sign(fish)
# Define fish_indent.
add_executable(fish_indent
src/fish_indent.cpp src/print_help.cpp)
fish_link_deps_and_sign(fish_indent)
# Define fish_key_reader.
add_executable(fish_key_reader
src/fish_key_reader.cpp src/print_help.cpp)
fish_link_deps_and_sign(fish_key_reader)
# Set up the docs.
include(cmake/Docs.cmake)
# A helper for running tests.
add_executable(fish_test_helper src/fish_test_helper.cpp)
# Set up tests.
include(cmake/Tests.cmake)
# Benchmarking support.
include(cmake/Benchmark.cmake)
# Set up install.
include(cmake/Install.cmake)
# Mac app.
include(cmake/MacApp.cmake)
# ThreadSanitizer likes to muck with signal handlers, which interferes
# with fish_test_helper printing the ignored signal mask.
# Ensure fish_test_helper does not use TSan.
# Note the environment var is CXXFLAGS, but the CMake var is CMAKE_CXX_FLAGS.
if (CMAKE_CXX_FLAGS MATCHES ".*-fsanitize=thread.*")
target_compile_options(fish_test_helper PRIVATE "-fno-sanitize=all")
target_link_libraries(fish_test_helper "-fno-sanitize=all")
endif()
# Lint targets
# This could be implemented as target properties, but the script has the useful feature of only
# checking the currently-staged commands
# The generator expressions below rebuild the command line for the fishlib targets
# CMake does not support the "iquote" flag - https://gitlab.kitware.com/cmake/cmake/issues/15491
set(LINT_ARGS "-D$<JOIN:$<TARGET_PROPERTY:fishlib,COMPILE_DEFINITIONS>, -D>" "-I$<JOIN:$<TARGET_PROPERTY:fishlib,INCLUDE_DIRECTORIES>, -I>")
add_custom_target(lint
COMMAND build_tools/lint.fish -p ${CMAKE_BINARY_DIR} -- ${LINT_ARGS}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
)
add_custom_target(lint-all
COMMAND build_tools/lint.fish --all -p ${CMAKE_BINARY_DIR} -- ${LINT_ARGS}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
)
include(FeatureSummary)
feature_summary(WHAT ALL)

View File

@@ -1,129 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -1,449 +0,0 @@
Guidelines For Developers
=========================
This document provides guidelines for making changes to the fish-shell
project. This includes rules for how to format the code, naming
conventions, et cetera.
In short:
- Be conservative in what you need (``C++11``, few dependencies)
- Use automated tools to help you (including ``make test``, ``build_tools/style.fish`` and ``make lint``)
Contributing completions
------------------------
Completion scripts are the most common contribution to fish, and they are very welcome.
In general, we'll take all well-written completion scripts for a command that is publically available.
This means no private tools or personal scripts, and we do reserve the right to reject for other reasons.
Before you try to contribute them to fish, consider if the authors of the tool you are completing want to maintain the script instead.
Often that makes more sense, specifically because they can add new options to the script immediately once they add them,
and don't have to maintain one completion script for multiple versions. If the authors no longer wish to maintain the script,
they can of course always contact the fish maintainers to hand it over, preferably by opening a PR.
This isn't a requirement - if the authors don't want to maintain it, or you simply don't want to contact them,
you can contribute your script to fish.
Completion scripts should
1. Use as few dependencies as possible - try to use fish's builtins like ``string`` instead of ``grep`` and ``awk``,
use ``python`` to read json instead of ``jq`` (because it's already a soft dependency for fish's tools)
2. If it uses a common unix tool, use posix-compatible invocations - ideally it would work on GNU/Linux, macOS, the BSDs and other systems
3. Option and argument descriptions should be kept short.
The shorter the description, the more likely it is that fish can use more columns.
4. Function names should start with ``__fish``, and functions should be kept in the completion file unless they're used elsewhere.
5. Run ``fish_indent`` on your script.
6. Try not to use minor convenience features right after they are available in fish - we do try to keep completion scripts backportable.
If something has a real impact on the correctness or performance, feel free to use it,
but if it is just a shortcut, please leave it.
Put your completion script into share/completions/name-of-command.fish. If you have multiple commands, you need multiple files.
If you want to add tests, you probably want to add a littlecheck test. See below for details.
Contributing to fish's C++ core
-------------------------------
Fish uses C++11. Newer C++ features should not be used to make it possible to use on older systems.
It does not use exceptions, they are disabled at build time with ``-fno-exceptions``.
Don't introduce new dependencies unless absolutely necessary, and if you do,
please make it optional with graceful failure if possible.
Add any new dependencies to the README.rst under the *Running* and/or *Building* sections.
Linters
-------
Automated analysis tools like cppcheck can point out
potential bugs or code that is extremely hard to understand. They also
help ensure the code has a consistent style and that it avoids patterns
that tend to confuse people.
To make linting the code easy there are two make targets: ``lint``,
to lint any modified but not committed ``*.cpp`` files, and
``lint-all`` to lint all files.
Fish has custom cppcheck rules in the file ``.cppcheck.rule``. These
help catch mistakes such as using ``wcwidth()`` rather than
``fish_wcwidth()``. Please add a new rule if you find similar mistakes
being made.
Suppressing Lint Warnings
~~~~~~~~~~~~~~~~~~~~~~~~~
Once in a while the lint tools emit a false positive warning. For
example, cppcheck might suggest a memory leak is present when that is
not the case. To suppress that cppcheck warning you should insert a line
like the following immediately prior to the line cppcheck warned about:
::
// cppcheck-suppress memleak // addr not really leaked
The explanatory portion of the suppression comment is optional. For
other types of warnings replace “memleak” with the value inside the
parenthesis (e.g., “nullPointerRedundantCheck”) from a warning like the
following:
::
[src/complete.cpp:1727]: warning (nullPointerRedundantCheck): Either the condition 'cmd_node' is redundant or there is possible null pointer dereference: cmd_node.
Code Style
----------
To ensure your changes conform to the style rules run
::
build_tools/style.fish
before committing your change. That will run our autoformatters:
- ``git-clang-format`` for c++
- ``fish_indent`` (shipped with fish) for fish script
- ``black`` for python
If youve already committed your changes thats okay since it will then
check the files in the most recent commit. This can be useful after
youve merged another persons change and want to check that its style
is acceptable. However, in that case it will run ``clang-format`` to
ensure the entire file, not just the lines modified by the commit,
conform to the style.
If you want to check the style of the entire code base run
::
build_tools/style.fish --all
That command will refuse to restyle any files if you have uncommitted
changes.
Configuring Your Editor for Fish Scripts
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you use Vim: Install `vim-fish <https://github.com/dag/vim-fish>`__,
make sure you have syntax and filetype functionality in ``~/.vimrc``:
::
syntax enable
filetype plugin indent on
Then turn on some options for nicer display of fish scripts in
``~/.vim/ftplugin/fish.vim``:
::
" Set up :make to use fish for syntax checking.
compiler fish
" Set this to have long lines wrap inside comments.
setlocal textwidth=79
" Enable folding of block structures in fish.
setlocal foldmethod=expr
If you use Emacs: Install
`fish-mode <https://github.com/wwwjfy/emacs-fish>`__ (also available in
melpa and melpa-stable) and ``(setq-default indent-tabs-mode nil)`` for
it (via a hook or in ``use-package``\ s “:init” block). It can also be
made to run fish_indent via e.g.
.. code:: elisp
(add-hook 'fish-mode-hook (lambda ()
(add-hook 'before-save-hook 'fish_indent-before-save)))
Fish Script Style Guide
-----------------------
1. All fish scripts, such as those in the *share/functions* and *tests*
directories, should be formatted using the ``fish_indent`` command.
2. Function names should be in all lowercase with words separated by
underscores. Private functions should begin with an underscore. The
first word should be ``fish`` if the function is unique to fish.
3. The first word of global variable names should generally be ``fish``
for public vars or ``_fish`` for private vars to minimize the
possibility of name clashes with user defined vars.
C++ Style Guide
---------------
1. The `Google C++ Style
Guide <https://google.github.io/styleguide/cppguide.html>`__ forms
the basis of the fish C++ style guide. There are two major deviations
for the fish project. First, a four, rather than two, space indent.
Second, line lengths up to 100, rather than 80, characters.
2. The ``clang-format`` command is authoritative with respect to
indentation, whitespace around operators, etc.
3. All names in code should be ``small_snake_case``. No Hungarian
notation is used. The names for classes and structs should be
followed by ``_t``.
4. Always attach braces to the surrounding context.
5. Indent with spaces, not tabs and use four spaces per indent.
6. Document the purpose of a function or class with doxygen-style
comment blocks. e.g.:
::
/**
* Sum numbers in a vector.
*
* @param values Container whose values are summed.
* @return sum of `values`, or 0.0 if `values` is empty.
*/
double sum(std::vector<double> & const values) {
...
}
*/
or
::
/// brief description of somefunction()
void somefunction() {
Testing
-------
The source code for fish includes a large collection of tests. If you
are making any changes to fish, running these tests is a good way to make
sure the behaviour remains consistent and regressions are not
introduced. Even if you dont run the tests on your machine, they will
still be run via Github Actions.
You are strongly encouraged to add tests when changing the functionality
of fish, especially if you are fixing a bug to help ensure there are no
regressions in the future (i.e., we dont reintroduce the bug).
The tests can be found in three places:
- src/fish_tests.cpp for tests to the core C++ code
- tests/checks for script tests, run by `littlecheck <https://github.com/ridiculousfish/littlecheck>`__
- tests/pexpects for interactive tests using `pexpect <https://pexpect.readthedocs.io/en/stable/>`__
When in doubt, the bulk of the tests should be added as a littlecheck test in tests/checks, as they are the easiest to modify and run, and much faster and more dependable than pexpect tests. The syntax is fairly self-explanatory. It's a fish script with the expected output in ``# CHECK:`` or ``# CHECKERR:`` (for stderr) comments.
fish_tests.cpp is mostly useful for unit tests - if you wish to test that a function does the correct thing for given input, use it.
The pexpects are written in python and can simulate input and output to/from a terminal, so they are needed for anything that needs actual interactivity. The runner is in build_tools/pexpect_helper.py, in case you need to modify something there.
Local testing
~~~~~~~~~~~~~
The tests can be run on your local computer on all operating systems.
::
cmake path/to/fish-shell
make test
Git hooks
~~~~~~~~~
Since developers sometimes forget to run the tests, it can be helpful to
use git hooks (see githooks(5)) to automate it.
One possibility is a pre-push hook script like this one:
.. code:: sh
#!/bin/sh
#### A pre-push hook for the fish-shell project
# This will run the tests when a push to master is detected, and will stop that if the tests fail
# Save this as .git/hooks/pre-push and make it executable
protected_branch='master'
# Git gives us lines like "refs/heads/frombranch SOMESHA1 refs/heads/tobranch SOMESHA1"
# We're only interested in the branches
while read from _ to _; do
if [ "x$to" = "xrefs/heads/$protected_branch" ]; then
isprotected=1
fi
done
if [ "x$isprotected" = x1 ]; then
echo "Running tests before push to master"
make test
RESULT=$?
if [ $RESULT -ne 0 ]; then
echo "Tests failed for a push to master, we can't let you do that" >&2
exit 1
fi
fi
exit 0
This will check if the push is to the master branch and, if it is, only
allow the push if running ``make test`` succeeds. In some circumstances
it may be advisable to circumvent this check with
``git push --no-verify``, but usually that isnt necessary.
To install the hook, place the code in a new file
``.git/hooks/pre-push`` and make it executable.
Coverity Scan
~~~~~~~~~~~~~
We use Coveritys static analysis tool which offers free access to open
source projects. While access to the tool itself is restricted,
fish-shell organization members should know that they can login
`here <https://scan.coverity.com/projects/fish-shell-fish-shell?tab=overview>`__
with their GitHub account. Currently, tests are triggered upon merging
the ``master`` branch into ``coverity_scan_master``. Even if you are not
a fish developer, you can keep an eye on our statistics there.
Installing the Required Tools
-----------------------------
Installing the Linting Tools
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To install the lint checkers on Mac OS X using Homebrew:
::
brew install cppcheck
To install the lint checkers on Debian-based Linux distributions:
::
sudo apt-get install clang
sudo apt-get install cppcheck
Installing the Formatting Tools
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Mac OS X:
::
brew install clang-format
Debian-based:
::
sudo apt-get install clang-format
Message Translations
--------------------
Fish uses the GNU gettext library to translate messages from English to
other languages.
All non-debug messages output for user consumption should be marked for
translation. In C++, this requires the use of the ``_`` (underscore)
macro:
::
streams.out.append_format(_(L"%ls: There are no jobs\n"), argv[0]);
All messages in fish script must be enclosed in single or double quote
characters. They must also be translated via a subcommand. This means
that the following are **not** valid:
::
echo (_ hello)
_ "goodbye"
Above should be written like this instead:
::
echo (_ "hello")
echo (_ "goodbye")
Note that you can use either single or double quotes to enclose the
message to be translated. You can also optionally include spaces after
the opening parentheses and once again before the closing parentheses.
Creating and updating translations requires the Gettext tools, including
``xgettext``, ``msgfmt`` and ``msgmerge``. Translation sources are
stored in the ``po`` directory, named ``LANG.po``, where ``LANG`` is the
two letter ISO 639-1 language code of the target language (eg ``de`` for
German).
To create a new translation, for example for German:
* generate a ``messages.pot`` file by running ``build_tools/fish_xgettext.fish`` from
the source tree
* copy ``messages.pot`` to ``po/LANG.po``
To update a translation:
* generate a ``messages.pot`` file by running
``build_tools/fish_xgettext.fish`` from the source tree
* update the existing translation by running
``msgmerge --update --no-fuzzy-matching po/LANG.po messages.pot``
Many tools are available for editing translation files, including
command-line and graphical user interface programs.
Be cautious about blindly updating an existing translation file. Trivial
changes to an existing message (eg changing the punctuation) will cause
existing translations to be removed, since the tools do literal string
matching. Therefore, in general, you need to carefully review any
recommended deletions.
Read the `translations
wiki <https://github.com/fish-shell/fish-shell/wiki/Translations>`__ for
more information.
Versioning
----------
The fish version is constructed by the *build_tools/git_version_gen.sh*
script. For developers the version is the branch name plus the output of
``git describe --always --dirty``. Normally the main part of the version
will be the closest annotated tag. Which itself is usually the most
recent release number (e.g., ``2.6.0``).
Include What You Use
--------------------
You should not depend on symbols being visible to a ``*.cpp`` module
from ``#include`` statements inside another header file. In other words
if your module does ``#include "common.h"`` and that header does
``#include "signal.h"`` your module should not assume the sub-include is
present. It should instead directly ``#include "signal.h"`` if it needs
any symbol from that header. That makes the actual dependencies much
clearer. It also makes it easy to modify the headers included by a
specific header file without having to worry that will break any module
(or header) that includes a particular header.
To help enforce this rule the ``make lint`` (and ``make lint-all``)
command will run the
`include-what-you-use <https://include-what-you-use.org/>`__ tool. You
can find the IWYU project on
`github <https://github.com/include-what-you-use/include-what-you-use>`__.
To install the tool on OS X youll need to add a
`formula <https://github.com/jasonmp85/homebrew-iwyu>`__ then install
it:
::
brew tap jasonmp85/iwyu
brew install iwyu
On Ubuntu you can install it via ``apt-get``:
::
sudo apt-get install iwyu

20
COPYING
View File

@@ -1,20 +0,0 @@
Fish is a smart and user-friendly command line shell.
Copyright (C) 2005-2009 Axel Liljencrantz
Copyright (C) 2009-2023 fish-shell contributors
fish is free software.
Most of fish is licensed under the GNU General Public License version 2, and
you can redistribute it and/or modify it under the terms of the GNU GPL as
published by the Free Software Foundation.
fish also includes software licensed under the GNU Lesser General Public
License version 2, the OpenBSD license, the ISC license, and the NetBSD license.
Full licensing information is contained in doc_src/license.rst.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.

View File

@@ -1,19 +0,0 @@
FROM centos:latest
# Build dependency
RUN yum update -y &&\
yum install -y epel-release &&\
yum install -y clang cmake3 gcc-c++ make ncurses-devel &&\
yum clean all
# Test dependency
RUN yum install -y expect vim-common
ADD . /src
WORKDIR /src
# Build fish
RUN cmake3 . &&\
make &&\
make install

1136
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

1161
Doxyfile.help Normal file

File diff suppressed because it is too large Load Diff

161
Doxyfile.user Normal file
View File

@@ -0,0 +1,161 @@
PROJECT_NAME = fish
PROJECT_NUMBER = 1
OUTPUT_DIRECTORY = user_doc
CREATE_SUBDIRS = NO
OUTPUT_LANGUAGE = English
USE_WINDOWS_ENCODING = NO
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ABBREVIATE_BRIEF = YES
ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = NO
FULL_PATH_NAMES = YES
STRIP_FROM_PATH =
STRIP_FROM_INC_PATH =
SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = YES
MULTILINE_CPP_IS_BRIEF = NO
DETAILS_AT_TOP = NO
INHERIT_DOCS = YES
DISTRIBUTE_GROUP_DOC = NO
TAB_SIZE = 8
ALIASES =
OPTIMIZE_OUTPUT_FOR_C = YES
OPTIMIZE_OUTPUT_JAVA = NO
SUBGROUPING = YES
EXTRACT_ALL = NO
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = YES
EXTRACT_LOCAL_CLASSES = YES
EXTRACT_LOCAL_METHODS = NO
HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO
HIDE_FRIEND_COMPOUNDS = NO
HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
CASE_SENSE_NAMES = YES
HIDE_SCOPE_NAMES = NO
SHOW_INCLUDE_FILES = YES
INLINE_INFO = YES
SORT_MEMBER_DOCS = YES
SORT_BRIEF_DOCS = NO
SORT_BY_SCOPE_NAME = NO
GENERATE_TODOLIST = YES
GENERATE_TESTLIST = YES
GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = YES
SHOW_DIRECTORIES = YES
QUIET = NO
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_FORMAT = "$file:$line: $text"
WARN_LOGFILE =
INPUT =
FILE_PATTERNS = doc.h
RECURSIVE = NO
EXCLUDE =
EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS =
EXAMPLE_PATH =
EXAMPLE_PATTERNS =
EXAMPLE_RECURSIVE = NO
IMAGE_PATH =
INPUT_FILTER =
FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
SOURCE_BROWSER = NO
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
REFERENCED_BY_RELATION = YES
REFERENCES_RELATION = YES
VERBATIM_HEADERS = YES
ALPHABETICAL_INDEX = NO
COLS_IN_ALPHA_INDEX = 5
IGNORE_PREFIX =
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html
HTML_HEADER = user_doc.head.html
HTML_FOOTER =
HTML_STYLESHEET =
HTML_ALIGN_MEMBERS = YES
GENERATE_HTMLHELP = NO
CHM_FILE =
HHC_LOCATION =
GENERATE_CHI = NO
BINARY_TOC = NO
TOC_EXPAND = NO
DISABLE_INDEX = YES
ENUM_VALUES_PER_LINE = 4
GENERATE_TREEVIEW = NO
TREEVIEW_WIDTH = 250
GENERATE_LATEX = NO
LATEX_OUTPUT = latex
LATEX_CMD_NAME = latex
MAKEINDEX_CMD_NAME = makeindex
COMPACT_LATEX = NO
PAPER_TYPE = a4wide
EXTRA_PACKAGES =
LATEX_HEADER =
PDF_HYPERLINKS = YES
USE_PDFLATEX = YES
LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
GENERATE_RTF = NO
RTF_OUTPUT = rtf
COMPACT_RTF = NO
RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE =
RTF_EXTENSIONS_FILE =
GENERATE_MAN = NO
MAN_OUTPUT = man
MAN_EXTENSION = .3
MAN_LINKS = NO
GENERATE_XML = NO
XML_OUTPUT = xml
XML_SCHEMA =
XML_DTD =
XML_PROGRAMLISTING = YES
GENERATE_AUTOGEN_DEF = NO
GENERATE_PERLMOD = NO
PERLMOD_LATEX = NO
PERLMOD_PRETTY = YES
PERLMOD_MAKEVAR_PREFIX =
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = NO
EXPAND_ONLY_PREDEF = NO
SEARCH_INCLUDES = YES
INCLUDE_PATH =
INCLUDE_FILE_PATTERNS =
PREDEFINED =
EXPAND_AS_DEFINED =
SKIP_FUNCTION_MACROS = YES
TAGFILES =
GENERATE_TAGFILE =
ALLEXTERNALS = NO
EXTERNAL_GROUPS = YES
PERL_PATH = /usr/bin/perl
CLASS_DIAGRAMS = YES
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = NO
CLASS_GRAPH = NO
COLLABORATION_GRAPH = YES
UML_LOOK = NO
TEMPLATE_RELATIONS = NO
INCLUDE_GRAPH = NO
INCLUDED_BY_GRAPH = YES
CALL_GRAPH = YES
GRAPHICAL_HIERARCHY = YES
DOT_IMAGE_FORMAT = png
DOT_PATH =
DOTFILE_DIRS =
MAX_DOT_GRAPH_WIDTH = 750
MAX_DOT_GRAPH_HEIGHT = 1024
MAX_DOT_GRAPH_DEPTH = 0
GENERATE_LEGEND = YES
DOT_CLEANUP = YES
SEARCHENGINE = NO

View File

@@ -1,72 +0,0 @@
# This is a very basic `make` wrapper around the CMake build toolchain.
#
# Supported arguments:
# PREFIX: sets the installation prefix
# GENERATOR: explicitly specifies the CMake generator to use
CMAKE ?= cmake
GENERATOR ?= $(shell (which ninja > /dev/null 2> /dev/null && echo Ninja) || \
echo 'Unix Makefiles')
prefix ?= /usr/local
PREFIX ?= $(prefix)
ifeq ($(GENERATOR), Ninja)
BUILDFILE = build.ninja
else
BUILDFILE = Makefile
endif
# If CMake has generated an in-tree Makefile, use that instead (issue #6264)
MAKE_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
ifeq ($(shell test -f $(MAKE_DIR)/Makefile && echo 1), 1)
all:
@+$(MAKE) -f $(MAKE_DIR)/Makefile $(MAKECMDGOALS) --no-print-directory
%:
@+$(MAKE) -f $(MAKE_DIR)/Makefile $(MAKECMDGOALS) --no-print-directory
else
all: .begin build/fish
.PHONY: .begin
.begin:
@which $(CMAKE) > /dev/null 2> /dev/null || \
(echo 'Please install CMake and then re-run the `make` command!' 1>&2 && false)
.PHONY: build/fish
build/fish: build/$(BUILDFILE)
$(CMAKE) --build build
# Use build as an order-only dependency. This prevents the target from always being outdated
# after a make run, and more importantly, doesn't clobber manually specified CMake options.
build/$(BUILDFILE): | build
cd build; $(CMAKE) .. -G "$(GENERATOR)" \
-DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_EXPORT_COMPILE_COMMANDS=1
build:
mkdir -p build
.PHONY: clean
clean:
rm -rf build
.PHONY: test
test: build/fish
$(CMAKE) --build build --target test
.PHONY: install
install: build/fish
$(CMAKE) --build build --target install
.PHONY: run
run: build/fish
./build/fish || true
.PHONY: exec
exec: build/fish
exec ./build/fish
endif # CMake in-tree build check

985
Makefile.in Normal file
View File

@@ -0,0 +1,985 @@
# Copyright (C) 2005-2006 Axel Liljencrantz
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
# @configure_input@
#
#
# Makefile for the fish shell. Can build fish and associated
# applications, install them, recalculate dependencies and also create
# binary distributions in tar.bz2, tar.gz and rpm formats.
#
#
# The fish buildprocess is quite complex. Do not stare directly into
# the Makefile. Doing so may cause nausea, dizziness and
# hallucinations.
#
# Used by docdir
PACKAGE_TARNAME = @PACKAGE_TARNAME@
#
# Programs
#
CXX := @CXX@
INSTALL:=@INSTALL@
#
# Installation directories
#
prefix = @prefix@
exec_prefix = @exec_prefix@
datarootdir = @datarootdir@
datadir = @datadir@
bindir = @bindir@
mandir = @mandir@
sysconfdir = @sysconfdir@
docdir = @docdir@
localedir = @localedir@
optbindirs = @optbindirs@
#
# Various flags
#
MACROS = -DLOCALEDIR=\"$(localedir)\" -DPREFIX=L\"$(prefix)\" -DDATADIR=L\"$(datadir)\" -DSYSCONFDIR=L\"$(sysconfdir)\" -DBINDIR=L\"$(bindir)\"
CXXFLAGS = @CXXFLAGS@ $(MACROS) $(EXTRA_CXXFLAGS)
LDFLAGS = @LIBS@ @LDFLAGS@
LDFLAGS_FISH = ${LDFLAGS} @LIBS_FISH@ @LDFLAGS_FISH@
LDFLAGS_FISH_INDENT = ${LDFLAGS} @LIBS_FISH_INDENT@
LDFLAGS_FISH_PAGER = ${LDFLAGS} @LIBS_FISH_PAGER@
LDFLAGS_FISHD = ${LDFLAGS} @LIBS_FISHD@
LDFLAGS_MIMEDB = ${LDFLAGS} @LIBS_MIMEDB@
#
# Set to 1 if we have gettext
#
HAVE_GETTEXT=@HAVE_GETTEXT@
#
#Additional .cpp files used by common.o. These also have a corresponding
#.h file.
#
COMMON_FILES := util.cpp fallback.cpp
#
# All objects that the system needs to build fish, except fish.o
#
FISH_OBJS := function.o builtin.o complete.o env.o exec.o expand.o \
highlight.o history.o kill.o parser.o proc.o reader.o sanity.o \
tokenizer.o wildcard.o wgetopt.o wutil.o input.o output.o intern.o \
env_universal.o env_universal_common.o input_common.o event.o \
signal.o io.o parse_util.o common.o screen.o path.o autoload.o \
parser_keywords.o iothread.o color.o postfork.o \
builtin_test.o
FISH_INDENT_OBJS := fish_indent.o print_help.o common.o \
parser_keywords.o wutil.o tokenizer.o
#
# Additional files used by builtin.o
#
BUILTIN_FILES := builtin_set.cpp builtin_commandline.cpp \
builtin_ulimit.cpp builtin_complete.cpp builtin_jobs.cpp \
builtin_set_color.cpp builtin_printf.cpp
#
# All objects that the system needs to build fish_pager
#
FISH_PAGER_OBJS := fish_pager.o output.o wutil.o \
input_common.o env_universal.o env_universal_common.o common.o \
print_help.o iothread.o color.o
#
# All objects that the system needs to build fish_tests
#
FISH_TESTS_OBJS := $(FISH_OBJS) fish_tests.o
#
# All objects that the system needs to build fishd
#
FISHD_OBJS := fishd.o env_universal_common.o wutil.o print_help.o \
common.o
#
# All objects needed to build mimedb
#
MIME_OBJS := mimedb.o print_help.o xdgmimealias.o xdgmime.o \
xdgmimeglob.o xdgmimeint.o xdgmimemagic.o xdgmimeparent.o wutil.o \
common.o
#
# Files containing user documentation
#
#
# These files are the source files, they contain a few @FOO@-style substitutions
#
HDR_FILES_SRC := doc_src/index.hdr.in doc_src/commands.hdr.in doc_src/design.hdr doc_src/license.hdr doc_src/faq.hdr
#
# These are the generated result files
#
HDR_FILES := $(HDR_FILES_SRC:.hdr.in=.hdr)
# Use a pattern rule so that Make knows to only issue one invocation
# per http://www.gnu.org/software/make/manual/make.html#Pattern-Intro
# Internalized scripts are currently disabled.
# For now, we just generate empty arrays.
# To generate them again, you would run this:
# ./internalize_scripts.py share/functions/*.fish share/completions/*.fish
#
# Files containing documentation for external commands.
#
HELP_SRC := $(wildcard doc_src/*.txt)
#
# Files in the test directory
#
TEST_IN := $(wildcard tests/test*.in)
#
# Files that should be added to the tar archives
#
#
# Files in ./doc_src/
#
DOC_SRC_DIR_FILES := $(HDR_FILES_SRC) $(HELP_SRC)
#
# Files in ./
#
MAIN_DIR_FILES_UNSORTED := Doxyfile Doxyfile.user Doxyfile.help \
Makefile.in configure configure.ac config.h.in install-sh \
key_reader.cpp $(MIME_OBJS:.o=.h) \
$(MIME_OBJS:.o=.cpp) $(FISH_OBJS:.o=.h) $(BUILTIN_FILES) \
$(COMMON_FILES) $(COMMON_FILES:.cpp=.h) $(FISH_OBJS:.o=.cpp) \
fish.spec.in INSTALL README user_doc.head.html \
ChangeLog config.sub config.guess fish_tests.cpp fish.cpp fish_pager.cpp \
fishd.cpp make_vcs_completions.fish $(FISH_INDENT_OBJS:.o=.cpp)
#
# The sorting is not meaningful in itself, but it has the side effect
# of removing duplicates, which means there will be fewer warnings
# during building.
#
MAIN_DIR_FILES := $(sort $(MAIN_DIR_FILES_UNSORTED))
#
# Files in ./tests/
#
TESTS_DIR_FILES := $(TEST_IN) $(TEST_IN:.in=.out) $(TEST_IN:.in=.err) \
$(TEST_IN:.in=.status) tests/test.fish tests/gen_output.fish
#
# Files in ./share/completions/
#
COMPLETIONS_DIR_FILES := $(wildcard share/completions/*.fish)
#
# Files in ./share/functions/
#
FUNCTIONS_DIR_FILES := $(wildcard share/functions/*.fish)
#
# Programs to install
#
PROGRAMS := fish mimedb fish_pager fishd fish_indent
#
# Manual pages to install
#
MANUALS := $(addsuffix .1, $(addprefix share/man/man1/, \
$(PROGRAMS)))
#
# All translation message catalogs
#
TRANSLATIONS_SRC := $(wildcard po/*.po)
TRANSLATIONS := $(TRANSLATIONS_SRC:.po=.gmo)
#
# Make everything needed for installing fish
#
all: $(PROGRAMS) user_doc share/man $(TRANSLATIONS)
@echo fish has now been built.
@echo Use \'$(MAKE) install\' to install fish.
.PHONY: all
#
# These dependencies make sure that autoconf and configure are run
# when the source code for the build configuration has changed.
#
configure: configure.ac
./config.status --recheck
Makefile: Makefile.in configure
./config.status
#
# Build fish with some debug flags specified. This is GCC specific,
# and should only be used when debuging fish.
#
prof: EXTRA_CXXFLAGS += -pg
prof: LDFLAGS += -pg
prof: all
.PHONY: prof
#
# User documentation, describing the features of the fish shell.
#
# Depend on the sources (*.hdr.in) and manually make the
# intermediate *.hdr and doc.h files if needed
# Allow doxygen to fail, e.g. if it does not exist
user_doc: $(HDR_FILES_SRC) Doxyfile.user user_doc.head.html $(HELP_SRC) doc.h $(HDR_FILES)
- (cat Doxyfile.user ; echo PROJECT_NUMBER=@PACKAGE_VERSION@) | doxygen - && touch user_doc
#
# Source code documentation. Also includes user documentation.
#
doc: *.h *.cpp doc.h Doxyfile
(cat Doxyfile ; echo PROJECT_NUMBER=@PACKAGE_VERSION@) | doxygen - ;
#
# PDF version of the source code documentation.
#
doc/refman.pdf: doc
cd doc/latex;
make;
mv refman.pdf ..;
cd ../..;
rm -r doc/latex;
#
# This target runs both the low level code tests and the high level script tests.
#
test: $(PROGRAMS) fish_tests
./fish_tests; cd tests; ../fish <test.fish;
.PHONY: test
#
# commands.hdr collects documentation on all commands, functions and
# builtins
#
doc_src/commands.hdr:$(HELP_SRC) doc_src/commands.hdr.in
-rm command_list.tmp $@
for i in `printf "%s\n" $(HELP_SRC)|sort`; do \
echo "<hr>" >>command_list.tmp; \
cat $$i >>command_list.tmp; \
echo >>command_list.tmp; \
echo >>command_list.tmp; \
echo "Back to <a href='index.html#toc-commands'>index</a>". >>command_list.tmp; \
done
mv command_list.tmp command_list.txt
cat $@.in | awk '{if ($$0 ~ /@command_list@/){ system("cat command_list.txt");} else{ print $$0;}}' >$@
toc.txt: $(HDR_FILES:index.hdr=index.hdr.in)
-rm toc.tmp $@
for i in $(HDR_FILES:index.hdr=index.hdr.in); do\
NAME=`basename $$i .hdr`; \
NAME=`basename $$NAME .hdr.in`; \
sed <$$i >>toc.tmp -n \
-e 's,.*\\page *\([^ ]*\) *\(.*\)$$,- <a href="'$$NAME'.html" id="toc-'$$NAME'">\2</a>,p' \
-e 's,.*\\section *\([^ ]*\) *\(.*\)$$, - <a href="'$$NAME'.html#\1">\2</a>,p'; \
done
mv toc.tmp $@
doc_src/index.hdr: toc.txt doc_src/index.hdr.in
cat $@.in | awk '{if ($$0 ~ /@toc@/){ system("cat toc.txt");} else{ print $$0;}}' >$@
#
# doc.h is a compilation of the various snipptes of text used both for
# the user documentation and for internal help functions into a single
# file that can be parsed dy Doxygen to generate the user
# documentation.
#
doc.h: $(HDR_FILES)
cat $(HDR_FILES) >$@
#
# This rule creates complete doxygen headers from each of the various
# snipptes of text used both for the user documentation and for
# internal help functions, that can be parsed to Doxygen to generate
# the internal help function text.
#
%.doxygen:%.txt
echo "/** \page " `basename $*` >$@;
cat $*.txt >>$@;
echo "*/" >>$@
%: %.in
sed <$@.in >$@ \
-e "s,@sysconfdir\@,$(sysconfdir),g" \
-e "s,@datadir\@,$(datadir),g" \
-e "s,@docdir\@,$(docdir),g" \
-e "s|@configure_input\@|$@, generated from $@.in by the Makefile. DO NOT MANUALLY EDIT THIS FILE!|g" \
-e "s,@prefix\@,$(prefix),g" \
-e "s,@optbindirs\@,$(optbindirs),g"
#-e "s,@\@,$(),"
#
# Compile translation files to binary format
#
%.gmo:
if test "$(HAVE_GETTEXT)" = 1; then \
msgfmt -o $*.gmo $*.po; \
fi
#
# Update existing po file or copy messages.pot
#
%.po:messages.pot
if test $(HAVE_GETTEXT) = 1;then \
if test -f $*.po; then \
msgmerge -U --backup=existing $*.po messages.pot;\
else \
cp messages.pot $*.po;\
fi; \
fi
#
# Create a template translation object
#
messages.pot: *.cpp *.h share/completions/*.fish share/functions/*.fish
if test $(HAVE_GETTEXT) = 1;then \
xgettext -k_ -kN_ *.cpp *.h -o messages.pot; \
if xgettext -j -k_ -kN_ -k--description -LShell share/completions/*.fish share/functions/*.fish -o messages.pot; then true; else \
echo "Your xgettext version is too old to build the messages.pot file"\
rm messages.pot\
false;\
fi; \
fi
builtin.o: $(BUILTIN_FILES)
common.o: $(COMMON_FILES)
#
# Generate the internal help functions by making doxygen create
# man-pages. The convertion path looks like this:
#
# .txt file
# ||
# (make)
# ||
# \/
# .doxygen file
# ||
# (doxygen)
# ||
# \/
# roff file
# ||
# (__fish_print_help)
# ||
# \/
# formated text
# with escape
# sequences
#
#
# There ought to be something simpler.
#
share/man: $(HELP_SRC)
-mkdir share/man
touch share/man
-rm -Rf share/man/man1
./build_tools/build_documentation.sh Doxyfile.help ./doc_src ./share
#
# The build rules for installing/uninstalling fish
#
#
# Check for an incompatible installed fish version, and fail with an
# error if found
#
check-uninstall:
if test -f $(DESTDIR)$(sysconfdir)/fish.d/fish_function.fish -o -f $(DESTDIR)$(sysconfdir)/fish.d/fish_complete.fish; then \
echo;\
echo ERROR;\
echo;\
echo An older fish installation using an incompatible filesystem hierarchy was detected;\
echo You must uninstall this fish version before proceeding;\
echo type \'$(MAKE) uninstall-legacy\' to uninstall these files,;\
echo or type \'$(MAKE) force-install\' to force installation.;\
echo The latter may result in a broken installation.;\
echo;\
false;\
fi;
if test -f $(DESTDIR)$(sysconfdir)/fish; then \
echo;\
echo ERROR;\
echo;\
echo An older fish installation using an incompatible filesystem hierarchy was detected;\
echo You must remove the file $(DESTDIR)$(sysconfdir)/fish before proceeding;\
echo type \'$(MAKE) uninstall-legacy\' to uninstall this file,;\
echo or remove it manually using \'rm $(DESTDIR)$(sysconfdir)/fish\'.;\
echo;\
false;\
fi;
.PHONY: check-uninstall
check-legacy-binaries:
@SEQLOC=$(prefix)/bin/seq;\
if test -f "$$SEQLOC" && grep -q '\(^#!/.*/fish\|^#!/usr/bin/env fish\)' "$$SEQLOC"; then\
echo "An outdated seq from a previous fish install was found. You should remove it with:";\
echo " rm '$$SEQLOC'";\
fi;
@SETCOLOR_LOC=$(prefix)/bin/set_color;\
if test -x "$$SETCOLOR_LOC" && $$SETCOLOR_LOC -v 2>&1 >/dev/null | grep -q "^set_color, version "; then\
echo "An outdated set_color from a previous fish install was found. You should remove it with:";\
echo " rm '$$SETCOLOR_LOC'";\
fi;
@true;
.PHONY: check-legacy-binaries
#
# This check makes sure that the install-sh script is executable. The
# darcs repo doesn't preserve the executable bit, so this needs to be
# run after checkout.
#
install-sh:
if test -x $@; then true; else chmod 755 $@; fi
.PHONY: install-sh
#
# Try to install after checking for incompatible installed versions.
#
install: all install-sh check-uninstall install-force check-legacy-binaries
.PHONY: install
#
# Xcode install
#
xcode-install:
rm -Rf /tmp/fish_build;\
xcodebuild install DSTROOT=/tmp/fish_build;\
ditto /tmp/fish_build /
.PHONY: xcode-install
#
# Force installation, even in presense of incompatible previous
# version. This may fail.
# These 'true' lines are to prevent installs from failing for (e.g.) missing man pages.
#
install-force: all install-translations
$(INSTALL) -m 755 -d $(DESTDIR)$(bindir)
for i in $(PROGRAMS); do\
$(INSTALL) -m 755 $$i $(DESTDIR)$(bindir) ; \
true ;\
done;
$(INSTALL) -m 755 -d $(DESTDIR)$(sysconfdir)/fish
$(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish
$(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish/completions
$(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish/functions
$(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish/man/man1
$(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish/tools
$(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish/tools/web_config
$(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/fish/tools/web_config/sample_prompts
$(INSTALL) -m 644 etc/config.fish $(DESTDIR)$(sysconfdir)/fish/
$(INSTALL) -m 644 share/config.fish $(DESTDIR)$(datadir)/fish/
for i in $(COMPLETIONS_DIR_FILES:%='%'); do \
$(INSTALL) -m 644 $$i $(DESTDIR)$(datadir)/fish/completions/; \
true; \
done;
for i in $(FUNCTIONS_DIR_FILES:%='%'); do \
$(INSTALL) -m 644 $$i $(DESTDIR)$(datadir)/fish/functions/; \
true; \
done;
for i in share/man/man1/*.1; do \
$(INSTALL) -m 644 $$i $(DESTDIR)$(datadir)/fish/man/man1/; \
true; \
done;
for i in share/tools/*.py; do\
$(INSTALL) -m 755 $$i $(DESTDIR)$(datadir)/fish/tools/; \
true; \
done;
for i in share/tools/web_config/*; do\
$(INSTALL) -m 644 $$i $(DESTDIR)$(datadir)/fish/tools/web_config/; \
true; \
done;
for i in share/tools/web_config/sample_prompts/*.fish; do\
$(INSTALL) -m 644 $$i $(DESTDIR)$(datadir)/fish/tools/web_config/sample_prompts/; \
true; \
done;
for i in share/tools/web_config/*.py; do\
$(INSTALL) -m 755 $$i $(DESTDIR)$(datadir)/fish/tools/web_config/; \
true; \
done;
$(INSTALL) -m 755 -d $(DESTDIR)$(docdir)
for i in user_doc/html/* ChangeLog; do \
if test -f $$i; then \
$(INSTALL) -m 644 $$i $(DESTDIR)$(docdir); \
fi; \
done;
$(INSTALL) -m 755 -d $(DESTDIR)$(mandir)/man1
for i in $(MANUALS); do \
$(INSTALL) -m 644 $$i $(DESTDIR)$(mandir)/man1/; \
true; \
done;
@echo fish is now installed on your system.
@echo To run fish, type \'fish\' in your terminal.
@echo
@echo To use fish as your login shell:
@grep -q -- "$(DESTDIR)$(bindir)/fish" /etc/shells || echo \* add the line \'$(DESTDIR)$(bindir)/fish\' to the file \'/etc/shells\'.
@echo \* use the command \'chsh -s $(DESTDIR)$(bindir)/fish\'.
@echo
@echo To set your colors, run \'fish_config\'
@echo To scan your man pages for completions, run \'fish_update_completions\'
@echo To autocomplete command suggestions press Ctrl + F or right arrow key.
@echo
@echo Have fun!
.PHONY: install-force
#
# Uninstall this fish version
#
uninstall: uninstall-translations
-for i in $(PROGRAMS); do \
rm -f $(DESTDIR)$(bindir)/$$i; \
done;
-rm -rf $(DESTDIR)$(sysconfdir)/fish
-if test -d $(DESTDIR)$(datadir)/fish; then \
rm -r $(DESTDIR)$(datadir)/fish; \
fi
-if test -d $(DESTDIR)$(docdir); then \
rm -rf $(DESTDIR)$(docdir);\
fi
-for i in $(MANUALS); do \
rm -rf $(DESTDIR)$(mandir)/man1/`basename $$i`*; \
done;
.PHONY: uninstall
#
# Uninstall an older fish release. This is not the default uninstall
# since there is a slight chance that it removes a file put in place by
# the sysadmin. But if 'make install' detects a file confligt, it
# suggests using this target.
#
uninstall-legacy: uninstall
-rm -f $(DESTDIR)$(sysconfdir)/fish.d/fish_interactive.fish
-rm -f $(DESTDIR)$(sysconfdir)/fish.d/fish_complete.fish
-rm -f $(DESTDIR)$(sysconfdir)/fish.d/fish_function.fish
-rm -f $(DESTDIR)$(sysconfdir)/fish/fish_inputrc
-if test -d $(DESTDIR)$(sysconfdir)/fish.d/completions; then \
for i in $(COMPLETIONS_DIR_FILES); do \
basename=`basename $$i`; \
if test -f $(DESTDIR)$(sysconfdir)/fish.d/completions/$$basename; then \
rm $(DESTDIR)$(sysconfdir)/fish.d/completions/$$basename; \
fi; \
done; \
fi;
-rmdir $(DESTDIR)$(sysconfdir)/fish.d/completions
-rmdir $(DESTDIR)$(sysconfdir)/fish.d
-rm $(DESTDIR)$(sysconfdir)/fish
@echo The previous fish installation has been removed.
.PHONY: uninstall-legacy
install-translations: $(TRANSLATIONS)
if test $(HAVE_GETTEXT) = 1; then \
for i in $(TRANSLATIONS); do \
$(INSTALL) -m 755 -d $(DESTDIR)$(datadir)/locale/`basename $$i .gmo`/LC_MESSAGES; \
$(INSTALL) -m 644 $$i $(DESTDIR)$(datadir)/locale/`basename $$i .gmo`/LC_MESSAGES/fish.mo; \
echo $(DESTDIR)$(datadir)/locale/`basename $$i .gmo`/LC_MESSAGES/fish.mo;\
done; \
fi;
.PHONY: install-translations
uninstall-translations:
if test $(HAVE_GETTEXT) = 1; then \
for i in $(TRANSLATIONS_SRC); do \
rm -f $(DESTDIR)$(datadir)/locale/*/LC_MESSAGES/fish.mo; \
done; \
fi
.PHONY: uninstall-translations
#
# The build rules for all the commands
#
#
# Build the fish program.
#
fish: $(FISH_OBJS) fish.o
$(CXX) $(FISH_OBJS) fish.o $(LDFLAGS_FISH) -o $@
#
# Build the fish_pager program.
#
fish_pager: $(FISH_PAGER_OBJS)
$(CXX) $(FISH_PAGER_OBJS) $(LDFLAGS_FISH_PAGER) -o $@
#
# Build the fishd program.
#
fishd: $(FISHD_OBJS)
$(CXX) $(FISHD_OBJS) $(LDFLAGS_FISHD) -o $@
#
# Build the fish_tests program.
#
fish_tests: $(FISH_TESTS_OBJS)
$(CXX) $(FISH_TESTS_OBJS) $(LDFLAGS_FISH) -o $@
#
# Build the mimedb program.
#
# mimedb does not need any libraries, so we don't use LDFLAGS here.
#
mimedb: $(MIME_OBJS)
$(CXX) $(MIME_OBJS) $(LDFLAGS_MIMEDB) -o $@
#
# Build the fish_indent program.
#
fish_indent: $(FISH_INDENT_OBJS)
$(CXX) $(FISH_INDENT_OBJS) $(LDFLAGS_FISH_INDENT) -o $@
#
# Neat little program to show output from terminal
#
key_reader: key_reader.o input_common.o common.o env_universal.o env_universal_common.o wutil.o iothread.o
$(CXX) key_reader.o input_common.o common.o env_universal.o env_universal_common.o wutil.o iothread.o $(LDFLAGS_FISH) -o $@
#
# Update dependencies
#
depend:
makedepend -fMakefile.in -Y *.cpp
./config.status
.PHONY: depend
#
# Make compressed tar archives
#
fish-@PACKAGE_VERSION@.tar.gz: fish-@PACKAGE_VERSION@.tar
gzip -f --best -c fish-@PACKAGE_VERSION@.tar >fish-@PACKAGE_VERSION@.tar.gz
fish-@PACKAGE_VERSION@.tar.bz2: fish-@PACKAGE_VERSION@.tar
bzip2 -f --best -k fish-@PACKAGE_VERSION@.tar
dist: fish-@PACKAGE_VERSION@.tar.bz2
.PHONY: dist
#
# Build the RPM spec file.
#
fish.spec: fish.spec.in
./config.status
#
# Create .rpm file for the current systems architecture and an
# .src.rpm file.
#
rpm: fish-@PACKAGE_VERSION@.tar.bz2 fish.spec
@if which rpmbuild; then true; else \
echo Could not find the rpmbuild command, needed to build an rpm; \
echo You may be able to install it using the following command:; \
echo \'yum install rpm-build\'; \
false; \
fi
cp fish.spec /usr/src/redhat/SPECS/
cp fish-@PACKAGE_VERSION@.tar.bz2 /usr/src/redhat/SOURCES/
rpmbuild -ba --clean /usr/src/redhat/SPECS/fish.spec
mv /usr/src/redhat/RPMS/*/fish*@PACKAGE_VERSION@*.rpm .
mv /usr/src/redhat/SRPMS/fish*@PACKAGE_VERSION@*.src.rpm .
.PHONY: rpm
#
# Cleanup targets
#
#
# distclean should restore the tree to the state right after extracting a tarball.
#
distclean: clean
rm -f fish.spec
rm -f config.status config.log config.h Makefile
.PHONY: distclean
#
# clean removes everything built by the makefile, but not things that
# are created by the configure script.
#
# Don't delete the docs unless we have Doxygen installed
# We provide pre-built docs in the tarball, and if they get
# deleted we won't be able to regenerate them
clean:
rm -f *.o doc.h doc.tmp doc_src/*.doxygen doc_src/*.cpp doc_src/*.o doc_src/commands.hdr
rm -f $(GENERATED_INTERN_SCRIPT_FILES)
rm -f tests/tmp.err tests/tmp.out tests/tmp.status tests/foo.txt
rm -f $(PROGRAMS) fish_tests key_reader
rm -f command_list.txt toc.txt
rm -f doc_src/index.hdr doc_src/commands.hdr
rm -f fish-@PACKAGE_VERSION@.tar
rm -f fish-@PACKAGE_VERSION@.tar.gz
rm -f fish-@PACKAGE_VERSION@.tar.bz2
if command -v doxygen; then \
rm -rf doc user_doc share/man; \
fi
rm -rf fish-@PACKAGE_VERSION@
rm -f $(TRANSLATIONS)
.PHONY: clean
# DO NOT DELETE THIS LINE -- make depend depends on it.
autoload.o: config.h autoload.h common.h util.h lru.h wutil.h signal.h env.h
autoload.o: exec.h proc.h io.h
builtin.o: config.h signal.h fallback.h util.h wutil.h common.h builtin.h
builtin.o: io.h function.h event.h complete.h proc.h parser.h reader.h env.h
builtin.o: wgetopt.h sanity.h tokenizer.h wildcard.h expand.h input_common.h
builtin.o: input.h intern.h exec.h highlight.h screen.h color.h parse_util.h
builtin.o: autoload.h lru.h parser_keywords.h path.h history.h
builtin.o: builtin_set.cpp builtin_commandline.cpp builtin_complete.cpp
builtin.o: builtin_ulimit.cpp builtin_jobs.cpp builtin_printf.cpp
builtin_commandline.o: config.h signal.h fallback.h util.h wutil.h common.h
builtin_commandline.o: builtin.h io.h wgetopt.h reader.h complete.h proc.h
builtin_commandline.o: parser.h event.h function.h tokenizer.h input_common.h
builtin_commandline.o: input.h parse_util.h autoload.h lru.h
builtin_complete.o: config.h signal.h fallback.h util.h wutil.h common.h
builtin_complete.o: builtin.h io.h complete.h wgetopt.h parser.h proc.h
builtin_complete.o: event.h function.h reader.h
builtin_jobs.o: config.h fallback.h signal.h util.h wutil.h common.h
builtin_jobs.o: builtin.h io.h proc.h parser.h event.h function.h wgetopt.h
builtin_set.o: config.h signal.h fallback.h util.h wutil.h common.h builtin.h
builtin_set.o: io.h env.h expand.h wgetopt.h proc.h parser.h event.h
builtin_set.o: function.h
builtin_test.o: config.h common.h util.h builtin.h io.h wutil.h proc.h
builtin_test.o: signal.h
builtin_ulimit.o: config.h fallback.h signal.h util.h builtin.h io.h common.h
builtin_ulimit.o: wgetopt.h
builtin_printf.o: wgetopt.h
color.o: color.h config.h common.h util.h fallback.h signal.h
common.o: config.h fallback.h signal.h util.h wutil.h common.h expand.h
common.o: proc.h io.h wildcard.h parser.h event.h function.h complete.h
common.o: util.cpp fallback.cpp
complete.o: config.h signal.h fallback.h util.h tokenizer.h common.h
complete.o: wildcard.h expand.h proc.h io.h parser.h event.h function.h
complete.o: complete.h builtin.h env.h exec.h reader.h history.h wutil.h
complete.o: intern.h parse_util.h autoload.h lru.h parser_keywords.h path.h
env.o: config.h signal.h fallback.h util.h wutil.h common.h proc.h io.h env.h
env.o: sanity.h expand.h history.h reader.h complete.h parser.h event.h
env.o: function.h env_universal.h env_universal_common.h input.h
env.o: input_common.h path.h
env_universal.o: config.h signal.h fallback.h util.h common.h wutil.h
env_universal.o: env_universal_common.h env_universal.h
env_universal_common.o: config.h signal.h fallback.h util.h common.h wutil.h
env_universal_common.o: env_universal_common.h
event.o: config.h signal.h fallback.h util.h wutil.h common.h function.h
event.o: event.h proc.h io.h parser.h
exec.o: config.h signal.h fallback.h util.h iothread.h postfork.h common.h
exec.o: proc.h io.h wutil.h exec.h parser.h event.h function.h builtin.h
exec.o: env.h wildcard.h expand.h sanity.h parse_util.h autoload.h lru.h
expand.o: config.h signal.h fallback.h util.h common.h wutil.h env.h proc.h
expand.o: io.h parser.h event.h function.h expand.h wildcard.h exec.h
expand.o: tokenizer.h complete.h parse_util.h autoload.h lru.h
fallback.o: config.h fallback.h signal.h util.h
fish.o: config.h signal.h fallback.h util.h common.h reader.h io.h complete.h
fish.o: builtin.h function.h event.h wutil.h env.h sanity.h proc.h parser.h
fish.o: expand.h intern.h exec.h output.h screen.h color.h history.h path.h
fish_indent.o: config.h fallback.h signal.h util.h common.h wutil.h
fish_indent.o: tokenizer.h print_help.h parser_keywords.h
fish_pager.o: config.h signal.h fallback.h util.h wutil.h common.h complete.h
fish_pager.o: output.h screen.h color.h input_common.h env_universal.h
fish_pager.o: env_universal_common.h print_help.h
fish_tests.o: config.h signal.h fallback.h util.h common.h proc.h io.h
fish_tests.o: reader.h complete.h builtin.h function.h event.h autoload.h
fish_tests.o: lru.h wutil.h env.h expand.h parser.h tokenizer.h output.h
fish_tests.o: screen.h color.h exec.h path.h history.h highlight.h iothread.h
fish_tests.o: postfork.h
fishd.o: config.h signal.h fallback.h util.h common.h wutil.h
fishd.o: env_universal_common.h path.h env.h print_help.h
function.o: config.h signal.h wutil.h common.h util.h fallback.h function.h
function.o: event.h proc.h io.h parser.h intern.h reader.h complete.h
function.o: parse_util.h autoload.h lru.h parser_keywords.h env.h expand.h
highlight.o: config.h signal.h fallback.h util.h wutil.h common.h highlight.h
highlight.o: env.h screen.h color.h tokenizer.h proc.h io.h parser.h event.h
highlight.o: function.h parse_util.h autoload.h lru.h parser_keywords.h
highlight.o: builtin.h expand.h sanity.h complete.h output.h wildcard.h
highlight.o: path.h history.h
history.o: config.h fallback.h signal.h util.h sanity.h tokenizer.h common.h
history.o: wutil.h history.h intern.h path.h env.h autoload.h lru.h
history.o: iothread.h
input.o: config.h signal.h fallback.h util.h wutil.h common.h reader.h io.h
input.o: complete.h proc.h sanity.h input_common.h input.h parser.h event.h
input.o: function.h env.h expand.h output.h screen.h color.h intern.h
input_common.o: config.h fallback.h signal.h util.h common.h wutil.h
input_common.o: input_common.h env_universal.h env_universal_common.h
input_common.o: iothread.h
intern.o: config.h fallback.h signal.h util.h wutil.h common.h intern.h
io.o: config.h fallback.h signal.h util.h wutil.h common.h exec.h proc.h io.h
iothread.o: config.h iothread.h common.h util.h signal.h
key_reader.o: config.h common.h util.h fallback.h signal.h input_common.h
kill.o: config.h signal.h fallback.h util.h wutil.h common.h kill.h proc.h
kill.o: io.h sanity.h env.h exec.h path.h
mimedb.o: config.h xdgmime.h fallback.h signal.h util.h print_help.h
output.o: config.h signal.h fallback.h util.h wutil.h common.h expand.h
output.o: output.h screen.h color.h highlight.h env.h
parse_util.o: config.h fallback.h signal.h util.h wutil.h common.h
parse_util.o: tokenizer.h parse_util.h autoload.h lru.h expand.h intern.h
parse_util.o: exec.h proc.h io.h env.h wildcard.h
parser.o: config.h signal.h fallback.h util.h common.h wutil.h proc.h io.h
parser.o: parser.h event.h function.h parser_keywords.h tokenizer.h exec.h
parser.o: wildcard.h expand.h builtin.h env.h reader.h complete.h sanity.h
parser.o: env_universal.h env_universal_common.h intern.h parse_util.h
parser.o: autoload.h lru.h path.h
parser_keywords.o: config.h fallback.h signal.h common.h util.h
parser_keywords.o: parser_keywords.h
path.o: config.h fallback.h signal.h util.h common.h env.h wutil.h path.h
path.o: expand.h
postfork.o: signal.h postfork.h config.h common.h util.h proc.h io.h wutil.h
postfork.o: iothread.h exec.h
print_help.o: print_help.h
proc.o: config.h signal.h fallback.h util.h wutil.h common.h proc.h io.h
proc.o: reader.h complete.h sanity.h env.h parser.h event.h function.h
proc.o: output.h screen.h color.h
reader.o: config.h signal.h fallback.h util.h wutil.h common.h highlight.h
reader.o: env.h screen.h color.h reader.h io.h complete.h proc.h parser.h
reader.o: event.h function.h history.h sanity.h exec.h expand.h tokenizer.h
reader.o: kill.h input_common.h input.h output.h iothread.h intern.h path.h
reader.o: parse_util.h autoload.h lru.h
sanity.o: config.h signal.h fallback.h util.h common.h sanity.h proc.h io.h
sanity.o: history.h wutil.h reader.h complete.h kill.h
screen.o: config.h fallback.h signal.h common.h util.h wutil.h output.h
screen.o: screen.h color.h highlight.h env.h
signal.o: config.h signal.h common.h util.h fallback.h wutil.h event.h
signal.o: reader.h io.h complete.h proc.h
tokenizer.o: config.h fallback.h signal.h util.h wutil.h common.h tokenizer.h
util.o: config.h fallback.h signal.h util.h common.h wutil.h
wgetopt.o: config.h wgetopt.h wutil.h common.h util.h fallback.h signal.h
wildcard.o: config.h fallback.h signal.h util.h wutil.h common.h complete.h
wildcard.o: wildcard.h expand.h reader.h io.h exec.h proc.h
wutil.o: config.h fallback.h signal.h util.h common.h wutil.h
xdgmime.o: xdgmime.h xdgmimeint.h xdgmimeglob.h xdgmimemagic.h xdgmimealias.h
xdgmime.o: xdgmimeparent.h
xdgmimealias.o: xdgmimealias.h xdgmime.h xdgmimeint.h
xdgmimeglob.o: xdgmimeglob.h xdgmime.h xdgmimeint.h
xdgmimeint.o: xdgmimeint.h xdgmime.h
xdgmimemagic.o: xdgmimemagic.h xdgmime.h xdgmimeint.h
xdgmimeparent.o: xdgmimeparent.h xdgmime.h xdgmimeint.h

71
README.md Normal file
View File

@@ -0,0 +1,71 @@
[fish](http://ridiculousfish.com/shell/) - the friendly interactive shell
================================================
fish is a smart and user-friendly command line shell for OS X, Linux, and the rest of the family. fish includes features like syntax highlighting, autosuggest-as-you-type, and fancy tab completions that just work, with no configuration required.
For more on fish's design philosophy, see the [design document](http://ridiculousfish.com/shell/user_doc/html/design.html).
## Quick Start
fish generally works like other shells, like bash or zsh. A few important differences are documented at <http://ridiculousfish.com/shell/faq.html>
Detailed user documentation is available by running `help` within fish, and also at <http://ridiculousfish.com/shell/user_doc/html/>
## Building
fish is written in a sane subset of C++98, with a few components from C++TR1. It builds successfully with g++ 4.2 or later, and with clang. It also will build as C++11.
fish can be built using autotools or Xcode.
### Autotools Build
autoconf
./configure
make [gmake on BSD]
sudo make install
### Xcode Development Build
* Build the `base` target in Xcode
* Run the fish executable, for example, in `DerivedData/fish/Build/Products/Debug/base/bin/fish`
### Xcode Build and Install
xcodebuild install
sudo ditto /tmp/fish.dst /
## Help, it didn't build!
If fish reports that it could not find curses, try installing a curses development package and build again.
On Debian or Ubuntu you want:
sudo apt-get install libncurses5-dev libncursesw5-dev
on RedHat, CentOS, or Amazon EC2:
sudo yum install ncurses-devel
## Packages for Linux
Nightly builds for several Linux distros can be downloaded from <http://download.opensuse.org/repositories/home:/siteshwar/>
## Switching to fish
If you wish to use fish as your default shell, use the following command:
chsh -s /usr/local/bin/fish
chsh will prompt you for your password, and change your default shell.
To switch your default shell back, you can run:
chsh -s /bin/bash
Substitute /bin/bash with /bin/tcsh or /bin/zsh as appropriate.
## Contact Us
Questions, comments, rants and raves can be posted to the official fish mailing list at <https://lists.sourceforge.net/lists/listinfo/fish-users> or join us on our IRC channel #fish at irc.oftc.net
Found a bug? Have an awesome idea? Please open an issue on this github page.

View File

@@ -1,249 +0,0 @@
.. |Cirrus CI| image:: https://api.cirrus-ci.com/github/fish-shell/fish-shell.svg?branch=master
:target: https://cirrus-ci.com/github/fish-shell/fish-shell
:alt: Cirrus CI Build Status
`fish <https://fishshell.com/>`__ - the friendly interactive shell |Build Status| |Cirrus CI|
=================================================================================
fish is a smart and user-friendly command line shell for macOS, Linux,
and the rest of the family. fish includes features like syntax
highlighting, autosuggest-as-you-type, and fancy tab completions that
just work, with no configuration required.
For downloads, screenshots and more, go to https://fishshell.com/.
Quick Start
-----------
fish generally works like other shells, like bash or zsh. A few
important differences can be found at
https://fishshell.com/docs/current/tutorial.html by searching for the
magic phrase “unlike other shells”.
Detailed user documentation is available by running ``help`` within
fish, and also at https://fishshell.com/docs/current/index.html
Getting fish
------------
macOS
~~~~~
fish can be installed:
- using `Homebrew <http://brew.sh/>`__: ``brew install fish``
- using `MacPorts <https://www.macports.org/>`__:
``sudo port install fish``
- using the `installer from fishshell.com <https://fishshell.com/>`__
- as a `standalone app from fishshell.com <https://fishshell.com/>`__
Note: The minimum supported macOS version is 10.10 "Yosemite".
Packages for Linux
~~~~~~~~~~~~~~~~~~
Packages for Debian, Fedora, openSUSE, and Red Hat Enterprise
Linux/CentOS are available from the `openSUSE Build
Service <https://software.opensuse.org/download.html?project=shells%3Afish&package=fish>`__.
Packages for Ubuntu are available from the `fish
PPA <https://launchpad.net/~fish-shell/+archive/ubuntu/release-3>`__,
and can be installed using the following commands:
::
sudo apt-add-repository ppa:fish-shell/release-3
sudo apt update
sudo apt install fish
Instructions for other distributions may be found at
`fishshell.com <https://fishshell.com>`__.
Windows
~~~~~~~
- On Windows 10, fish can be installed under the WSL Windows Subsystem
for Linux with the instructions for the appropriate distribution
listed above under “Packages for Linux”, or from source with the
instructions below.
- Fish can also be installed on all versions of Windows using
`Cygwin <https://cygwin.com/>`__ (from the **Shells** category).
Building from source
~~~~~~~~~~~~~~~~~~~~
If packages are not available for your platform, GPG-signed tarballs are
available from `fishshell.com <https://fishshell.com/>`__ and
`fish-shell on
GitHub <https://github.com/fish-shell/fish-shell/releases>`__. See the
`Building <#building>`__ section for instructions.
Running fish
------------
Once installed, run ``fish`` from your current shell to try fish out!
Dependencies
~~~~~~~~~~~~
Running fish requires:
- curses or ncurses (preinstalled on most \*nix systems)
- some common \*nix system utilities (currently ``mktemp``), in
addition to the basic POSIX utilities (``cat``, ``cut``, ``dirname``,
``ls``, ``mkdir``, ``mkfifo``, ``rm``, ``sort``, ``tee``, ``tr``,
``uname`` and ``sed`` at least, but the full coreutils plus ``find`` and
``awk`` is preferred)
- The gettext library, if compiled with
translation support
The following optional features also have specific requirements:
- builtin commands that have the ``--help`` option or print usage
messages require ``nroff`` or ``mandoc`` for
display
- automated completion generation from manual pages requires Python 3.5+
- the ``fish_config`` web configuration tool requires Python 3.5+ and a web browser
- system clipboard integration (with the default Ctrl-V and Ctrl-X
bindings) require either the ``xsel``, ``xclip``,
``wl-copy``/``wl-paste`` or ``pbcopy``/``pbpaste`` utilities
- full completions for ``yarn`` and ``npm`` require the
``all-the-package-names`` NPM module
- ``colorls`` is used, if installed, to add color when running ``ls`` on platforms
that do not have color support (such as OpenBSD)
Switching to fish
~~~~~~~~~~~~~~~~~
If you wish to use fish as your default shell, use the following
command:
::
chsh -s /usr/local/bin/fish
``chsh`` will prompt you for your password and change your default
shell. (Substitute ``/usr/local/bin/fish`` with whatever path fish was
installed to, if it differs.) Log out, then log in again for the changes
to take effect.
Use the following command if fish isnt already added to ``/etc/shells``
to permit fish to be your login shell:
::
echo /usr/local/bin/fish | sudo tee -a /etc/shells
To switch your default shell back, you can run ``chsh -s /bin/bash``
(substituting ``/bin/bash`` with ``/bin/tcsh`` or ``/bin/zsh`` as
appropriate).
Building
--------
.. _dependencies-1:
Dependencies
~~~~~~~~~~~~
Compiling fish requires:
- a C++11 compiler (g++ 4.8 or later, or clang 3.3 or later)
- CMake (version 3.5 or later)
- a curses implementation such as ncurses (headers and libraries)
- PCRE2 (headers and libraries) - optional, this will be downloaded if missing
- gettext (headers and libraries) - optional, for translation support
Sphinx is also optionally required to build the documentation from a
cloned git repository.
Additionally, running the test suite requires Python 3.5+ and the pexpect package.
Building from source (all platforms) - Makefile generator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To install into ``/usr/local``, run:
.. code:: bash
mkdir build; cd build
cmake ..
make
sudo make install
The install directory can be changed using the
``-DCMAKE_INSTALL_PREFIX`` parameter for ``cmake``.
Building from source (macOS) - Xcode
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Note: The minimum supported macOS version is 10.10 "Yosemite".
.. code:: bash
mkdir build; cd build
cmake .. -G Xcode
An Xcode project will now be available in the ``build`` subdirectory.
You can open it with Xcode, or run the following to build and install in
``/usr/local``:
.. code:: bash
xcodebuild
xcodebuild -scheme install
The install directory can be changed using the
``-DCMAKE_INSTALL_PREFIX`` parameter for ``cmake``.
Build options
~~~~~~~~~~~~~
In addition to the normal cmake build options (like ``CMAKE_INSTALL_PREFIX``), fish has some other options available to customize it.
- BUILD_DOCS=ON|OFF - whether to build the documentation. This is automatically set to OFF when sphinx isn't installed.
- INSTALL_DOCS=ON|OFF - whether to install the docs. This is automatically set to on when BUILD_DOCS is or prebuilt documentation is available (like when building in-tree from a tarball).
- FISH_USE_SYSTEM_PCRE2=ON|OFF - whether to use an installed pcre2. This is normally autodetected.
- MAC_CODESIGN_ID=String|OFF - the codesign ID to use on Mac, or "OFF" to disable codesigning.
- WITH_GETTEXT=ON|OFF - whether to build with gettext support for translations.
Note that fish does *not* support static linking and will attempt to error out if it detects it.
Help, it didnt build!
~~~~~~~~~~~~~~~~~~~~~~
If fish reports that it could not find curses, try installing a curses
development package and build again.
On Debian or Ubuntu you want:
::
sudo apt install build-essential cmake ncurses-dev libncurses5-dev libpcre2-dev gettext
On RedHat, CentOS, or Amazon EC2:
::
sudo yum install ncurses-devel
Contributing Changes to the Code
--------------------------------
See the `Guide for Developers <CONTRIBUTING.rst>`__.
Contact Us
----------
Questions, comments, rants and raves can be posted to the official fish
mailing list at https://lists.sourceforge.net/lists/listinfo/fish-users
or join us on our `gitter.im
channel <https://gitter.im/fish-shell/fish-shell>`__. Or use the `fish tag
on Unix & Linux Stackexchange <https://unix.stackexchange.com/questions/tagged/fish>`__.
There is also a fish tag on Stackoverflow, but it is typically a poor fit.
Found a bug? Have an awesome idea? Please `open an
issue <https://github.com/fish-shell/fish-shell/issues/new>`__.
.. |Build Status| image:: https://github.com/fish-shell/fish-shell/workflows/make%20test/badge.svg
:target: https://github.com/fish-shell/fish-shell/actions

105
STYLEGUIDE.md Normal file
View File

@@ -0,0 +1,105 @@
# Style guide
This is style guide for fish contributors. You should use it for any new code
that you would add to this project and try to format existing code to use this
style.
## Formatting
1. fish uses the Allman/BSD style of indentation.
2. Indent with spaces, not tabs.
3. Use 4 spaces per indent (unless needed like `Makefile`).
4. Opening curly bracket is on the following line:
// ✔:
struct name
{
// code
};
void func()
{
// code
}
if (...)
{
// code
}
// ✗:
void func() {
// code
}
5. Put space after `if`, `while` and `for` before conditions.
// ✔:
if () {}
// ✗:
if() {}
6. Put spaces before and after operators excluding increment and decrement;
// ✔:
int a = 1 + 2 * 3;
a++;
// ✗:
int a=1+2*3;
a ++;
7. Never put spaces between function name and parameters list.
// ✔:
func(args);
// ✗:
func (args);
8. Never put spaces after `(` and before `)`.
9. Always put space after comma and semicolon.
// ✔:
func(arg1, arg2);
for (int i = 0; i < LENGTH; i++) {}
// ✗:
func(arg1,arg2);
for (int i = 0;i<LENGTH;i++) {}
## Documentation
Document your code using [Doxygen][dox].
1. Documentation comment should use double star notation or tripple slash:
// ✔:
/// Some var
int var;
/**
* Some func
*/
void func();
2. Use slash as tag mark:
// ✔:
/**
* \param a an integer argument.
* \param s a constant character pointer.
* \return The results
*/
int foo(int a, const char *s);
## Naming
All names in code should be `small_snake_case`. No Hungarian notation is used.
Classes and structs names should be followed by `_t`.
[dox]: http://www.stack.nl/~dimitri/doxygen/ "Doxygen homepage"

376
autoload.cpp Normal file
View File

@@ -0,0 +1,376 @@
/** \file autoload.cpp
The classes responsible for autoloading functions and completions.
*/
#include "config.h"
#include "autoload.h"
#include "wutil.h"
#include "common.h"
#include "signal.h"
#include "env.h"
#include "exec.h"
#include <assert.h>
#include <algorithm>
/* The time before we'll recheck an autoloaded file */
static const int kAutoloadStalenessInterval = 15;
file_access_attempt_t access_file(const wcstring &path, int mode)
{
//printf("Touch %ls\n", path.c_str());
file_access_attempt_t result = {0};
struct stat statbuf;
if (wstat(path, &statbuf))
{
result.error = errno;
}
else
{
result.mod_time = statbuf.st_mtime;
if (waccess(path, mode))
{
result.error = errno;
}
else
{
result.accessible = true;
}
}
// Note that we record the last checked time after the call, on the assumption that in a slow filesystem, the lag comes before the kernel check, not after.
result.stale = false;
result.last_checked = time(NULL);
return result;
}
autoload_t::autoload_t(const wcstring &env_var_name_var, const builtin_script_t * const scripts, size_t script_count) :
lock(),
env_var_name(env_var_name_var),
builtin_scripts(scripts),
builtin_script_count(script_count),
last_path(),
is_loading_set()
{
pthread_mutex_init(&lock, NULL);
}
autoload_t::~autoload_t()
{
pthread_mutex_destroy(&lock);
}
void autoload_t::node_was_evicted(autoload_function_t *node)
{
// This should only ever happen on the main thread
ASSERT_IS_MAIN_THREAD();
// Tell ourselves that the command was removed if it was loaded
if (! node->is_loaded)
this->command_removed(node->key);
delete node;
}
int autoload_t::unload(const wcstring &cmd)
{
return this->evict_node(cmd);
}
int autoload_t::load(const wcstring &cmd, bool reload)
{
int res;
CHECK_BLOCK(0);
ASSERT_IS_MAIN_THREAD();
env_var_t path_var = env_get_string(env_var_name);
/*
Do we know where to look?
*/
if (path_var.empty())
return 0;
/* Check if the lookup path has changed. If so, drop all loaded files. path_var may only be inspected on the main thread. */
if (path_var != this->last_path)
{
this->last_path = path_var;
scoped_lock locker(lock);
this->evict_all_nodes();
}
/** Warn and fail on infinite recursion. It's OK to do this because this function is only called on the main thread. */
if (this->is_loading(cmd))
{
debug(0,
_(L"Could not autoload item '%ls', it is already being autoloaded. "
L"This is a circular dependency in the autoloading scripts, please remove it."),
cmd.c_str());
return 1;
}
/* Mark that we're loading this */
is_loading_set.insert(cmd);
/* Get the list of paths from which we will try to load */
std::vector<wcstring> path_list;
tokenize_variable_array(path_var, path_list);
/* Try loading it */
res = this->locate_file_and_maybe_load_it(cmd, true, reload, path_list);
/* Clean up */
bool erased = !! is_loading_set.erase(cmd);
assert(erased);
return res;
}
bool autoload_t::can_load(const wcstring &cmd, const env_vars_snapshot_t &vars)
{
const env_var_t path_var = vars.get(env_var_name);
if (path_var.missing_or_empty())
return false;
std::vector<wcstring> path_list;
tokenize_variable_array(path_var, path_list);
return this->locate_file_and_maybe_load_it(cmd, false, false, path_list);
}
static bool script_name_precedes_script_name(const builtin_script_t &script1, const builtin_script_t &script2)
{
return wcscmp(script1.name, script2.name) < 0;
}
void autoload_t::unload_all(void)
{
scoped_lock locker(lock);
this->evict_all_nodes();
}
/** Check whether the given command is loaded. */
bool autoload_t::has_tried_loading(const wcstring &cmd)
{
scoped_lock locker(lock);
autoload_function_t * func = this->get_node(cmd);
return func != NULL;
}
static bool is_stale(const autoload_function_t *func)
{
/** Return whether this function is stale. Internalized functions can never be stale. */
return ! func->is_internalized && time(NULL) - func->access.last_checked > kAutoloadStalenessInterval;
}
autoload_function_t *autoload_t::get_autoloaded_function_with_creation(const wcstring &cmd, bool allow_eviction)
{
ASSERT_IS_LOCKED(lock);
autoload_function_t *func = this->get_node(cmd);
if (! func)
{
func = new autoload_function_t(cmd);
if (allow_eviction)
{
this->add_node(func);
}
else
{
this->add_node_without_eviction(func);
}
}
return func;
}
/**
This internal helper function does all the real work. By using two
functions, the internal function can return on various places in
the code, and the caller can take care of various cleanup work.
cmd: the command name ('grep')
really_load: whether to actually parse it as a function, or just check it it exists
reload: whether to reload it if it's already loaded
path_list: the set of paths to check
Result: if really_load is true, returns whether the function was loaded. Otherwise returns whether the function existed.
*/
bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_load, bool reload, const wcstring_list_t &path_list)
{
/* Note that we are NOT locked in this function! */
size_t i;
bool reloaded = 0;
/* Try using a cached function. If we really want the function to be loaded, require that it be really loaded. If we're not reloading, allow stale functions. */
{
bool allow_stale_functions = ! reload;
/* Take a lock */
scoped_lock locker(lock);
/* Get the function */
autoload_function_t * func = this->get_node(cmd);
/* Determine if we can use this cached function */
bool use_cached;
if (! func)
{
/* Can't use a function that doesn't exist */
use_cached = false;
}
else if (really_load && ! func->is_placeholder && ! func->is_loaded)
{
/* Can't use an unloaded function */
use_cached = false;
}
else if (! allow_stale_functions && is_stale(func))
{
/* Can't use a stale function */
use_cached = false;
}
else
{
/* I guess we can use it */
use_cached = true;
}
/* If we can use this function, return whether we were able to access it */
if (use_cached)
{
return func->is_internalized || func->access.accessible;
}
}
/* The source of the script will end up here */
wcstring script_source;
bool has_script_source = false;
/* Whether we found an accessible file */
bool found_file = false;
/* Look for built-in scripts via a binary search */
const builtin_script_t *matching_builtin_script = NULL;
if (builtin_script_count > 0)
{
const builtin_script_t test_script = {cmd.c_str(), NULL};
const builtin_script_t *array_end = builtin_scripts + builtin_script_count;
const builtin_script_t *found = std::lower_bound(builtin_scripts, array_end, test_script, script_name_precedes_script_name);
if (found != array_end && ! wcscmp(found->name, test_script.name))
{
/* We found it */
matching_builtin_script = found;
}
}
if (matching_builtin_script)
{
has_script_source = true;
script_source = str2wcstring(matching_builtin_script->def);
/* Make a node representing this function */
scoped_lock locker(lock);
autoload_function_t *func = this->get_autoloaded_function_with_creation(cmd, really_load);
/* This function is internalized */
func->is_internalized = true;
/* It's a fiction to say the script is loaded at this point, but we're definitely going to load it down below. */
if (really_load) func->is_loaded = true;
}
if (! has_script_source)
{
/* Iterate over path searching for suitable completion files */
for (i=0; i<path_list.size(); i++)
{
wcstring next = path_list.at(i);
wcstring path = next + L"/" + cmd + L".fish";
const file_access_attempt_t access = access_file(path, R_OK);
if (access.accessible)
{
/* Found it! */
found_file = true;
/* Now we're actually going to take the lock. */
scoped_lock locker(lock);
autoload_function_t *func = this->get_node(cmd);
/* Generate the source if we need to load it */
bool need_to_load_function = really_load && (func == NULL || func->access.mod_time != access.mod_time || ! func->is_loaded);
if (need_to_load_function)
{
/* Generate the script source */
wcstring esc = escape_string(path, 1);
script_source = L". " + esc;
has_script_source = true;
/* Remove any loaded command because we are going to reload it. Note that this will deadlock if command_removed calls back into us. */
if (func && func->is_loaded)
{
command_removed(cmd);
func->is_placeholder = false;
}
/* Mark that we're reloading it */
reloaded = true;
}
/* Create the function if we haven't yet. This does not load it. Do not trigger eviction unless we are actually loading, because we don't want to evict off of the main thread. */
if (! func)
{
func = get_autoloaded_function_with_creation(cmd, really_load);
}
/* It's a fiction to say the script is loaded at this point, but we're definitely going to load it down below. */
if (need_to_load_function) func->is_loaded = true;
/* Unconditionally record our access time */
func->access = access;
break;
}
}
/*
If no file or builtin script was found we insert a placeholder function.
Later we only research if the current time is at least five seconds later.
This way, the files won't be searched over and over again.
*/
if (! found_file && ! has_script_source)
{
scoped_lock locker(lock);
/* Generate a placeholder */
autoload_function_t *func = this->get_node(cmd);
if (! func)
{
func = new autoload_function_t(cmd);
func->is_placeholder = true;
if (really_load)
{
this->add_node(func);
}
else
{
this->add_node_without_eviction(func);
}
}
func->access.last_checked = time(NULL);
}
}
/* If we have a script, either built-in or a file source, then run it */
if (really_load && has_script_source)
{
if (exec_subshell(script_source, false /* do not apply exit status */) == -1)
{
/* Do nothing on failure */
}
}
if (really_load)
{
return reloaded;
}
else
{
return found_file || has_script_source;
}
}

138
autoload.h Normal file
View File

@@ -0,0 +1,138 @@
/** \file autoload.h
The classes responsible for autoloading functions and completions.
*/
#ifndef FISH_AUTOLOAD_H
#define FISH_AUTOLOAD_H
#include <wchar.h>
#include <map>
#include <set>
#include <list>
#include "common.h"
#include "lru.h"
/** A struct responsible for recording an attempt to access a file. */
struct file_access_attempt_t
{
time_t mod_time; /** The modification time of the file */
time_t last_checked; /** When we last checked the file */
bool accessible; /** Whether we believe we could access this file */
bool stale; /** Whether the access attempt is stale */
int error; /** If we could not access the file, the error code */
};
file_access_attempt_t access_file(const wcstring &path, int mode);
struct autoload_function_t : public lru_node_t
{
autoload_function_t(const wcstring &key) : lru_node_t(key), access(), is_loaded(false), is_placeholder(false), is_internalized(false) { }
file_access_attempt_t access; /** The last access attempt */
bool is_loaded; /** Whether we have actually loaded this function */
bool is_placeholder; /** Whether we are a placeholder that stands in for "no such function". If this is true, then is_loaded must be false. */
bool is_internalized; /** Whether this function came from a builtin "internalized" script */
};
struct builtin_script_t
{
const wchar_t *name;
const char *def;
};
struct builtin_script_t;
class env_vars_snapshot_t;
/**
A class that represents a path from which we can autoload, and the autoloaded contents.
*/
class autoload_t : private lru_cache_t<autoload_function_t>
{
private:
/** Lock for thread safety */
pthread_mutex_t lock;
/** The environment variable name */
const wcstring env_var_name;
/** Builtin script array */
const struct builtin_script_t *const builtin_scripts;
/** Builtin script count */
const size_t builtin_script_count;
/** The path from which we most recently autoloaded */
wcstring last_path;
/**
A table containing all the files that are currently being
loaded. This is here to help prevent recursion.
*/
std::set<wcstring> is_loading_set;
bool is_loading(const wcstring &name) const
{
return is_loading_set.find(name) != is_loading_set.end();
}
void remove_all_functions(void)
{
this->evict_all_nodes();
}
bool locate_file_and_maybe_load_it(const wcstring &cmd, bool really_load, bool reload, const wcstring_list_t &path_list);
virtual void node_was_evicted(autoload_function_t *node);
autoload_function_t *get_autoloaded_function_with_creation(const wcstring &cmd, bool allow_eviction);
protected:
/** Overridable callback for when a command is removed */
virtual void command_removed(const wcstring &cmd) { }
public:
/** Create an autoload_t for the given environment variable name */
autoload_t(const wcstring &env_var_name_var, const builtin_script_t *scripts, size_t script_count);
/** Destructor */
virtual ~autoload_t();
/**
Autoload the specified file, if it exists in the specified path. Do
not load it multiple times unless its timestamp changes or
parse_util_unload is called.
Autoloading one file may unload another.
\param cmd the filename to search for. The suffix '.fish' is always added to this name
\param on_unload a callback function to run if a suitable file is found, which has not already been run. unload will also be called for old files which are unloaded.
\param reload wheter to recheck file timestamps on already loaded files
*/
int load(const wcstring &cmd, bool reload);
/** Check whether we have tried loading the given command. Does not do any I/O. */
bool has_tried_loading(const wcstring &cmd);
/**
Tell the autoloader that the specified file, in the specified path,
is no longer loaded.
\param cmd the filename to search for. The suffix '.fish' is always added to this name
\param on_unload a callback function which will be called before (re)loading a file, may be used to unload the previous file.
\return non-zero if the file was removed, zero if the file had not yet been loaded
*/
int unload(const wcstring &cmd);
/**
Unloads all files.
*/
void unload_all();
/** Check whether the given command could be loaded, but do not load it. */
bool can_load(const wcstring &cmd, const env_vars_snapshot_t &vars);
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +0,0 @@
for i in (seq 1000)
command true
end

View File

@@ -1,8 +0,0 @@
# Glob fish's source directory.
# This timing is bound to change if the repo does,
# so it's best to build two fishes, check out one version of the repo,
# and then run this script with both.
set -l dir (dirname (status current-filename))
for i in (seq 1 10)
echo $dir/../../**
end

View File

@@ -1,3 +0,0 @@
for i in (seq 100000)
math $i + $i
end

View File

@@ -1,3 +0,0 @@
for i in (seq 10000)
echo $i
end

View File

@@ -1,43 +0,0 @@
#!/bin/sh
if [ "$#" -gt 2 -o "$#" -eq 0 ]; then
echo "Usage: driver.sh /path/to/fish [/path/to/other/fish]"
exit 1
fi
FISH_PATH=$1
FISH2_PATH=$2
BENCHMARKS_DIR=$(dirname "$0")/benchmarks
quote() {
# Single-quote the given string for a POSIX shell, except in common cases that don't need it.
printf %s "$1" |
sed "/[^[:alnum:]\/.-]/ {
s/'/'\\\''/g
s/^/'/
s/\$/'/
}"
}
for benchmark in "$BENCHMARKS_DIR"/*; do
basename "$benchmark"
# If we have hyperfine, use it first to warm up the cache
if command -v hyperfine >/dev/null 2>&1; then
cmd1="$(quote "${FISH_PATH}") --no-config $(quote "$benchmark")"
if [ -n "$FISH2_PATH" ]; then
cmd2="$(quote "${FISH2_PATH}") --no-config $(quote "$benchmark")"
hyperfine --warmup 3 "$cmd1" "$cmd2"
else
hyperfine --warmup 3 "$cmd1"
fi
fi
[ -n "$FISH2_PATH" ] && echo "$FISH_PATH"
"${FISH_PATH}" --print-rusage-self "$benchmark" > /dev/null
if [ -n "$FISH2_PATH" ]; then
echo "$FISH2_PATH"
"${FISH2_PATH}" --print-rusage-self "$benchmark" > /dev/null
fi
done

View File

@@ -0,0 +1,136 @@
#!/bin/sh
# This script is run as part of the build process
if test $# -eq 0
then
# Use fish's defaults
DOXYFILE=Doxyfile.help
INPUTDIR=doc_src
OUTPUTDIR=share
echo "Using defaults: $0 ${DOXYFILE} ${INPUTDIR} ${OUTPUTDIR}"
elif test $# -eq 3
then
DOXYFILE="$1"
INPUTDIR="$2"
OUTPUTDIR="$3"
else
echo "Usage: $0 doxygen_file input_directory output_directory"
exit 1
fi
# Determine which man pages we don't want to generate.
# Don't make a test man page. fish's test is conforming, so the system man pages
# are applicable and generally better.
# on OS X, don't make a man page for open, since we defeat fish's open function on OS X.
CONDEMNED_PAGES=test.1
if test `uname` = 'Darwin'; then
CONDEMNED_PAGES="$CONDEMNED_PAGES open.1"
fi
# Helper function to turn a relative path into an absolute path
resolve_path()
{
D=`command dirname "$1"`
B=`command basename "$1"`
echo "`cd \"$D\" 2>/dev/null && pwd || echo \"$D\"`/$B"
}
# Expand relative paths
DOXYFILE=`resolve_path "$DOXYFILE"`
INPUTDIR=`resolve_path "$INPUTDIR"`
OUTPUTDIR=`resolve_path "$OUTPUTDIR"`
echo " doxygen file: $DOXYFILE"
echo " input directory: $INPUTDIR"
echo " output directory: $OUTPUTDIR"
echo " skipping: $CONDEMNED_PAGES"
# Make sure INPUTDIR is found
if test ! -d "$INPUTDIR"; then
echo >&2 "Could not find input directory '${INPUTDIR}'"
exit 1
fi
# Make sure doxygen is found
DOXYGENPATH=`command -v doxygen`
if test -z "$DOXYGENPATH" ; then
for i in /usr/local/bin/doxygen /opt/bin/doxygen /Applications/Doxygen.app/Contents/Resources/doxygen ~/Applications/Doxygen.app/Contents/Resources/doxygen ; do
if test -f "$i"; then
DOXYGENPATH="$i"
break
fi
done
fi
if test -z "$DOXYGENPATH"; then
echo >&2 "doxygen is not installed, so documentation will not be built."
exit 0
fi
# Determine where our output should go
if ! mkdir -p "${OUTPUTDIR}" ; then
echo "Could not create output directory '${OUTPUTDIR}'"
fi
# Make a temporary directory
TMPLOC=`mktemp -d -t fish_doc_build_XXXXXX` || { echo >&2 "Could not build documentation because mktemp failed"; exit 1; }
# Copy stuff to the temp directory
for i in "$INPUTDIR"/*.txt; do
INPUTFILE=$TMPLOC/`basename $i .txt`.doxygen
echo "/** \page" `basename $i .txt` > $INPUTFILE
cat $i >>$INPUTFILE
echo "*/" >>$INPUTFILE
done
# Make some extra stuff to pass to doxygen
# Input is kept as . because we cd to the input directory beforehand
# This prevents doxygen from generating "documentation" for intermediate directories
DOXYPARAMS=$(cat <<EOF
PROJECT_NUMBER=2.0.0
INPUT=.
OUTPUT_DIRECTORY=$OUTPUTDIR
QUIET=YES
EOF
);
# echo "$DOXYPARAMS"
# Clear out the output directory first
find "${OUTPUTDIR}" -name "*.1" -delete
# Run doxygen
cd "$TMPLOC"
(cat "${DOXYFILE}" ; echo "$DOXYPARAMS";) | "$DOXYGENPATH" -
# Remember errors
RESULT=$?
cd "${OUTPUTDIR}/man/man1/"
if test "$RESULT" = 0 ; then
# Postprocess the files
for i in "$INPUTDIR"/*.txt; do
# It would be nice to use -i here for edit in place, but that is not portable
CMD_NAME=`basename "$i" .txt`;
sed -e "s/\(.\)\\.SH/\1/" -e "s/$CMD_NAME *\\\\- *\"\(.*\)\"/\1/" "${CMD_NAME}.1" > "${CMD_NAME}.1.tmp"
mv "${CMD_NAME}.1.tmp" "${CMD_NAME}.1"
done
# Erase condemned pages
rm -f $CONDEMNED_PAGES
fi
# Destroy TMPLOC
echo "Cleaning up '$TMPLOC'"
rm -Rf "$TMPLOC"
if test "$RESULT" = 0; then
# Tell the user what we did
echo "Output man pages into '${OUTPUTDIR}'"
else
echo "Doxygen failed. See the output log for details."
fi
exit $RESULT

View File

@@ -1,26 +0,0 @@
<?xml version="1.0"?>
<![CDATA[
<!-- Sadly we can't enable the following two rules since doing so causes false
positives in standard header files rather than just project specific
source files. If we can find a way to enable these rules by also
excluding system include files we should do so.
<rule version="1">
<pattern> wcwidth \(</pattern>
<message>
<id>wcwidthForbidden</id>
<severity>warning</severity>
<summary>Always use fish_wcwidth rather than wcwidth.</summary>
</message>
</rule>
<rule version="1">
<pattern> wcswidth \(</pattern>
<message>
<id>wcswidthForbidden</id>
<severity>warning</severity>
<summary>Always use fish_wcswidth rather than wcswidth.</summary>
</message>
</rule>
<--!>
]]>

View File

@@ -1,7 +0,0 @@
#!/bin/sh
cppcheck --std=c++11 --quiet \
--suppressions-list=build_tools/cppcheck.suppressions --inline-suppr \
--rule-file=build_tools/cppcheck.rules \
--force \
${@:---enable=all ./src/}

View File

@@ -1,15 +0,0 @@
// suppress all instances of varFuncNullUB: "Passing NULL after the last typed
// argument to a variadic function leads to undefined behaviour." That's
// because all the places we do this are valid and won't cause problems even
// on a ILP64 platform because we're careful about using NULL rather than 0.
varFuncNullUB
// Suppress the warning about unmatched suppressions. At the moment these
// warnings are emitted even when removing the suppression comment results in
// the warning being suppressed. In other words this unmatchedSuppression
// warnings are false positives.
unmatchedSuppression
// Suppress this one because it reports assert(condition && "message"), which we use all over the place
incorrectStringBooleanError
// This is of very little use and pops up *everywhere*.
useStlAlgorithm

View File

@@ -0,0 +1,3 @@
This is the_ridiculous'fish s delightful fork of, fish friendly interactive shell. For more information, visit http://ridiculousfish.com/shell/ .
This installer will install fish, but will not modify your /etc/shells file or your default shell. I trust you know how to do that yourself if you care to!

View File

@@ -1,46 +0,0 @@
#!/usr/bin/env fish
#
# Compares the output of two fish profile runs and emits the time difference between
# the first and second set of results.
#
# Usage: ./diff_profiles.fish profile1.log profile2.log > profile_diff.log
set -l profile1 (cat $argv[1])
set -l profile2 (cat $argv[2])
set -l line_no 0
while set -l next_line_no (math $line_no + 1) && set -q profile1[$next_line_no] && set -q profile2[$next_line_no]
set line_no $next_line_no
set -l line1 $profile1[$line_no]
set -l line2 $profile2[$line_no]
if not string match -qr '^\d+\t\d+' $line1
echo $line1
continue
end
set -l results1 (string match -r '^(\d+)\t(\d+)\s+(.*)' $line1)
set -l results2 (string match -r '^(\d+)\t(\d+)\s+(.*)' $line2)
# times from both files
set -l time1 $results1[2..3]
set -l time2 $results2[2..3]
# leftover from both files
set -l remainder1 $results1[4]
set -l remainder2 $results2[4]
if not string match -q -- $remainder1 $remainder2
echo Mismatch on line $line_no:
echo - $remainder1
echo + $remainder2
exit 1
end
set -l diff
set diff[1] (math $time1[1] - $time2[1])
set diff[2] (math $time1[2] - $time2[2])
echo $diff[1] $diff[2] $remainder1
end

View File

@@ -1,23 +0,0 @@
#!/usr/bin/env fish
# Build a list of all sections in the html sphinx docs, separately by page,
# so it can be added to share/functions/help.fish
# Use like
# fish extract_help_sections.fish user_doc/html/{fish_for_bash_users.html,faq.html,interactive.html,language.html,tutorial.html}
# TODO: Currently `help` uses variable names we can't generate, so it needs to be touched up manually.
# Also this could easily be broken by changes in sphinx, ideally we'd have a way to let it print the section titles.
#
for file in $argv
set -l varname (string replace -r '.*/(.*).html' '$1' -- $file | string escape --style=var)pages
# Technically we can use any id in the document as an anchor, but listing them all is probably too much.
# Sphinx stores section titles (in a slug-ized form) in the id,
# and stores explicit section links in a `span` tag like
# `<span id="identifiers"></span>`
# We extract both separately.
set -l sections (string replace -rf '.*class="headerlink" href="#([^"]*)".*' '$1' <$file)
# Sections titled "id5" and such are internal cruft and shouldn't be offered.
set -a sections (string replace -rf '.*span id="([^"]*)".*' '$1' <$file | string match -rv 'id\d+')
set sections (printf '%s\n' $sections | sort -u)
echo set -l $varname $sections
end

View File

@@ -1,110 +0,0 @@
#!/usr/bin/env fish
# Finds global variables by parsing the output of 'nm'
# for object files in this directory.
# This was written for macOS nm.
set -l FISH_SOURCE_DIR $argv[1]
if not test -d "$FISH_SOURCE_DIR"
echo "FISH_SOURCE_DIR not given"
exit 1
end
set -g whitelist \
# unclear what this is \
l_constinit \
# hacks to work around missing ncurses strings on mac \
sitm_esc ritm_esc dim_esc
# In our nm regex, we are interested in data (dD) and bss (bB) segments.
set -g nm_regex '^([^ ]+) ([dDbB])'
set -l total_globals 0
set -l boring_files \
fish_key_reader.cpp.o \
fish_tests.cpp.o \
fish_indent.cpp.o
# return if we should ignore the given symbol name
function should_ignore
set -l symname $argv[1]
string match -q '*guard variable for*' $symname
and return 0
contains $symname $whitelist
and return 0
return 1
end
# echo a cleaned-up symbol name, e.g. replacing template gunk
function cleanup_syname
set -l symname $argv[1]
set symname (string replace --all 'std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> >' 'wcstring' $symname)
set symname (string replace --all 'std::__1::vector<wcstring, std::__1::allocator<wcstring > >' 'wcstring_list_t' $symname)
echo $symname
end
# Output the declaration for a symbol name in a given file.
function print_decl -a FISH_SOURCE_DIR objfile symname
set -l varname (string split '::' $symname)[-1]
set -l srcfile (basename $objfile .o)
set -l srcpath $FISH_SOURCE_DIR/src/$srcfile
# A leading underscore indicates a global, strip it.
set varname (string replace --regex '^_' '' $varname)
if not test -f "$srcpath"
echo "Could not find $srcpath"
end
# Guess the variable as the first usage of the name.
# Strip everything after the first =.
set -l vardecl (egrep -m 1 " $varname\\b" $srcpath | cut -f -1 -d '=' | string trim)
if test -z "$vardecl"
echo "COULD_NOT_FIND_$varname"
return 1
end
echo $vardecl
return 0
end
# Return if a variable declaration is "thread safe".
function decl_is_threadsafe
set -l vardecl $argv[1]
# decls starting with 'const ' or containing ' const ' are assumed safe.
string match -q --regex '(^|\\*| )const ' $vardecl
and return 0
# Ordinary types indicating a safe variable.
set -l safes relaxed_atomic_bool_t std::mutex std::condition_variable std::once_flag sig_atomic_t
for safe in $safes
string match -q "*$safe*" $vardecl
and return 0
end
# Template types indicate a safe variable.
set safes owning_lock mainthread_t std::atomic relaxed_atomic_t latch_t
for safe in $safes
string match -q "*$safe<*" $vardecl
and return 0
end
end
for file in ./**.o
set -l filename (basename $file)
# Skip boring files.
contains $filename $boring_files
and continue
for line in (nm -p -P -U $file | egrep $nm_regex)
set -l matches (string match --regex $nm_regex -- $line)
or continue
set -l symname (cleanup_syname (echo $matches[2] | c++filt))
should_ignore $symname
and continue
set -l vardecl (print_decl $FISH_SOURCE_DIR $filename $symname)
decl_is_threadsafe $vardecl
and continue
echo $filename $symname $matches[3] ":" $vardecl
set total_globals (math $total_globals + 1)
end
end
echo "Total: $total_globals"

View File

@@ -1,63 +0,0 @@
#!/usr/bin/env python3
# Finds potential ODR violations due to weak symbols.
# For example, if you have two different structs with the same name in different files,
# their inline constructors may collide.
# This works only on Linux. It is designed to be run from the cmake build directory.
# clang seems more willing to emit non-inlined ctors. Of course perform a Debug build.
import re
import subprocess
output = subprocess.check_output(
"nm --radix=d -g --demangle -l --print-size CMakeFiles/fishlib.dir/src/*.o",
shell=True,
universal_newlines=True,
)
files_by_name = {} # Symbol to set of paths
sizes_by_name = {} # Symbol to set of int sizes
for line in output.split("\n"):
# Keep only weak symbols with default values (e.g. emitted inline functions).
# Example line: "0000000000000000 0000000000000107 W symbol_name"
# First number is offset, second is size.
# Note this is decimal because of radix=d.
m = re.match(r"\d+ (\d+) W (.*)\t(.*)", line)
if not m:
continue
size, name, filename = m.groups()
files_by_name.setdefault(name, set()).add(filename)
sizes_by_name.setdefault(name, set()).add(int(size))
odr_violations = 0
for name, sizes in sizes_by_name.items():
if len(sizes) == 1:
continue
files = files_by_name[name]
# Ignore symbols that only appear in one file.
# These are typically headers - unclear why they get different sizes but it appears benign.
if len(files) == 1:
continue
# Multiple sizes for this symbol name.
odr_violations += 1
print("Multiple sizes for symbol: " + name)
print("\t%s" % ", ".join([str(x) for x in sizes]))
print("\tFound in files:")
for filename in files:
print("\t\t%s" % filename)
if odr_violations == 0:
print("No ODR violations found, hooray\n")
# Show potential weak symbols.
suspicious_odrs = 0
for (name, files) in files_by_name.items():
if len(files) != 1:
continue
(filename,) = files
if ".cpp" in filename:
if suspicious_odrs == 0:
print("Some suspicious singles:")
suspicious_odrs += 1
print("\t%s" % name)
print("\t\tIn file %s" % filename)

View File

@@ -1,60 +0,0 @@
#!/usr/bin/env fish
#
# Tool to generate messages.pot
# Extended to replace the old Makefile rule which did not port easily to CMake
# This script was originally motivated to work around a quirk (or bug depending on your viewpoint)
# of the xgettext command. See https://lists.gnu.org/archive/html/bug-gettext/2014-11/msg00006.html.
# However, it turns out that even if that quirk did not exist we would still need something like
# this script to properly extract descriptions. That's because we need to normalize the strings to
# a format that xgettext will handle correctly. Also, `xgettext -LShell` doesn't correctly extract
# all the strings we want translated. So we extract and normalize all such strings into a format
# that `xgettext` can handle.
# Start with the C++ source
xgettext -k -k_ -kN_ -LC++ --no-wrap -o messages.pot src/*.cpp src/*.h
# This regex handles descriptions for `complete` and `function` statements. These messages are not
# particularly important to translate. Hence the "implicit" label.
set -l implicit_regex '(?:^| +)(?:complete|function).*? (?:-d|--description) (([\'"]).+?(?<!\\\\)\\2).*'
# This regex handles explicit requests to translate a message. These are more important to translate
# than messages which should be implicitly translated.
set -l explicit_regex '.*\( *_ (([\'"]).+?(?<!\\\\)\\2) *\).*'
# Create temporary directory for these operations. OS X `mktemp` is somewhat restricted, so this block
# works around that - based on share/functions/funced.fish.
set -q TMPDIR
or set -l TMPDIR /tmp
set -l tmpdir (mktemp -d $TMPDIR/fish.XXXXXX)
or exit 1
mkdir -p $tmpdir/implicit/share/completions $tmpdir/implicit/share/functions
mkdir -p $tmpdir/explicit/share/completions $tmpdir/explicit/share/functions
for f in share/config.fish share/completions/*.fish share/functions/*.fish
# Extract explicit attempts to translate a message. That is, those that are of the form
# `(_ "message")`.
string replace --filter --regex $explicit_regex 'echo $1' <$f | fish >$tmpdir/explicit/$f.tmp 2>/dev/null
while read description
echo 'N_ "'(string replace --all '"' '\\"' -- $description)'"'
end <$tmpdir/explicit/$f.tmp >$tmpdir/explicit/$f
rm $tmpdir/explicit/$f.tmp
# Handle `complete` / `function` description messages. The `| fish` is subtle. It basically
# avoids the need to use `source` with a command substitution that could affect the current
# shell.
string replace --filter --regex $implicit_regex 'echo $1' <$f | fish >$tmpdir/implicit/$f.tmp 2>/dev/null
while read description
# We don't use `string escape` as shown in the next comment because it produces output that
# is not parsed correctly by xgettext. Instead just escape double-quotes and quote the
# resulting string.
echo 'N_ "'(string replace --all '"' '\\"' -- $description)'"'
end <$tmpdir/implicit/$f.tmp >$tmpdir/implicit/$f
rm $tmpdir/implicit/$f.tmp
end
xgettext -j -k -kN_ -LShell --from-code=UTF-8 -cDescription --no-wrap -o messages.pot $tmpdir/explicit/share/*/*.fish
xgettext -j -k -kN_ -LShell --from-code=UTF-8 -cDescription --no-wrap -o messages.pot $tmpdir/implicit/share/*/*.fish
rm -r $tmpdir

View File

@@ -1,70 +0,0 @@
#!/bin/sh
# Originally from the git sources (GIT-VERSION-GEN)
# Presumably (C) Junio C Hamano <junkio@cox.net>
# Reused under GPL v2.0
# Modified for fish by David Adam <zanchey@ucc.gu.uwa.edu.au>
set -e
# Find the fish directory as two levels up from script directory.
FISH_BASE_DIR="$( cd "$( dirname "$( dirname "$0" )" )" && pwd )"
DEF_VER=unknown
git_permission_failed=0
# First see if there is a version file (included in release tarballs),
# then try git-describe, then default.
if test -f version
then
VN=$(cat version) || VN="$DEF_VER"
else
if VN=$(git -C "$FISH_BASE_DIR" describe --always --dirty 2>/dev/null); then
:
else
if test $? = 128; then
# Current git versions return status 128
# when run in a repo owned by another user.
# Even for describe and everything.
# This occurs for `sudo make install`.
git_permission_failed=1
fi
VN="$DEF_VER"
fi
fi
# If the first param is --stdout, then output to stdout and exit.
if test "$1" = '--stdout'
then
echo $VN
exit 0
fi
# Set the output directory as either the first param or cwd.
test -n "$1" && OUTPUT_DIR=$1/ || OUTPUT_DIR=
FBVF="${OUTPUT_DIR}FISH-BUILD-VERSION-FILE"
if test "$VN" = unknown && test -r "$FBVF" && test "$git_permission_failed" = 1
then
# HACK: Git failed, so we keep the current version file.
# This helps in case you built fish as a normal user
# and then try to `sudo make install` it.
date +%s > ${OUTPUT_DIR}fish-build-version-witness.txt
exit 0
fi
if test -r "$FBVF"
then
VC=$(grep -v '^#' "$FBVF" | tr -d '"' | sed -e 's/^FISH_BUILD_VERSION=//')
else
VC="unset"
fi
# Maybe output the FBVF
# It looks like FISH_BUILD_VERSION="2.7.1-621-ga2f065e6"
test "$VN" = "$VC" || {
echo >&2 "FISH_BUILD_VERSION=$VN"
echo "FISH_BUILD_VERSION=\"$VN\"" >"$FBVF"
}
# Output the fish-build-version-witness.txt
# See https://cmake.org/cmake/help/v3.4/policy/CMP0058.html
date +%s > ${OUTPUT_DIR}fish-build-version-witness.txt

View File

@@ -1,28 +0,0 @@
# Map file for the include-what-you-use tool on Linux.
[
{ include: ["<bits/fcntl-linux.h>", "private", "<fcntl.h>", "public"] },
{ include: ["<bits/mman-linux.h>", "private", "<sys/mman.h>", "public"] },
{ include: ["<bits/socket-linux.h>", "private", "<sys/socket.h>", "public"] },
{ include: ["<bits/socket_type.h>", "private", "<sys/socket.h>", "public"] },
{ include: ["<bits/local_lim.h>", "private", "<limits.h>", "public"] },
{ include: ["<tr1/memory>", "public", "<memory>", "public"] },
{ include: ["<features.h>", "public", "<stdio.h>", "public"] },
{ include: ["<features.h>", "public", "<stddef.h>", "public"] },
{ include: ["<features.h>", "public", "<unistd.h>", "public"] },
{ symbol: ["size_t", "private", "<unistd.h>", "public"] },
{ symbol: ["size_t", "private", "<stddef.h>", "public"] },
{ symbol: ["size_t", "private", "<stdlib.h>", "public"] },
{ symbol: ["intmax_t", "private", "<sys/stdint.h>", "public"] },
{ symbol: ["intmax_t", "private", "<sys/types.h>", "public"] },
{ symbol: ["uint32_t", "private", "<sys/stdint.h>", "public"] },
{ symbol: ["uint32_t", "private", "<sys/types.h>", "public"] },
{ symbol: ["uint64_t", "private", "<sys/stdint.h>", "public"] },
{ symbol: ["uint64_t", "private", "<sys/types.h>", "public"] },
{ symbol: ["uintmax_t", "private", "<sys/stdint.h>", "public"] },
{ symbol: ["uintmax_t", "private", "<sys/types.h>", "public"] },
{ symbol: ["clock_gettime", "private", "<sys/time.h>", "public"] },
{ symbol: ["timespec", "private", "<sys/time.h>", "public"] },
{ symbol: ["memset", "private", "<string.h>", "public"] },
{ symbol: ["strerror", "private", "<string.h>", "public"] },
]

View File

@@ -1,171 +0,0 @@
# Map file for the include-what-you-use tool on OS X. For some reason
# the version installed by HomeBrew doesn't have useful mappings for the
# system provided headers. This also has mappings for FreeBSD.
[
{ include: ["<__functional_base>", private, "<functional>", public ] },
{ include: ["<__mutex_base>", private, "<mutex>", public ] },
{ include: ["@<__algorithm/.*>", "private", "<algorithm>", "public"] },
{ include: ["@<__iterator/.*>", "private", "<iterator>", "public"] },
{ include: ["@<__functional/.*>", "private", "<functional>", "public"] },
{ include: ["@<__memory/.*>", "private", "<memory>", "public"] },
{ include: ["@<__utility/.*>", "private", "<utility>", "public"] },
{ include: ["@<__chrono/.*>", "private", "<chrono>", "public"] },
{ include: ["@<__numeric/.*>", "private", "<numeric>", "public"] },
{ include: ["@<__random/.*>", "private", "<random>", "public"] },
{ include: ["@<__locale/.*>", "private", "<locale>", "public"] },
{ include: ["@<xlocale/.*>", "private", "<xlocale.h>", "public"] },
# ratio false positive. See https://groups.google.com/g/include-what-you-use/c/OKVkkWUlx44
{ include: ["<ratio>", "public", "<chrono>", "public"] },
{ include: ["<__locale>", "private", "<locale>", "public"] },
{ include: ["<_ctype.h>", "private", "<ctype.h>", "public"] },
{ include: ["<sys/_pthread/_pthread_once_t.h>", "private", "<pthread.h>", "public"] },
{ include: ["<sys/_pthread/_pthread_mutex_t.h>", "private", "<pthread.h>", "public"] },
{ include: ["<sys/_pthread/_pthread_rwlock_t.h>", "private", "<pthread.h>", "public"] },
{ include: ["<sys/_pthread/_pthread_mutexattr_t.h>", "private", "<pthread.h>", "public"] },
{ include: ["<sys/_pthread/_pthread_attr_t.h>", "private", "<pthread.h>", "public"] },
{ include: ["<sys/_pthread/_pthread_cond_t.h>", "private", "<pthread.h>", "public"] },
{ include: ["<sys/_pthread/_pthread_t.h>", "private", "<pthread.h>", "public"] },
{ include: ["<sys/_pthread/_pthread_key_t.h>", "private", "<pthread.h>", "public"] },
{ include: ["<sys/_pthreadtypes.h>", "private", "<pthread.h>", "public"] },
{ include: ["<sys/_types/_posix_vdisable.h>", "private", "<pthread.h>", "public"] },
{ include: ["<sys/_types/_time_t.h>", "private", "<time.h>", "public"] },
{ include: ["<sys/_types/_suseconds_t.h>", "private", "<time.h>", "public"] },
{ include: ["<sys/_types/_suseconds_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/errno.h>", "private", "<errno.h>", "public"] },
{ include: ["<sys/unistd.h>", "private", "<unistd.h>", "public"] },
{ include: ["<_wctype.h>", "private", "<wctype.h>", "public"] },
{ include: ["<sys/fcntl.h>", "private", "<fcntl.h>", "public"] },
{ include: ["<sys/_types/_seek_set.h>", "private", "<fcntl.h>", "public"] },
{ include: ["<sys/_types/_mbstate_t.h>", "private", "<wchar.h>", "public"] },
{ include: ["<iosfwd>", "public", "<string>", "public"] },
{ include: ["<sys/_stdint.h>", "private", "<stdint.h>", "public"] },
{ include: ["<sys/_types/_s_ifmt.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_size_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_size_t.h>", "private", "<stdlib.h>", "public"] },
{ include: ["<sys/_types/_mode_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_pid_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_fd_def.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_fd_isset.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_fd_set.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_fd_zero.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_timeval.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_uid_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<_types/_intmax_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<_types/_uintmax_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<_types/_uint8_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_int32_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<_types/_uint64_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_uintptr_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_dev_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_ino_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_va_list.h>", "private", "<stdio.h>", "public"] },
{ include: ["<__functional_base>", "private", "<memory>", "public"] },
{ include: ["<__functional_base>", "private", "<vector>", "public"] },
{ include: ["<__functional_base>", "private", "<string>", "public"] },
{ include: ["<__tree>", "private", "<map>", "public"] },
{ include: ["<__tree>", "private", "<set>", "public"] },
{ include: ["<_types/_uint32_t.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_va_list.h>", "private", "<sys/types.h>", "public"] },
{ include: ["<sys/_types/_sigset_t.h>", "private", "<signal.h>", "public"] },
{ include: ["<sys/signal.h>", "private", "<signal.h>", "public"] },
{ include: ["<sys/termios.h>", "private", "<termios.h>", "public"] },
{ include: ["<sys/_termios.h>", "private", "<termios.h>", "public"] },
{ include: ["<sys/ttycom.h>", "private", "<termios.h>", "public"] },
{ include: ["<sys/syslimits.h>", "private", "<limits.h>", "public"] },
{ include: ["<i386/limits.h>", "private", "<limits.h>", "public"] },
{ include: ["<sys/limits.h>", "private", "<limits.h>", "public"] },
{ include: ["<sys/_types/_wint_t.h>", "private", "<stddef.h>", "public"] },
{ include: ["<sys/_select.h>", "private", "<select.h>", "public"] },
{ include: ["<sys/cdefs.h>", "private", "<unistd.h>", "public"] },
{ include: ["<istream>", "public", "<iostream>", "public"] },
{ include: ["<sys/_endian.h>", "private", "<netinet/in.h>", "public"] },
{ include: ["<sys/_types/_timespec.h>", "private", "<time.h>", "public"] },
{ include: ["<sys/_timespec.h>", "private", "<time.h>", "public"] },
{ include: ["<sys/spawn.h>", "private", "<spawn.h>", "public"] },
{ include: ["<sys/dirent.h>", "private", "<dirent.h>", "public"] },
{ include: ["<__mutex_base>", "private", "<mutex>", "public"] },
{ include: ["<__hash_table>", "private", "<unordered_map>", "public"] },
{ include: ["<__hash_table>", "private", "<unordered_set>", "public"] },
{ include: ['"../common.h"', "public", '"common.h"', "public"] },
# We provide our own assert. including assert.h/cassert spoils it and redefines the macro
{ symbol: ["assert", "private", '"common.h"', "public"] },
{ symbol: ["assert", "private", '"../common.h"', "public"] },
{ symbol: ["wcstring", "private", '"common.h"', "public"] },
{ symbol: ["wcstring", "private", '"../common.h"', "public"] },
{ symbol: ["wcstring_list_t", "private", '"common.h"', "public"] },
{ symbol: ["wcstring_list_t", "private", '"../common.h"', "public"] },
{ symbol: ["wcstring", "private", '"flog.h"', "public"] },
{ symbol: ["wcstring_list_t", "private", '"flog.h"', "public"] },
{ symbol: ["size_t", "private", "<cstddef>", "public"] },
{ symbol: ["mutex", "private", "<mutex>", "public"] },
{ symbol: ["sig_atomic_t", "private", "<csignal>", "public"] },
{ symbol: ["va_end", "private", "<stdarg.h>", "public"] },
{ symbol: ["va_list", "private", "<stdarg.h>", "public"] },
{ symbol: ["va_start", "private", "<stdarg.h>", "public"] },
{ symbol: ["NULL", "private", "<cstddef>", "public"] },
{ symbol: ["NULL", "private", "<stdlib.h>", "public"] },
{ symbol: ["NULL", "private", "<stdio.h>", "public"] },
{ symbol: ["NULL", "private", "<unistd.h>", "public"] },
{ symbol: ["off_t", "private", "<unistd.h>", "public"] },
{ symbol: ["off_t", "private", "<sys/types.h>", "public"] },
{ symbol: ["size_t", "private", "<cstddef>", "public"] },
{ symbol: ["ssize_t", "private", "<cstddef>", "public"] },
{ symbol: ["intptr_t", "private", "<unistd.h>", "public"] },
{ symbol: ["gid_t", "private", "<unistd.h>", "public"] },
{ symbol: ["uid_t", "private", "<unistd.h>", "public"] },
{ symbol: ["pid_t", "private", "<unistd.h>", "public"] },
{ symbol: ["pid_t", "private", "<sys/types.h>", "public"] },
{ symbol: ["uid_t", "private", "<sys/types.h>", "public"] },
{ symbol: ["gid_t", "private", "<sys/types.h>", "public"] },
{ symbol: ["timeval", "private", "<sys/time.h>", "public"] },
{ symbol: ["__uint32_t", "private", "<cstdint>", "public"] },
{ symbol: ["uint32_t", "private", "<cstdint>", "public"] },
{ symbol: ["intptr_t", "private", "<cstdint>", "public"] },
{ symbol: ["tparm", "private", "<ncurses.h>", "public"] },
{ symbol: ["select", "private", "<sys/select.h>", "public"] },
{ symbol: ["_LIBCPP_VERSION", "private", "<cstddef>", "public"] },
{ symbol: ["_LIBCPP_VERSION", "private", "<unistd.h>", "public"] },
{ symbol: ["MB_CUR_MAX", "private", "<cstdlib>", "public"] },
{ symbol: ["MB_LEN_MAX", "private", "<cstdlib>", "public"] },
{ symbol: ["WEOF", "private", "<cwctype>", "public"] },
{ symbol: [ "std::declval", private, "<utility>", public ] },
{ symbol: [ "std::forward", private, "<utility>", public ] },
{ symbol: [ "std::move", private, "<utility>", public ] },
{ symbol: [ "std::nullptr_t", private, "<cstddef>", public ] },
{ symbol: [ "std::string", private, "<string>", public ] },
{ symbol: [ "std::isalnum", private, "<locale>", public ] },
{ symbol: [ "std::toupper", private, "<locale>", public ] },
{ symbol: [ "sem_t", private, "<semaphore.h>", public ] },
{ symbol: [ "sem_post", private, "<semaphore.h>", public ] },
{ symbol: [ "sem_wait", private, "<semaphore.h>", public ] },
{ symbol: [ "sem_init", private, "<semaphore.h>", public ] },
{ symbol: [ "sem_destroy", private, "<semaphore.h>", public ] },
{ symbol: [ "FD_SETSIZE", private, "<sys/select.h>", public ] },
{ symbol: [ "locale_t", private, "<locale>", public ] },
{ include: [ "<assert.h>", public, "<cassert>", public ] },
{ include: [ "<complex.h>", public, "<ccomplex>", public ] },
{ include: [ "<ctype.h>", public, "<cctype>", public ] },
{ include: [ "<errno.h>", public, "<cerrno>", public ] },
{ include: [ "<fenv.h>", public, "<cfenv>", public ] },
{ include: [ "<float.h>", public, "<cfloat>", public ] },
{ include: [ "<inttypes.h>", public, "<cinttypes>", public ] },
{ include: [ "<iso646.h>", public, "<ciso646>", public ] },
{ include: [ "<limits.h>", public, "<climits>", public ] },
{ include: [ "<locale.h>", public, "<clocale>", public ] },
{ include: [ "<math.h>", public, "<cmath>", public ] },
{ include: [ "<setjmp.h>", public, "<csetjmp>", public ] },
{ include: [ "<signal.h>", public, "<csignal>", public ] },
{ include: [ "<stdalign.h>", public, "<cstdalign>", public ] },
{ include: [ "<stdarg.h>", public, "<cstdarg>", public ] },
{ include: [ "<stdbool.h>", public, "<cstdbool>", public ] },
{ include: [ "<stddef.h>", public, "<cstddef>", public ] },
{ include: [ "<stdlib.h>", public, "<cstdlib>", public ] },
{ include: [ "<string.h>", public, "<cstring>", public ] },
{ include: [ "<tgmath.h>", public, "<ctgmath>", public ] },
{ include: [ "<time.h>", public, "<ctime>", public ] },
{ include: [ "<uchar.h>", public, "<cuchar>", public ] },
{ include: [ "<wchar.h>", public, "<cwchar>", public ] },
{ include: [ "<wctype.h>", public, "<cwctype>", public ] },
{ include: [ "<_xlocale.h>", private, "<xlocale.h>", public ] },
]

View File

@@ -1,119 +0,0 @@
#!/usr/bin/env fish
#
# This is meant to be run by "make lint" or "make lint-all". It is not meant to
# be run directly from a shell prompt.
#
# We don't include "missingInclude" as that doesn't find our config.h.
# Missing includes will quickly be found by... compiling the thing anyway.
set -l cppchecks warning,performance,portability,information #,missingInclude
set -l cppcheck_args
set -l c_files
set -l all no
set -l kernel_name (uname -s)
set -l machine_type (uname -m)
argparse a/all p/project= -- $argv
# We only want -D and -I options to be passed thru to cppcheck.
for arg in $argv
if string match -q -- '-D*' $arg
set -a cppcheck_args (string split -- ' ' $arg)
else if string match -q -- '-I*' $arg
set -a cppcheck_args (string split -- ' ' $arg)
else if string match -q -- '-iquote*' $arg
set -a cppcheck_args (string split -- ' ' $arg)
end
end
# Not sure when this became necessary but without these flags cppcheck no longer works on macOS.
# It complains that "Cppcheck cannot find all the include files." Adding these include paths should
# be harmless everywhere else.
set cppcheck_args $cppcheck_args -I /usr/include -I .
if test "$machine_type" = x86_64
set cppcheck_args -D__x86_64__ -D__LP64__ $cppcheck_args
end
if set -q _flag_all
set c_files src/*.cpp
set cppchecks "$cppchecks,unusedFunction"
else
# We haven't been asked to lint all the source. If there are uncommitted
# changes lint those, else lint the files in the most recent commit.
# Select (cached files) (modified but not cached, and untracked files)
set -l files (git diff-index --cached HEAD --name-only)
set files $files (git ls-files --exclude-standard --others --modified)
if not set -q files[1]
# No pending changes so lint the files in the most recent commit.
set files (git diff-tree --no-commit-id --name-only -r HEAD)
end
# Extract just the C/C++ files that exist.
set c_files
for file in (string match -r '.*\.c(?:pp)?$' -- $files)
test -f $file; and set c_files $c_files $file
end
end
# We now have a list of files to check so run the linters.
if set -q c_files[1]
if type -q include-what-you-use
echo
echo ========================================
echo Running IWYU
echo ========================================
for c_file in $c_files
switch $kernel_name
case Darwin FreeBSD
include-what-you-use -Xiwyu --no_default_mappings -Xiwyu \
--mapping_file=build_tools/iwyu.osx.imp --std=c++11 \
$cppcheck_args $c_file 2>&1
case Linux
include-what-you-use -Xiwyu --mapping_file=build_tools/iwyu.linux.imp \
$cppcheck_args $c_file 2>&1
case '*' # hope for the best
include-what-you-use --std=c++11 $cppcheck_args $c_file 2>&1
end
end
end
if type -q cppcheck
echo
echo ========================================
echo Running cppcheck
echo ========================================
build_tools/cppcheck.sh --enable=$cppchecks $c_files 2>&1
echo
echo ========================================
echo 'Running `cppcheck --check-config` to identify missing includes and similar problems.'
echo 'Ignore unmatchedSuppression warnings as they are probably false positives we'
echo 'cannot suppress.'
echo ========================================
cppcheck $cppcheck_args --check-config $c_files 2>&1
end
if type -q oclint
echo
echo ========================================
echo Running oclint
echo ========================================
# The stderr to stdout redirection is because oclint, incorrectly writes its final summary
# counts of the errors detected to stderr. Anyone running this who wants to capture its
# output will expect those messages to be written to stdout.
oclint $c_files -- $argv 2>&1
end
if type -q clang-tidy; and set -q _flag_project
echo
echo ========================================
echo Running clang-tidy
echo ========================================
clang-tidy -p $_flag_project $c_files
end
else
echo
echo 'WARNING: No C/C++ files to check'
echo
end

View File

@@ -1,36 +0,0 @@
#! /usr/bin/env fish
set -l TAG $argv[1]
if test -z "$TAG"
echo "Tag name required."
exit 1
end
if not contains -- $TAG (git tag)
echo "$TAG is not a valid tag name."
exit 1
end
set -l committers_to_tag (mktemp)
or exit 1
set -l committers_from_tag (mktemp)
or exit 1
# You might think it would be better to case-insensitively sort/compare the names
# to produce a more natural-looking list.
# Unicode collation tables mean that this is fraught with danger; for example, the
# "“" character will not case-fold in UTF-8 locales. sort suggests using the C locale!
git log "$TAG" --format="%aN" --reverse | sort -u >$committers_to_tag
git log "$TAG".. --format="%aN" --reverse | sort -u >$committers_from_tag
echo New committers:
echo (comm -13 $committers_to_tag $committers_from_tag)','
echo
echo Returning committers:
echo (comm -12 $committers_to_tag $committers_from_tag)','
rm $committers_to_tag $committers_from_tag

View File

@@ -1,792 +0,0 @@
#!/usr/bin/env python
""" Command line test driver. """
from __future__ import unicode_literals
from __future__ import print_function
import argparse
import datetime
import io
import re
import shlex
import subprocess
import sys
try:
from itertools import zip_longest
except ImportError:
from itertools import izip_longest as zip_longest
from difflib import SequenceMatcher
# Directives can occur at the beginning of a line, or anywhere in a line that does not start with #.
COMMENT_RE = r"^(?:[^#].*)?#\s*"
# A regex showing how to run the file.
RUN_RE = re.compile(COMMENT_RE + r"RUN:\s+(.*)\n")
REQUIRES_RE = re.compile(COMMENT_RE + r"REQUIRES:\s+(.*)\n")
# A regex capturing lines that should be checked against stdout.
CHECK_STDOUT_RE = re.compile(COMMENT_RE + r"CHECK:\s+(.*)\n")
# A regex capturing lines that should be checked against stderr.
CHECK_STDERR_RE = re.compile(COMMENT_RE + r"CHECKERR:\s+(.*)\n")
SKIP = object()
def find_command(program):
import os
path, name = os.path.split(program)
if path:
return os.path.isfile(program) and os.access(program, os.X_OK)
for path in os.environ["PATH"].split(os.pathsep):
exe = os.path.join(path, program)
if os.path.isfile(exe) and os.access(exe, os.X_OK):
return exe
return None
class Config(object):
def __init__(self):
# Whether to have verbose output.
self.verbose = False
# Whether output gets ANSI colorization.
self.colorize = False
# Whether to show which file was tested.
self.progress = False
def colors(self):
""" Return a dictionary mapping color names to ANSI escapes """
def ansic(n):
return "\033[%dm" % n if self.colorize else ""
return {
"RESET": ansic(0),
"BOLD": ansic(1),
"NORMAL": ansic(39),
"BLACK": ansic(30),
"RED": ansic(31),
"GREEN": ansic(32),
"YELLOW": ansic(33),
"BLUE": ansic(34),
"MAGENTA": ansic(35),
"CYAN": ansic(36),
"LIGHTGRAY": ansic(37),
"DARKGRAY": ansic(90),
"LIGHTRED": ansic(91),
"LIGHTGREEN": ansic(92),
"LIGHTYELLOW": ansic(93),
"LIGHTBLUE": ansic(94),
"LIGHTMAGENTA": ansic(95),
"LIGHTCYAN": ansic(96),
"WHITE": ansic(97),
}
def output(*args):
print("".join(args) + "\n")
import unicodedata
def esc(m):
map = {
"\n": "\\n",
"\\": "\\\\",
"\a": "\\a",
"\b": "\\b",
"\f": "\\f",
"\r": "\\r",
"\t": "\\t",
"\v": "\\v",
}
if m in map:
return map[m]
if unicodedata.category(m)[0] == "C":
return "\\x{:02x}".format(ord(m))
else:
return m
def escape_string(s):
return "".join(esc(ch) for ch in s)
class CheckerError(Exception):
"""Exception subclass for check line parsing.
Attributes:
line: the Line object on which the exception occurred.
"""
def __init__(self, message, line=None):
super(CheckerError, self).__init__(message)
self.line = line
class Line(object):
""" A line that remembers where it came from. """
def __init__(self, text, number, file):
self.text = text
self.number = number
self.file = file
def __hash__(self):
# Chosen by fair diceroll
# No, just kidding.
# HACK: We pass this to the Sequencematcher, which puts the Checks into a dict.
# To force it to match the regexes, we return a hash collision intentionally,
# so it falls back on __eq__().
#
# CheckCmd has the same thing.
return 0
def __eq__(self, other):
if other is None:
return False
if isinstance(other, CheckCmd):
return other.regex.match(self.text)
if isinstance(other, Line):
# We only compare the text here so SequenceMatcher can reshuffle these
return self.text == other.text
raise NotImplementedError
def subline(self, text):
""" Return a substring of our line with the given text, preserving number and file. """
return Line(text, self.number, self.file)
@staticmethod
def readfile(file, name):
return [Line(text, idx + 1, name) for idx, text in enumerate(file)]
def is_empty_space(self):
return not self.text or self.text.isspace()
def escaped_text(self, for_formatting=False):
ret = escape_string(self.text.rstrip("\n"))
if for_formatting:
ret = ret.replace("{", "{{").replace("}", "}}")
return ret
class RunCmd(object):
"""A command to run on a given Checker.
Attributes:
args: Unexpanded shell command as a string.
"""
def __init__(self, args, line):
self.args = args
self.line = line
@staticmethod
def parse(line):
if not shlex.split(line.text):
raise CheckerError("Invalid RUN command", line)
return RunCmd(line.text, line)
class TestFailure(object):
def __init__(self, line, check, testrun, diff=None, lines=[], checks=[]):
self.line = line
self.check = check
self.testrun = testrun
self.error_annotation_lines = None
self.diff = diff
self.lines = lines
self.checks = checks
self.signal = None
def message(self):
fields = self.testrun.config.colors()
fields["name"] = self.testrun.name
fields["subbed_command"] = self.testrun.subbed_command
if self.line:
fields.update(
{
"output_file": self.line.file,
"output_lineno": self.line.number,
"output_line": self.line.escaped_text(),
}
)
if self.check:
fields.update(
{
"input_file": self.check.line.file,
"input_lineno": self.check.line.number,
"input_line": self.check.line.escaped_text(),
"check_type": self.check.type,
}
)
filemsg = "" if self.testrun.config.progress else " in {name}"
fmtstrs = ["{RED}Failure{RESET}" + filemsg + ":", ""]
if self.signal:
fmtstrs += [
" Process was killed by signal {BOLD}" + self.signal + "{RESET}",
""
]
if self.line and self.check:
fmtstrs += [
" The {check_type} on line {input_lineno} wants:",
" {BOLD}{input_line}{RESET}",
"",
" which failed to match line {output_file}:{output_lineno}:",
" {BOLD}{output_line}{RESET}",
"",
]
elif self.check:
fmtstrs += [
" The {check_type} on line {input_lineno} wants:",
" {BOLD}{input_line}{RESET}",
"",
" but there was no remaining output to match.",
"",
]
else:
fmtstrs += [
" There were no remaining checks left to match {output_file}:{output_lineno}:",
" {BOLD}{output_line}{RESET}",
"",
]
if self.error_annotation_lines:
fields["error_annotation"] = " ".join(
[x.text for x in self.error_annotation_lines]
)
fields["error_annotation_lineno"] = str(
self.error_annotation_lines[0].number
)
if len(self.error_annotation_lines) > 1:
fields["error_annotation_lineno"] += ":" + str(
self.error_annotation_lines[-1].number
)
fmtstrs += [
" additional output on stderr:{error_annotation_lineno}:",
" {BOLD}{error_annotation}{RESET}",
]
if self.diff:
fmtstrs += [" Context:"]
lasthi = 0
lastcheckline = None
for d in self.diff.get_grouped_opcodes():
for op, alo, ahi, blo, bhi in d:
color = "{BOLD}"
if op == "replace" or op == "delete":
color = "{RED}"
# We got a new chunk, so we print a marker.
if alo > lasthi:
fmtstrs += [
" [...] from line "
+ str(self.checks[blo].line.number)
+ " ("
+ self.lines[alo].file
+ ":"
+ str(self.lines[alo].number)
+ "):"
]
lasthi = ahi
# We print one "no more checks" after the last check and then skip any markers
lastcheck = False
for a, b in zip_longest(self.lines[alo:ahi], self.checks[blo:bhi]):
# Clean up strings for use in a format string - double up the curlies.
astr = (
color + a.escaped_text(for_formatting=True) + "{RESET}"
if a
else ""
)
if b:
bstr = (
"on line "
+ str(b.line.number)
+ ": {BLUE}"
+ b.line.escaped_text(for_formatting=True)
+ "{RESET}"
)
lastcheckline = b.line.number
if op == "equal":
fmtstrs += [" " + astr]
elif b and a:
fmtstrs += [
" "
+ astr
+ " <= does not match "
+ b.type
+ " "
+ bstr
]
elif b:
fmtstrs += [
" "
+ astr
+ " <= nothing to match "
+ b.type
+ " "
+ bstr
]
elif not b:
string = " " + astr
if bhi == len(self.checks):
if not lastcheck:
string += " <= no more checks"
lastcheck = True
elif lastcheckline is not None:
string += (
" <= no check matches this, previous check on line "
+ str(lastcheckline)
)
else:
string += " <= no check matches"
fmtstrs.append(string)
fmtstrs.append("")
fmtstrs += [" when running command:", " {subbed_command}"]
return "\n".join(fmtstrs).format(**fields)
def print_message(self):
""" Print our message to stdout. """
print(self.message())
def perform_substitution(input_str, subs):
"""Perform the substitutions described by subs to str
Return the substituted string.
"""
# Sort our substitutions into a list of tuples (key, value), descending by length.
# It needs to be descending because we need to try longer substitutions first.
subs_ordered = sorted(subs.items(), key=lambda s: len(s[0]), reverse=True)
def subber(m):
# We get the entire sequence of characters.
# Replace just the prefix and return it.
text = m.group(1)
for key, replacement in subs_ordered:
if text.startswith(key):
return replacement + text[len(key) :]
# No substitution found, so we default to running it as-is,
# which will end up running it via $PATH.
return text
return re.sub(r"%(%|[a-zA-Z0-9_-]+)", subber, input_str)
def runproc(cmd):
""" Wrapper around subprocess.Popen to save typing """
PIPE = subprocess.PIPE
proc = subprocess.Popen(
cmd,
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
shell=True,
close_fds=True, # For Python 2.6 as shipped on RHEL 6
)
return proc
class TestRun(object):
def __init__(self, name, runcmd, checker, subs, config):
self.name = name
self.runcmd = runcmd
self.subbed_command = perform_substitution(runcmd.args, subs)
self.checker = checker
self.subs = subs
self.config = config
def check(self, lines, checks):
# Reverse our lines and checks so we can pop off the end.
lineq = lines[::-1]
checkq = checks[::-1]
usedlines = []
usedchecks = []
mismatches = []
while lineq and checkq:
line = lineq[-1]
check = checkq[-1]
if check == line:
# This line matched this checker, continue on.
usedlines.append(line)
usedchecks.append(check)
lineq.pop()
checkq.pop()
elif line.is_empty_space():
# Skip all whitespace input lines.
lineq.pop()
else:
usedlines.append(line)
usedchecks.append(check)
mismatches.append((line, check))
# Failed to match.
lineq.pop()
checkq.pop()
# Drain empties
while lineq and lineq[-1].is_empty_space():
lineq.pop()
# Store the remaining lines for the diff
for i in lineq[::-1]:
if not i.is_empty_space():
usedlines.append(i)
# Store remaining checks for the diff
for i in checkq[::-1]:
usedchecks.append(i)
# If we have no more output, there's no reason to give
# SCREENFULS of text.
# So we truncate the check list.
if len(usedchecks) > len(usedlines):
usedchecks = usedchecks[:len(usedlines) + 5]
# Do a SequenceMatch! This gives us a diff-like thing.
diff = SequenceMatcher(a=usedlines, b=usedchecks, autojunk=False)
# If there's a mismatch or still lines or checkers, we have a failure.
# Otherwise it's success.
if mismatches:
return TestFailure(
mismatches[0][0],
mismatches[0][1],
self,
diff=diff,
lines=usedlines,
checks=usedchecks,
)
elif lineq:
return TestFailure(
lineq[-1], None, self, diff=diff, lines=usedlines, checks=usedchecks
)
elif checkq:
return TestFailure(
None, checkq[-1], self, diff=diff, lines=usedlines, checks=usedchecks
)
else:
# Success!
return None
def run(self):
""" Run the command. Return a TestFailure, or None. """
def split_by_newlines(s):
"""Decode a string and split it by newlines only,
retaining the newlines.
"""
return [s + "\n" for s in s.decode("utf-8").split("\n")]
if self.config.verbose:
print(self.subbed_command)
proc = runproc(self.subbed_command)
stdout, stderr = proc.communicate()
# HACK: This is quite cheesy: POSIX specifies that sh should return 127 for a missing command.
# It's also possible that it'll be returned in other situations,
# most likely when the last command in a shell script doesn't exist.
# So we check if the command *we execute* exists, and complain then.
status = proc.returncode
cmd = shlex.split(self.subbed_command)[0]
if status == 127 and not find_command(cmd):
raise CheckerError("Command could not be found: " + cmd)
if status == 126 and not find_command(cmd):
raise CheckerError("Command is not executable: " + cmd)
outlines = [
Line(text, idx + 1, "stdout")
for idx, text in enumerate(split_by_newlines(stdout))
]
errlines = [
Line(text, idx + 1, "stderr")
for idx, text in enumerate(split_by_newlines(stderr))
]
outfail = self.check(outlines, self.checker.outchecks)
errfail = self.check(errlines, self.checker.errchecks)
# It's possible that something going wrong on stdout resulted in new
# text being printed on stderr. If we have an outfailure, and either
# non-matching or unmatched stderr text, then annotate the outfail
# with it.
if outfail and errfail and errfail.line:
outfail.error_annotation_lines = errlines[errfail.line.number - 1 :]
# Trim a trailing newline
if outfail.error_annotation_lines[-1].text == "\n":
del outfail.error_annotation_lines[-1]
failure = outfail if outfail else errfail
if failure and status < 0:
# Process was killed by a signal and failed,
# add a message.
import signal
# Unfortunately strsignal only exists in python 3.8+,
# and signal.signals is 3.5+.
if hasattr(signal, "Signals"):
try:
sig = signal.Signals(-status)
failure.signal = sig.name + " (" + signal.strsignal(sig.value) + ")"
except ValueError:
failure.signal = str(-status)
else:
# No easy way to get the full list,
# make up a dict.
signals = {
signal.SIGABRT: "SIGABRT",
signal.SIGBUS: "SIGBUS",
signal.SIGFPE: "SIGFPE",
signal.SIGILL: "SIGILL",
signal.SIGSEGV: "SIGSEGV",
signal.SIGTERM: "SIGTERM",
}
failure.signal = signals.get(-status, str(-status))
return failure
class CheckCmd(object):
def __init__(self, line, checktype, regex):
self.line = line
self.type = checktype
self.regex = regex
def __hash__(self):
# HACK: We pass this to the Sequencematcher, which puts the Checks into a dict.
# To force it to match the regexes, we return a hash collision intentionally,
# so it falls back on __eq__().
#
# Line has the same thing.
return 0
def __eq__(self, other):
# "Magical" comparison with lines and strings.
# Typically I wouldn't use this, but it allows us to check if a line matches any check in a dict or list via
# the `in` operator.
if other is None:
return False
if isinstance(other, CheckCmd):
return self.regex == other.regex
if isinstance(other, Line):
return self.regex.match(other.text)
if isinstance(other, str):
return self.regex.match(other)
raise NotImplementedError
@staticmethod
def parse(line, checktype):
# type: (Line) -> CheckCmd
# Everything inside {{}} is a regular expression.
# Everything outside of it is a literal string.
# Split around {{...}}. Then every odd index will be a regex, and
# evens will be literals.
# Note that if {{...}} appears first we will get an empty string in
# the split array, so the {{...}} matches are always at odd indexes.
bracket_re = re.compile(
r"""
\{\{ # Two open brackets
(.*?) # Nongreedy capture
\}\} # Two close brackets
""",
re.VERBOSE,
)
pieces = bracket_re.split(line.text)
even = True
re_strings = []
for piece in pieces:
if even:
# piece is a literal string.
re_strings.append(re.escape(piece))
else:
# piece is a regex (found inside {{...}}).
# Verify the regex can be compiled.
try:
re.compile(piece)
except re.error:
raise CheckerError("Invalid regular expression: '%s'" % piece, line)
re_strings.append(piece)
even = not even
# Enclose each piece in a non-capturing group.
# This ensures that lower-precedence operators don't trip up catenation.
# For example: {{b|c}}d would result in /b|cd/ which is different.
# Backreferences are assumed to match across the entire string.
re_strings = ["(?:%s)" % s for s in re_strings]
# Anchor at beginning and end (allowing arbitrary whitespace), and maybe
# a terminating newline.
# We need the anchors because Python's match() matches an arbitrary prefix,
# not the entire string.
re_strings = [r"^\s*"] + re_strings + [r"\s*\n?$"]
full_re = re.compile("".join(re_strings))
return CheckCmd(line, checktype, full_re)
class Checker(object):
def __init__(self, name, lines):
self.name = name
# Helper to yield subline containing group1 from all matching lines.
def group1s(regex):
for line in lines:
m = regex.match(line.text)
if m:
yield line.subline(m.group(1))
# Find run commands.
self.runcmds = [RunCmd.parse(sl) for sl in group1s(RUN_RE)]
self.shebang_cmd = None
if not self.runcmds:
# If no RUN command has been given, fall back to the shebang.
if lines[0].text.startswith("#!"):
# Remove the "#!" at the beginning, and the newline at the end.
cmd = lines[0].text[2:-1]
self.shebang_cmd = cmd
self.runcmds = [RunCmd(cmd + " %s", lines[0])]
else:
raise CheckerError("No runlines ('# RUN') found")
self.requirecmds = [RunCmd.parse(sl) for sl in group1s(REQUIRES_RE)]
# Find check cmds.
self.outchecks = [
CheckCmd.parse(sl, "CHECK") for sl in group1s(CHECK_STDOUT_RE)
]
self.errchecks = [
CheckCmd.parse(sl, "CHECKERR") for sl in group1s(CHECK_STDERR_RE)
]
def check_file(input_file, name, subs, config, failure_handler):
""" Check a single file. Return a True on success, False on error. """
success = True
lines = Line.readfile(input_file, name)
checker = Checker(name, lines)
# Run all the REQUIRES lines first,
# if any of them fail it's a SKIP
for reqcmd in checker.requirecmds:
proc = runproc(
perform_substitution(reqcmd.args, subs)
)
proc.communicate()
if proc.returncode > 0:
return SKIP
if checker.shebang_cmd is not None and not find_command(checker.shebang_cmd):
raise CheckerError("Command could not be found: " + checker.shebang_cmd)
# Only then run the RUN lines.
for runcmd in checker.runcmds:
failure = TestRun(name, runcmd, checker, subs, config).run()
if failure:
failure_handler(failure)
success = False
return success
def check_path(path, subs, config, failure_handler):
with io.open(path, encoding="utf-8") as fd:
return check_file(fd, path, subs, config, failure_handler)
def parse_subs(subs):
"""Given a list of input substitutions like 'foo=bar',
return a dictionary like {foo:bar}, or exit if invalid.
"""
result = {}
for sub in subs:
try:
key, val = sub.split("=", 1)
if not key:
print("Invalid substitution %s: empty key" % sub)
sys.exit(1)
if not val:
print("Invalid substitution %s: empty value" % sub)
sys.exit(1)
result[key] = val
except ValueError:
print("Invalid substitution %s: equal sign not found" % sub)
sys.exit(1)
return result
def get_argparse():
""" Return a littlecheck argument parser. """
parser = argparse.ArgumentParser(
description="littlecheck: command line tool tester."
)
parser.add_argument(
"-s",
"--substitute",
type=str,
help="Add a new substitution for RUN lines. Example: bash=/bin/bash",
action="append",
default=[],
)
parser.add_argument(
"-p",
"--progress",
action="store_true",
dest="progress",
help="Show the files to be checked",
default=False,
)
parser.add_argument(
"--force-color",
action="store_true",
dest="force_color",
help="Force usage of color even if not connected to a terminal",
default=False,
)
parser.add_argument("file", nargs="+", help="File to check")
return parser
def main():
args = get_argparse().parse_args()
# Default substitution is %% -> %
def_subs = {"%": "%"}
def_subs.update(parse_subs(args.substitute))
tests_count = 0
failed = False
skip_count = 0
config = Config()
config.colorize = args.force_color or sys.stdout.isatty()
config.progress = args.progress
fields = config.colors()
for path in args.file:
tests_count += 1
fields["path"] = path
if config.progress:
print("Testing file {path} ... ".format(**fields), end="")
sys.stdout.flush()
subs = def_subs.copy()
subs["s"] = path
starttime = datetime.datetime.now()
ret = check_path(path, subs, config, TestFailure.print_message)
if ret is SKIP:
skip_count += 1
if not ret:
failed = True
elif config.progress:
endtime = datetime.datetime.now()
duration_ms = round((endtime - starttime).total_seconds() * 1000)
reason = "ok"
color = "{GREEN}"
if ret is SKIP:
reason = "SKIPPED"
color = "{BLUE}"
print(
(color + "{reason}{RESET} ({duration} ms)").format(
duration=duration_ms, reason=reason, **fields
)
)
# To facilitate integration with testing frameworks, use exit code 125 to indicate that all
# tests have been skipped (primarily for use when tests are run one at a time). Exit code 125 is
# used to indicate to automated `git bisect` runs that a revision has been skipped; we use it
# for the same reasons git does.
if skip_count > 0 and skip_count == tests_count:
sys.exit(125)
sys.exit(1 if failed else 0)
if __name__ == "__main__":
main()

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env bash
# Helper to notarize an .app.zip or .pkg file.
set -e
die() { echo "$*" 1>&2 ; exit 1; }
test "$#" -ge 1 || die "No paths specified."
for INPUT in "$@"; do
echo "Processing $INPUT"
test -f "$INPUT" || die "Not a file: $INPUT"
ext="${INPUT##*.}"
(test "$ext" = "zip" || test "$ext" = "pkg") || die "Unrecognized extension: $ext"
xcrun notarytool submit "$INPUT" --keychain-profile AC_PASSWORD --wait
if test "$ext" = "zip"; then
TMPDIR=$(mktemp -d)
echo "Extracting to $TMPDIR"
unzip -q "$INPUT" -d "$TMPDIR"
# Force glob expansion.
STAPLE_TARGET="$TMPDIR"/*
STAPLE_TARGET=$(echo $STAPLE_TARGET)
else
STAPLE_TARGET="$INPUT"
fi
echo "Stapling $STAPLE_TARGET"
xcrun stapler staple "$STAPLE_TARGET"
if test "$ext" = "zip"; then
# Zip it back up.
INPUT_FULL=$(realpath "$INPUT")
rm -f "$INPUT"
cd "$(dirname "$STAPLE_TARGET")"
zip -r -q "$INPUT_FULL" $(basename "$STAPLE_TARGET")
fi
echo "Processed $INPUT"
if test "$ext" = "zip"; then
spctl -a -v "$STAPLE_TARGET"
fi
done

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env fish
#
# This file produces command specific completions for csv. Meant to be executed
# from the root directory (so the completions get put in the right place).
. build_tools/make_vcs_completions_generic.fish
write_completions csv >share/completions/csv.fish

View File

@@ -0,0 +1,12 @@
#!/usr/bin/env fish
#
# This file produces command specific completions for darcs. Meant to be
# executed from the root directory (so the completions get put in the right
# place).
. build_tools/make_vcs_completions_generic.fish
set darcs_comp 'complete -c darcs -n "not __fish_use_subcommand" -a "(test -f _darcs/prefs/repos; and cat _darcs/prefs/repos)" --description "Darcs repo"'
set darcs_comp $darcs_comp 'complete -c darcs -a "test predist boringfile binariesfile" -n "contains setpref (commandline -poc)" --description "Set the specified option" -x'
write_completions darcs $darcs_comp >share/completions/darcs.fish

18
build_tools/make_deb.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/sh
# Terminate on error
set -e
sudo rm -Rf /tmp/fishfish
mkdir /tmp/fishfish
git archive --format=tar fish_fish | tar -x -C /tmp/fishfish
mkdir /tmp/fishfish/doc-pak
cp README INSTALL CHANGELOG release_notes.html /tmp/fishfish/doc-pak/
cp build_tools/description-pak /tmp/fishfish/
cd /tmp/fishfish
autoconf
./configure
make -j 3
sudo checkinstall --default --pakdir ~/fish_built/ --pkgversion 0.9 make install
mv ~/fish_built/fishfish_0.9-1_i386.deb ~/fish_built/fishfish_0.9_i386.deb

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env fish
#
# This file produces command specific completions for hg. Meant to be executed
# from the root directory (so the completions get put in the right place).
. build_tools/make_vcs_completions_generic.fish
write_completions hg >share/completions/hg.fish

View File

@@ -1,43 +1,23 @@
#!/usr/bin/env bash
#!/bin/sh
# Script to produce an OS X installer .pkg and .app(.zip)
VERSION=$(git describe --always --dirty 2>/dev/null)
VERSION=`sed -E -n 's/^.*PACKAGE_VERSION "([0-9.]+)"/\1/p' osx/config.h`
if test -z "$VERSION" ; then
echo "Could not get version from git"
if test -f version; then
VERSION=$(cat version)
fi
echo "Could not get version from osx/config.h"
exit 1
fi
echo "Version is $VERSION"
set -x
make distclean
rm -Rf /tmp/fish_pkg
#Exit on error
set -e
# Respect MAC_CODESIGN_ID, or default for ad-hoc.
# Note the :- means "or default" and the following - is the value.
MAC_CODESIGN_ID=${MAC_CODESIGN_ID:--}
mkdir -p /tmp/fish_pkg/root /tmp/fish_pkg/intermediates /tmp/fish_pkg/dst
xcodebuild install -scheme install_tree -configuration Release DSTROOT=/tmp/fish_pkg/root/
pkgbuild --scripts build_tools/osx_package_scripts --root /tmp/fish_pkg/root/ --identifier 'com.ridiculousfish.fish-shell-pkg' --version "$VERSION" /tmp/fish_pkg/intermediates/fish.pkg
PKGDIR=$(mktemp -d)
SRC_DIR=$PWD
OUTPUT_PATH=${FISH_ARTEFACT_PATH:-~/fish_built}
mkdir -p "$PKGDIR/build" "$PKGDIR/root" "$PKGDIR/intermediates" "$PKGDIR/dst"
# Pass FISH_USE_SYSTEM_PCRE2=OFF because a system PCRE2 on macOS will not be signed by fish,
# and will probably not be built universal, so the package will fail to validate/run on other systems.
{ cd "$PKGDIR/build" && cmake -DMAC_INJECT_GET_TASK_ALLOW=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_GETTEXT=OFF -DFISH_USE_SYSTEM_PCRE2=OFF -DCMAKE_OSX_ARCHITECTURES='arm64;x86_64' -DMAC_CODESIGN_ID="${MAC_CODESIGN_ID}" "$SRC_DIR" && make VERBOSE=1 -j 12 && env DESTDIR="$PKGDIR/root/" make install; }
pkgbuild --scripts "$SRC_DIR/build_tools/osx_package_scripts" --root "$PKGDIR/root/" --identifier 'com.ridiculousfish.fish-shell-pkg' --version "$VERSION" "$PKGDIR/intermediates/fish.pkg"
productbuild --package-path "$PKGDIR/intermediates" --distribution "$SRC_DIR/build_tools/osx_distribution.xml" --resources "$SRC_DIR/build_tools/osx_package_resources/" "$OUTPUT_PATH/fish-$VERSION.pkg"
MAC_PRODUCTSIGN_ID=${MAC_PRODUCTSIGN_ID:--}
productsign --sign "${MAC_PRODUCTSIGN_ID}" "$OUTPUT_PATH/fish-$VERSION.pkg" "$OUTPUT_PATH/fish-$VERSION-signed.pkg" && mv "$OUTPUT_PATH/fish-$VERSION-signed.pkg" "$OUTPUT_PATH/fish-$VERSION.pkg"
# Make the app
{ cd "$PKGDIR/build" && make -j 12 signed_fish_macapp && zip -r "$OUTPUT_PATH/fish-$VERSION.app.zip" fish.app; }
rm -rf "$PKGDIR"
productbuild --package-path /tmp/fish_pkg/intermediates --distribution build_tools/osx_distribution.xml --resources build_tools/osx_package_resources/ ~/fish_built/fish.pkg

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env fish
#
# This file produces command specific completions for svn. Meant to be executed
# from the root directory (so the completions get put in the right place).
. build_tools/make_vcs_completions_generic.fish
write_completions svn >share/completions/svn.fish

View File

@@ -2,70 +2,45 @@
# Script to generate a tarball
# We use git to output a tree. But we also want to build the user documentation
# and put that in the tarball, so that nobody needs to have sphinx installed
# and put that in the tarball, so that nobody needs to have doxygen installed
# to build it.
# Outputs to $FISH_ARTEFACT_PATH or ~/fish_built by default
# Exit on error
set -e
# We wil generate a tarball with a prefix "fish-VERSION"
# We wil generate a tarball with a prefix "fish"
# git can do that automatically for us via git-archive
# but to get the documentation in, we need to make a symlink called "fish-VERSION"
# but to get the documentation in, we need to make a symlink called "fish"
# and tar from that, so that the documentation gets the right prefix
# We need GNU tar as that supports the --mtime and --transform options
TAR=notfound
for try in tar gtar gnutar; do
if $try -Pcf /dev/null --mtime now /dev/null >/dev/null 2>&1; then
TAR=$try
break
fi
done
if [ "$TAR" = "notfound" ]; then
echo 'No suitable tar (supporting --mtime) found as tar/gtar/gnutar in PATH'
exit 1
fi
# Get the current directory, which we'll use for symlinks
wd="$PWD"
# Get the version from git-describe
VERSION=$(git describe --dirty 2>/dev/null)
# The name of the prefix, which is the directory that you get when you untar
prefix="fish-$VERSION"
prefix="fish"
# The path where we will output the tar file
# Defaults to ~/fish_built
path=${FISH_ARTEFACT_PATH:-~/fish_built}/$prefix.tar
path=~/fish_built/fish-2.0.tar
# Clean up stuff we've written before
rm -f "$path" "$path".xz
rm -f "$path" "$path".gz
# git starts the archive
git archive --format=tar --prefix="$prefix"/ HEAD > "$path"
git archive --format=tar --prefix="$prefix"/ master > "$path"
# tarball out the documentation, generate a version file
PREFIX_TMPDIR=$(mktemp -d)
cd "$PREFIX_TMPDIR"
echo "$VERSION" > version
cmake "$wd"
make doc
# tarball out the documentation
make user_doc
make share/man
cd /tmp
rm -f "$prefix"
ln -s "$wd" "$prefix"
tar --append --file="$path" "$prefix"/user_doc/html
tar --append --file="$path" "$prefix"/share/man
rm -f "$prefix"
TAR_APPEND="$TAR --append --file=$path --mtime=now --owner=0 --group=0 \
--mode=g+w,a+rX --transform s/^/$prefix\//"
$TAR_APPEND --no-recursion user_doc
$TAR_APPEND user_doc/html user_doc/man
$TAR_APPEND version
cd -
rm -r "$PREFIX_TMPDIR"
# xz it
xz "$path"
# gzip it
gzip "$path"
# Output what we did, and the sha1 hash
echo "Tarball written to $path".xz
openssl dgst -sha256 "$path".xz
echo "Tarball written to $path".gz
openssl sha1 "$path".gz

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env fish
#
# This file produces command specific completions for hg, darcs and a
# few other vcs systems.
build_tools/make_darcs_completions.fish
build_tools/make_hg_completions.fish
build_tools/make_svn_completions.fish
build_tools/make_csv_completions.fish

View File

@@ -0,0 +1,229 @@
#!/usr/bin/env fish
#
# This file provides generic functions for generating specific completions for
# hg, darcs and a few other vcs systems. It uses the fact that all these
# systems have a somewhat uniform command line help mechanism.
#
function cap
set res (echo $argv |cut -c 1|tr a-z A-Z)(echo $argv |cut -c 2-)
echo $res
end
#
# Escapes the single quote (') character and removes trailing whitespace from $argv
#
function esc
echo $argv | sed -e "s/\(['\\\]\)/\\\\\1/g" | sed -e 's/ *$//' | sed -e 's/ .*//'
end
#
# This function formats a list of completion information into a set of fish completions
#
# The first argument is the condition string, which will be copied to
# the resulting commandline verbatim
#
# Remaining arguments are tab separated lists of completion
# information. Each list contains four elements, the short switch, the
# long switch, the argument and the description.
#
function complete_from_list
set condition $argv[1]
set -e argv[1]
for j in $argv
set exploded (echo $j|tr \t \n)
set short $exploded[1]
set long $exploded[2]
set arg $exploded[3]
set desc (cap (esc $exploded[4]))
set str
switch $short
case '-?'
set str $str -s (printf "%s\n" $short|cut -c 2)
end
switch $long
case '--?*'
set str $str -l (printf "%s\n" $long|cut -c 3-)
end
switch $arg
case '=DIRECTORY' ' dir'
set str $str -x -a "'(__fish_complete_directories (commandline -ct))'"
case '=COMMAND'
set str $str -x -a "'(__fish_complete_command)'"
case '=USERNAME' ' <user>'
set str $str -x -a "'(__fish_complete_users)'"
case '=FILENAME' '=FILE' ' <file>'
set str $str -r
case ' arg'
set str $str -x
case ' (*):'
set str $str -x -a \'(echo $arg| sed -e "s/ (\(.*\)):/\1/" |tr '/' ' ')\'
case '?*'
set str $str -x
if not set -q unknown
set -g unknown
end
if not contains $arg $unknown
echo "Don't know how to handle arguments of type '$arg'" >&2
set unknown $unknown $arg
end
end
switch $desc
case '?*'
set str $str --description \'$desc\'
end
echo complete -c $cmd $condition $str
end
end
function write_completions
set -g cmd $argv[1]; or return 1
echo "Making completions for $cmd" >&2
echo '
#
# Completions for the '$cmd' command
# This file was autogenerated by the file make_vcs_completions.fish
# which is shipped with the fish source code.
#
#
# Completions from commandline
#
'
set -e argv[1]
while count $argv >/dev/null
echo $argv[1]
set -e argv[1]
end
eval "function cmd; $cmd \$argv; end"
set -l cmd_str
switch $cmd
case svn
function list_subcommand
set cmd1 '\([^ ]*\)'
set cmd2 '\([^,)]*\)'
set cmdn '\(, \([^,)]*\)\|\)'
set svn_re '^ *'$cmd1'\( ('$cmd2$cmdn$cmdn')\|\).*$'
cmd help|sed -ne 's/'$svn_re'/\1\n\3\n\5\n\7/p'| grep .
end
function list_subcommand_help
set short_exp '\(-.\|\)'
set long_exp '\(--[^ =,]*\)'
set arg_exp '\(\|[= ][^ ][^ ]*\)'
set desc_exp '\([\t ]*:[\t ]*\|\)\([^ ].*[^.]\)'
set re "^ *$short_exp *$long_exp$arg_exp *$desc_exp\(\|\\.\)\$"
cmd help $argv | sed -n -e 's/'$re'/\1\t\2\t\3\t\5/p'
end
for i in (list_subcommand)
set desc (cmd help $i|head -n 3| sed -e 's/usage:.*//'| tr \n \ | sed -e 's/[^:]*: *\(.*[^.]\)\(\|\\.\)$/\1/')
set desc (esc $desc)
set cmd_str $cmd_str "-a $i --description '$desc'"
end
case cvs
function list_subcommand
cmd --help-commands 2>| sed -n -e 's/^ *\([^ ][^ ]*\) .*$/\1/p'
end
set short_exp '\(-.\)'
set arg_exp '\(\| [^ \t][^ \t]*\)'
set desc_exp '\([\t ]*:[\t ]*\|\)\([^ ].*\)'
set -g re '^[ \t]*'$short_exp$arg_exp'[ \t]*'$desc_exp'$'
function list_subcommand_help
#'s/^[ \t]*\(-.\)[ \t]\([^- \t][^ \t]*\)*[ \t]*\([^-].*\)$/\1\t\2\t\3/p'
cmd -H $argv 2>| sed -n -e 's/'$re'/\1\t\t\2\t\4/p'
end
echo '
#
# Global switches
#
'
complete_from_list "-n '__fish_use_subcommand'" (cmd --help-options 2>| sed -n -e 's/'$re'/\1\t\t\2\t\4/p')
set cmd_str_internal (cmd --help-commands 2>| sed -n -e 's/^ *\([^ ][^ ]*\)[\t ]*\([^ ].*\)$/\1\t\2/p')
for i in $cmd_str_internal
set exploded (echo $i|tr \t \n)
set cmd_str $cmd_str "-a $exploded[1] --description '"(esc $exploded[2])"'"
end
case '*'
function list_subcommand
cmd help | sed -n -e 's/^ *\([^ ][^ ]*\) .*$/\1/p'
end
function list_subcommand_help
set -l short_exp '\(-.\|\)\( [^ -][^ ]*\|\)'
set -l long_exp '\(--[^ =,]*\)'
set -l arg_exp '\(\|[= ][^ ][^ ]*\)'
set -l desc_exp '\([\t ]*:[\t ]*\|\)\([^ ].*[^.]\)'
set -l re "^ *$short_exp *$long_exp$arg_exp *$desc_exp\(\|\\.\)\$"
cmd help $argv | sed -n -e 's/'$re'/\1\t\3\t\4\t\6/p'
end
set cmd_str (cmd help | sed -n -e 's/^ *\([^ ][^ ]*\)[\t ]*\([^ ].*[^.]\)\(\|\\.\)$/-a \1 --description \'\2\'/p')
end
echo '
#
# subcommands
#
'
printf "complete -c $cmd -n '__fish_use_subcommand' -x %s\n" $cmd_str
for i in (list_subcommand)
echo '
#
# Completions for the \''$i'\' subcommand
#
'
complete_from_list "-n 'contains \\'$i\\' (commandline -poc)'" (list_subcommand_help $i)
end
echo \n\n
end

View File

@@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<installer-gui-script minSpecVersion="1">
<title>fish shell</title>
<welcome file="welcome.html" mime-type="text/html"/>
<welcome file="welcome.rtf"/>
<background file="terminal_logo.png" scaling="proportional" alignment="bottomleft"/>
<pkg-ref id="com.ridiculousfish.fish-shell-pkg"/>
<options hostArchitectures="arm64,x86_64" rootVolumeOnly="true"/>
<options customize="never" require-scripts="true"/>
<options customize="never" require-scripts="false"/>
<choices-outline>
<line choice="default">
<line choice="com.ridiculousfish.fish-shell-pkg"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -1,29 +0,0 @@
<html>
<head>
<style>
body {
font-family: system-ui, -apple-system, "Helvetica Neue", sans-serif;
font-size: 10pt;
}
code, tt {
font-family: ui-monospace, Menlo, monospace;
}
</style>
</head>
<body>
<p>
<strong>fish</strong> is a smart and user-friendly command line shell. For more information, visit <a href="https://fishshell.com">fishshell.com</a>.
</p>
<p>
<strong>fish</strong> will be installed into <tt>/usr/local/</tt>, and its path will be added to <wbr><tt>/etc/shells</tt> if necessary.
</p>
<p>
Your default shell will <em>not</em> be changed. To make <strong>fish</strong> your login shell after the installation, run:
</p>
<p>
<code>chsh -s /usr/local/bin/fish</code>
</p>
<p>Enjoy! Bugs can be reported on <a href="https://github.org/fish-shell/fish-shell/">GitHub</a>.</p>
</body>
</html>

View File

@@ -0,0 +1,25 @@
{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf370
{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 Monaco;}
{\colortbl;\red255\green255\blue255;}
{\info
{\author dlkfjslfjsfdlkfk}}\margl1440\margr1440\vieww10800\viewh8400\viewkind0
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural
\f0\fs30 \cf0 The fish shell is a smart and user friendly command line shell. For more information, visit {\field{\*\fldinst{HYPERLINK "http://fishshell.com"}}{\fldrslt http://fishshell.com}}.\
\
fish will be installed into
\f1\fs26 /usr/local/
\f0\fs30 , and fish will be added to
\f1\fs26 /etc/shells
\f0\fs30 if necessary.\
\
Your default shell will
\i not
\i0 be changed. To make fish your default, run:\
\
\f1 chsh -s /usr/local/bin/fish
\f0 \
\
Enjoy!\
}

View File

@@ -1,3 +1,3 @@
#!/bin/sh -x
./add-shell ${DSTVOLUME}usr/local/bin/fish
./add-shell /usr/local/bin/fish > /tmp/fish_postinstall_output.log

View File

@@ -1,7 +0,0 @@
#!/bin/sh -x
echo "Removing any previous installation"
pkgutil --pkg-info ${INSTALL_PKG_SESSION_ID} && pkgutil --only-files --files ${INSTALL_PKG_SESSION_ID} | while read installed
do rm -v ${DSTVOLUME}${installed}
done
echo "... removed"

View File

@@ -1,365 +0,0 @@
"""pexpect_helper provides a wrapper around the pexpect module.
This module exposes a single class SpawnedProc, which wraps pexpect.spawn().
This exposes a pseudo-tty, which fish or another process may talk to.
The send() function may be used to send data to fish, and the expect_* family
of functions may be used to match what is output to the tty.
Example usage:
sp = SpawnedProc() # this launches fish
sp.expect_prompt() # wait for a prompt
sp.sendline("echo hello world")
sp.expect_prompt("hello world")
"""
from __future__ import print_function
import inspect
import os
import os.path
import re
import sys
import time
import pexpect
# Default timeout for failing to match.
TIMEOUT_SECS = 5
UNEXPECTED_SUCCESS = object()
def get_prompt_re(counter):
"""Return a regular expression for matching a with a given prompt counter."""
return re.compile(
r"""(?:\r\n?|^) # beginning of line
(?:\x1b[\d\[KB(m]*)* # optional colors
(?:\[.\]\ )? # optional vi mode prompt
"""
+ (r"prompt\ %d>" % counter) # prompt with counter
+ r"""
(?:\x1b[\d\[KB(m]*)* # optional colors
""",
re.VERBOSE,
)
def get_callsite():
"""Return a triple (filename, line_number, line_text) of the call site location."""
callstack = inspect.getouterframes(inspect.currentframe())
for f in callstack:
# Skip call sites from this file.
if inspect.getmodule(f.frame) is Message.MODULE:
continue
# Skip functions which have a truthy callsite_skip attribute.
if getattr(f.function, "callsite_skip", False):
continue
return (os.path.basename(f.filename), f.lineno, f.code_context)
return ("Unknown", -1, "")
def escape(s):
"""Escape the string 's' to make it human-understandable."""
res = []
for c in s:
if c == "\n":
res.append("\\n")
elif c == "\r":
res.append("\\r")
elif c == "\t":
res.append("\\t")
elif c.isprintable():
res.append(c)
else:
res.append("\\x{:02x}".format(ord(c)))
return "".join(res)
def pexpect_error_type(err):
"""Return a human-readable description of a pexpect error type."""
if isinstance(err, pexpect.EOF):
return "EOF"
elif isinstance(err, pexpect.TIMEOUT):
return "timeout"
elif err is UNEXPECTED_SUCCESS:
return "unexpected success"
else:
return "unknown error"
class Message(object):
"""Some text either sent-to or received-from the spawned proc.
Attributes:
dir: the message direction, either DIR_INPUT or DIR_OUTPUT
filename: the name of the file from which the message was sent
text: the text of the messages
when: a timestamp of when the message was sent
"""
# Input is input into fish shell ("sent data").
DIR_INPUT = " INPUT"
# Output means output from fish shell ("received data").
DIR_OUTPUT = "OUTPUT"
MODULE = sys.modules[__name__]
def __init__(self, dir, text, when):
"""Construct from a direction, message text and timestamp."""
self.dir = dir
self.filename, self.lineno, _ = get_callsite()
self.text = text
self.when = when
@staticmethod
def sent_input(text, when):
"""Return an input message with the given text."""
return Message(Message.DIR_INPUT, text, when)
@staticmethod
def received_output(text, when):
"""Return a output message with the given text."""
return Message(Message.DIR_OUTPUT, text, when)
class SpawnedProc(object):
"""A process, talking to our ptty. This wraps pexpect.spawn.
Attributes:
colorize: whether error messages should have ANSI color escapes
messages: list of Message sent and received, in-order
start_time: the timestamp of the first message, or None if none yet
spawn: the pexpect.spawn value
prompt_counter: the index of the prompt. This cooperates with the fish_prompt
function to ensure that each printed prompt is distinct.
"""
def __init__(
self, name="fish", timeout=TIMEOUT_SECS, env=os.environ.copy(), **kwargs
):
"""Construct from a name, timeout, and environment.
Args:
name: the name of the executable to launch, as a key into the
environment dictionary. By default this is 'fish' but may be
other executables.
timeout: A timeout to pass to pexpect. This indicates how long to wait
before giving up on some expected output.
env: a string->string dictionary, describing the environment variables.
"""
if name not in env:
raise ValueError("'%s' variable not found in environment" % name)
exe_path = env.get(name)
self.colorize = sys.stdout.isatty() or env.get("FISH_FORCE_COLOR", "0") == "1"
self.messages = []
self.start_time = None
self.spawn = pexpect.spawn(
exe_path, env=env, encoding="utf-8", timeout=timeout, **kwargs
)
self.spawn.delaybeforesend = None
self.prompt_counter = 0
def time_since_first_message(self):
"""Return a delta in seconds since the first message, or 0 if this is the first."""
now = time.monotonic()
if not self.start_time:
self.start_time = now
return now - self.start_time
def send(self, s):
"""Cover over pexpect.spawn.send().
Send the given string to the tty, returning the number of bytes written.
"""
res = self.spawn.send(s)
when = self.time_since_first_message()
self.messages.append(Message.sent_input(s, when))
return res
def sendline(self, s):
"""Cover over pexpect.spawn.sendline().
Send the given string + linesep to the tty, returning the number of bytes written.
"""
return self.send(s + os.linesep)
def expect_re(self, pat, pat_desc=None, unmatched=None, shouldfail=False, **kwargs):
"""Cover over pexpect.spawn.expect().
Consume all "new" output of self.spawn until the given pattern is matched, or
the timeout is reached.
Note that output between the current position and the location of the match is
consumed as well.
The pattern is typically a regular expression in string form, but may also be
any of the types accepted by pexpect.spawn.expect().
If the 'unmatched' parameter is given, it is printed as part of the error message
of any failure.
On failure, this prints an error and exits.
"""
try:
self.spawn.expect(pat, **kwargs)
when = self.time_since_first_message()
self.messages.append(
Message.received_output(self.spawn.match.group(), when)
)
# When a match is found,
# spawn.match is the MatchObject that produced it.
# This can be used to check what exactly was matched.
if shouldfail:
err = UNEXPECTED_SUCCESS
if not pat_desc:
pat_desc = str(pat)
self.report_exception_and_exit(pat_desc, unmatched, err)
return self.spawn.match
except pexpect.ExceptionPexpect as err:
if shouldfail:
return True
if not pat_desc:
pat_desc = str(pat)
self.report_exception_and_exit(pat_desc, unmatched, err)
def expect_str(self, s, **kwargs):
"""Cover over expect_re() which accepts a literal string."""
return self.expect_re(re.escape(s), **kwargs)
def expect_prompt(self, *args, increment=True, **kwargs):
"""Convenience function which matches some text and then a prompt.
Match the given positional arguments as expect_re, and then look
for a prompt.
If increment is set, then this should be a new prompt and the prompt counter
should be bumped; otherwise this is not a new prompt.
Returns None on success, and exits on failure.
Example:
sp.sendline("echo hello world")
sp.expect_prompt("hello world")
"""
if args:
self.expect_re(*args, **kwargs)
if increment:
self.prompt_counter += 1
self.expect_re(
get_prompt_re(self.prompt_counter),
pat_desc="prompt %d" % self.prompt_counter,
)
def report_exception_and_exit(self, pat, unmatched, err):
"""Things have gone badly.
We have an exception 'err', some pexpect.ExceptionPexpect.
Report it to stdout, along with the offending call site.
If 'unmatched' is set, print it to stdout.
"""
colors = self.colors()
failtype = pexpect_error_type(err)
fmtkeys = {"failtype": failtype, "pat": escape(pat)}
fmtkeys.update(**colors)
filename, lineno, code_context = get_callsite()
fmtkeys["filename"] = filename
fmtkeys["lineno"] = lineno
fmtkeys["code"] = "\n".join(code_context)
if unmatched:
print(
"{RED}Error: {NORMAL}{BOLD}{unmatched}{RESET}".format(
unmatched=unmatched, **fmtkeys
)
)
print(
"{RED}Failed to match pattern:{NORMAL} {BOLD}{pat}{RESET}".format(**fmtkeys)
)
print(
"{filename}:{lineno}: {BOLD}{failtype}{RESET} from {code}".format(**fmtkeys)
)
print("")
print("{CYAN}Escaped buffer:{RESET}".format(**colors))
print(escape(self.spawn.before))
print("")
print("{CYAN}When written to the tty, this looks like:{RESET}".format(**colors))
print("{CYAN}<-------{RESET}".format(**colors))
sys.stdout.write(self.spawn.before)
sys.stdout.flush()
print("{RESET}\n{CYAN}------->{RESET}".format(**colors))
print("")
# Show the last 10 messages.
print("Last 10 messages:")
delta = None
for m in self.messages[-10:]:
etext = escape(m.text)
timestamp = m.when * 1000.0
# Use relative timestamps and add a sign.
# This assumes a max length of 10^10 milliseconds (115 days) for the initial timestamp,
# and 11.5 days for the delta.
if delta:
timestamp -= delta
timestampstr = "{timestamp:+10.2f} ms".format(timestamp=timestamp)
else:
timestampstr = "{timestamp:10.2f} ms".format(timestamp=timestamp)
delta = m.when * 1000.0
print(
"{dir} {timestampstr} (Line {lineno}): {BOLD}{etext}{RESET}".format(
dir=m.dir,
timestampstr=timestampstr,
filename=m.filename,
lineno=m.lineno,
etext=etext,
**colors
)
)
print("")
sys.exit(1)
def sleep(self, secs):
"""Cover over time.sleep()."""
time.sleep(secs)
def colors(self):
"""Return a dictionary mapping color names to ANSI escapes"""
def ansic(n):
"""Return either an ANSI escape sequence for a color, or empty string."""
return "\033[%dm" % n if self.colorize else ""
return {
"RESET": ansic(0),
"BOLD": ansic(1),
"NORMAL": ansic(39),
"BLACK": ansic(30),
"RED": ansic(31),
"GREEN": ansic(32),
"YELLOW": ansic(33),
"BLUE": ansic(34),
"MAGENTA": ansic(35),
"CYAN": ansic(36),
"LIGHTGRAY": ansic(37),
"DARKGRAY": ansic(90),
"LIGHTRED": ansic(91),
"LIGHTGREEN": ansic(92),
"LIGHTYELLOW": ansic(93),
"LIGHTBLUE": ansic(94),
"LIGHTMAGENTA": ansic(95),
"LIGHTCYAN": ansic(96),
"WHITE": ansic(97),
}
def control(char: str) -> str:
""" Returns the char sent when control is pressed along the given key. """
assert len(char) == 1
char = char.lower()
if ord("a") <= ord(char) <= ord("z"):
return chr(ord(char) - ord("a") + 1)
return chr({
"@": 0,
"`": 0,
"[": 27,
"{": 27,
"\\": 28,
"|": 28,
"]": 29,
"}": 29,
"^": 30,
"~": 30,
"_": 31,
"?": 127,
}[char])

View File

@@ -1,119 +0,0 @@
#!/usr/bin/env fish
#
# This runs C++ files and fish scripts (*.fish) through their respective code
# formatting programs.
#
set -l git_clang_format no
set -l c_files
set -l fish_files
set -l python_files
set -l all no
if test "$argv[1]" = --all
set all yes
set -e argv[1]
end
if set -q argv[1]
echo "Unexpected arguments: '$argv'"
exit 1
end
if test $all = yes
set -l files (git status --porcelain --short --untracked-files=all | sed -e 's/^ *[^ ]* *//')
if set -q files[1]
echo
echo 'You have uncommitted changes. Are you sure you want to restyle?'
read -P 'y/N? ' -n1 -l ans
if not string match -qi "y" -- $ans
exit 1
end
end
set c_files src/*.h src/*.cpp src/*.c
set fish_files share/**.fish
set python_files {doc_src,share,tests}/**.py
else
# We haven't been asked to reformat all the source. If there are uncommitted changes reformat
# those using `git clang-format`. Else reformat the files in the most recent commit.
# Select (cached files) (modified but not cached, and untracked files)
set -l files (git diff-index --cached HEAD --name-only) (git ls-files --exclude-standard --others --modified)
if set -q files[1]
set git_clang_format yes
else
# No pending changes so lint the files in the most recent commit.
set files (git diff-tree --no-commit-id --name-only -r HEAD)
end
# Extract just the C/C++ files that exist.
set c_files
for file in (string match -r '^.*\.(?:c|cpp|h)$' -- $files)
test -f $file; and set c_files $c_files $file
end
# Extract just the fish files.
set fish_files (string match -r '^.*\.fish$' -- $files)
set python_files (string match -r '^.*\.py$' -- $files)
end
set -l red (set_color red)
set -l green (set_color green)
set -l blue (set_color blue)
set -l normal (set_color normal)
# Run the C++ reformatter if we have any C++ files.
if set -q c_files[1]
if test $git_clang_format = yes
if type -q git-clang-format
echo === Running "$red"git-clang-format"$normal"
git add $c_files
git-clang-format
else
echo
echo 'WARNING: Cannot find git-clang-format command'
echo
end
else if type -q clang-format
echo === Running "$red"clang-format"$normal"
for file in $c_files
if clang-format --dry-run -Werror $file
# file was clean, remove it from the list
set -e c_files[(contains -i $file $c_files)]
end
end
if set -q c_files[1]
printf "Reformat those %d files?\n" (count $c_files)
read -P 'y/N? ' -n1 -l ans
if string match -qi "y" -- $ans
clang-format -i --verbose $c_files
else if string match -qi "n" -- $ans
echo Skipping
else # like they ctrl-C'd or something.
exit 1
end
end
else
echo
echo 'WARNING: Cannot find clang-format command'
echo
end
end
# Run the fish reformatter if we have any fish files.
if set -q fish_files[1]
if not type -q fish_indent
make fish_indent
set PATH . $PATH
end
echo === Running "$green"fish_indent"$normal"
fish_indent -w -- $fish_files
end
if set -q python_files[1]
if not type -q black
echo
echo Please install "`black`" to style python
echo
else
echo === Running "$blue"black"$normal"
black $python_files
end
end

4147
builtin.cpp Normal file

File diff suppressed because it is too large Load Diff

182
builtin.h Normal file
View File

@@ -0,0 +1,182 @@
/** \file builtin.h
Prototypes for functions for executing builtin functions.
*/
#ifndef FISH_BUILTIN_H
#define FISH_BUILTIN_H
#include <wchar.h>
#include "util.h"
#include "io.h"
#include "common.h"
class parser_t;
enum
{
COMMAND_NOT_BUILTIN,
BUILTIN_REGULAR,
BUILTIN_FUNCTION
}
;
/**
Error message on missing argument
*/
#define BUILTIN_ERR_MISSING _( L"%ls: Expected argument\n" )
/**
Error message on invalid combination of options
*/
#define BUILTIN_ERR_COMBO _( L"%ls: Invalid combination of options\n" )
/**
Error message on invalid combination of options
*/
#define BUILTIN_ERR_COMBO2 _( L"%ls: Invalid combination of options,\n%ls\n" )
/**
Error message on multiple scope levels for variables
*/
#define BUILTIN_ERR_GLOCAL _( L"%ls: Variable scope can only be one of universal, global and local\n" )
/**
Error message for specifying both export and unexport to set/read
*/
#define BUILTIN_ERR_EXPUNEXP _( L"%ls: Variable can't be both exported and unexported\n" )
/**
Error message for unknown switch
*/
#define BUILTIN_ERR_UNKNOWN _( L"%ls: Unknown option '%ls'\n" )
/**
Error message for invalid character in variable name
*/
#define BUILTIN_ERR_VARCHAR _( L"%ls: Invalid character '%lc' in variable name. Only alphanumerical characters and underscores are valid in a variable name.\n" )
/**
Error message for invalid (empty) variable name
*/
#define BUILTIN_ERR_VARNAME_ZERO _( L"%ls: Variable name can not be the empty string\n" )
/**
Error message when second argument to for isn't 'in'
*/
#define BUILTIN_FOR_ERR_IN _( L"%ls: Second argument must be 'in'\n" )
/**
Error message for insufficient number of arguments
*/
#define BUILTIN_FOR_ERR_COUNT _( L"%ls: Expected at least two arguments, got %d\n")
#define BUILTIN_FOR_ERR_NAME _( L"%ls: '%ls' is not a valid variable name\n" )
/** Error messages for 'else if' */
#define BUILTIN_ELSEIF_ERR_COUNT _( L"%ls: can only take 'if' and then another command as an argument\n")
#define BUILTIN_ELSEIF_ERR_ARGUMENT _( L"%ls: any second argument must be 'if'\n")
/**
Error message when too many arguments are supplied to a builtin
*/
#define BUILTIN_ERR_TOO_MANY_ARGUMENTS _( L"%ls: Too many arguments\n" )
/**
Error message when block types mismatch in the end builtin, e.g. 'begin; end for'
*/
#define BUILTIN_END_BLOCK_MISMATCH _( L"%ls: Block mismatch: '%ls' vs. '%ls'\n" )
/**
Error message for unknown block type in the end builtin, e.g. 'begin; end beggin'
*/
#define BUILTIN_END_BLOCK_UNKNOWN _( L"%ls: Unknown block type '%ls'\n" )
#define BUILTIN_ERR_NOT_NUMBER _( L"%ls: Argument '%ls' is not a number\n" )
/** Get the string used to represent stdout and stderr */
const wcstring &get_stdout_buffer();
const wcstring &get_stderr_buffer();
/** Output an error */
void builtin_show_error(const wcstring &err);
/**
Kludge. Tells builtins if output is to screen
*/
extern int builtin_out_redirect;
/**
Kludge. Tells builtins if error is to screen
*/
extern int builtin_err_redirect;
/**
Initialize builtin data.
*/
void builtin_init();
/**
Destroy builtin data.
*/
void builtin_destroy();
/**
Is there a builtin command with the given name?
*/
int builtin_exists(const wcstring &cmd);
/**
Execute a builtin command
\param parser The parser being used
\param argv Array containing the command and parameters
of the builtin. The list is terminated by a
null pointer. This syntax resembles the syntax
for exec.
\param io the io redirections to perform on this builtin.
\return the exit status of the builtin command
*/
int builtin_run(parser_t &parser, const wchar_t * const *argv, const io_chain_t &io);
/** Returns a list of all builtin names */
wcstring_list_t builtin_get_names(void);
/** Insert all builtin names into list. */
void builtin_get_names(std::vector<completion_t> &list);
/**
Pushes a new set of input/output to the stack. The new stdin is supplied, a new set of output strings is created.
*/
void builtin_push_io(parser_t &parser, int stdin_fd);
/**
Pops a set of input/output from the stack. The output strings are destroued, but the input file is not closed.
*/
void builtin_pop_io(parser_t &parser);
/**
Return a one-line description of the specified builtin.
*/
wcstring builtin_get_desc(const wcstring &b);
/**
Slightly kludgy function used with 'complete -C' in order to make
the commandline builtin operate on the string to complete instead
of operating on whatever is to be completed.
*/
const wchar_t *builtin_complete_get_temporary_buffer();
/**
Run the __fish_print_help function to obtain the help information
for the specified command.
*/
wcstring builtin_help_get(parser_t &parser, const wchar_t *cmd);
#endif

642
builtin_commandline.cpp Normal file
View File

@@ -0,0 +1,642 @@
/** \file builtin_commandline.c Functions defining the commandline builtin
Functions used for implementing the commandline builtin.
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <wctype.h>
#include <sys/types.h>
#include <termios.h>
#include <signal.h>
#include "fallback.h"
#include "util.h"
#include "wutil.h"
#include "builtin.h"
#include "common.h"
#include "wgetopt.h"
#include "reader.h"
#include "proc.h"
#include "parser.h"
#include "tokenizer.h"
#include "input_common.h"
#include "input.h"
#include "parse_util.h"
/**
Which part of the comandbuffer are we operating on
*/
enum
{
STRING_MODE=1, /**< Operate on entire buffer */
JOB_MODE, /**< Operate on job under cursor */
PROCESS_MODE, /**< Operate on process under cursor */
TOKEN_MODE /**< Operate on token under cursor */
}
;
/**
For text insertion, how should it be done
*/
enum
{
REPLACE_MODE=1, /**< Replace current text */
INSERT_MODE, /**< Insert at cursor position */
APPEND_MODE /**< Insert at end of current token/command/buffer */
}
;
/**
Pointer to what the commandline builtin considers to be the current
contents of the command line buffer.
*/
static const wchar_t *current_buffer=0;
/**
What the commandline builtin considers to be the current cursor
position.
*/
static size_t current_cursor_pos = (size_t)(-1);
/**
Returns the current commandline buffer.
*/
static const wchar_t *get_buffer()
{
return current_buffer;
}
/**
Returns the position of the cursor
*/
static size_t get_cursor_pos()
{
return current_cursor_pos;
}
/**
Replace/append/insert the selection with/at/after the specified string.
\param begin beginning of selection
\param end end of selection
\param insert the string to insert
\param append_mode can be one of REPLACE_MODE, INSERT_MODE or APPEND_MODE, affects the way the test update is performed
*/
static void replace_part(const wchar_t *begin,
const wchar_t *end,
const wchar_t *insert,
int append_mode)
{
const wchar_t *buff = get_buffer();
size_t out_pos = get_cursor_pos();
wcstring out;
out.append(buff, begin - buff);
switch (append_mode)
{
case REPLACE_MODE:
{
out.append(insert);
out_pos = wcslen(insert) + (begin-buff);
break;
}
case APPEND_MODE:
{
out.append(begin, end-begin);
out.append(insert);
break;
}
case INSERT_MODE:
{
long cursor = get_cursor_pos() -(begin-buff);
out.append(begin, cursor);
out.append(insert);
out.append(begin+cursor, end-begin-cursor);
out_pos += wcslen(insert);
break;
}
}
out.append(end);
reader_set_buffer(out, out_pos);
}
/**
Output the specified selection.
\param begin start of selection
\param end end of selection
\param cut_at_cursor whether printing should stop at the surrent cursor position
\param tokenize whether the string should be tokenized, printing one string token on every line and skipping non-string tokens
*/
static void write_part(const wchar_t *begin,
const wchar_t *end,
int cut_at_cursor,
int tokenize)
{
wcstring out;
wchar_t *buff;
size_t pos;
pos = get_cursor_pos()-(begin-get_buffer());
if (tokenize)
{
buff = wcsndup(begin, end-begin);
// fwprintf( stderr, L"Subshell: %ls, end char %lc\n", buff, *end );
out.clear();
tokenizer_t tok(buff, TOK_ACCEPT_UNFINISHED);
for (; tok_has_next(&tok); tok_next(&tok))
{
if ((cut_at_cursor) &&
(tok_get_pos(&tok)+wcslen(tok_last(&tok)) >= pos))
break;
switch (tok_last_type(&tok))
{
case TOK_STRING:
{
out.append(escape_string(tok_last(&tok), UNESCAPE_INCOMPLETE));
out.push_back(L'\n');
break;
}
}
}
stdout_buffer.append(out);
free(buff);
}
else
{
if (cut_at_cursor)
{
end = begin+pos;
}
// debug( 0, L"woot2 %ls -> %ls", buff, esc );
stdout_buffer.append(begin, end - begin);
stdout_buffer.append(L"\n");
}
}
/**
The commandline builtin. It is used for specifying a new value for
the commandline.
*/
static int builtin_commandline(parser_t &parser, wchar_t **argv)
{
int buffer_part=0;
int cut_at_cursor=0;
int argc = builtin_count_args(argv);
int append_mode=0;
int function_mode = 0;
int tokenize = 0;
int cursor_mode = 0;
int line_mode = 0;
int search_mode = 0;
const wchar_t *begin, *end;
current_buffer = (wchar_t *)builtin_complete_get_temporary_buffer();
if (current_buffer)
{
current_cursor_pos = wcslen(current_buffer);
}
else
{
current_buffer = reader_get_buffer();
current_cursor_pos = reader_get_cursor_pos();
}
if (!get_buffer())
{
if (is_interactive_session)
{
/*
Prompt change requested while we don't have
a prompt, most probably while reading the
init files. Just ignore it.
*/
return 1;
}
stderr_buffer.append(argv[0]);
stderr_buffer.append(L": Can not set commandline in non-interactive mode\n");
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
woptind=0;
while (1)
{
static const struct woption
long_options[] =
{
{
L"append", no_argument, 0, 'a'
}
,
{
L"insert", no_argument, 0, 'i'
}
,
{
L"replace", no_argument, 0, 'r'
}
,
{
L"current-job", no_argument, 0, 'j'
}
,
{
L"current-process", no_argument, 0, 'p'
}
,
{
L"current-token", no_argument, 0, 't'
}
,
{
L"current-buffer", no_argument, 0, 'b'
}
,
{
L"cut-at-cursor", no_argument, 0, 'c'
}
,
{
L"function", no_argument, 0, 'f'
}
,
{
L"tokenize", no_argument, 0, 'o'
}
,
{
L"help", no_argument, 0, 'h'
}
,
{
L"input", required_argument, 0, 'I'
}
,
{
L"cursor", no_argument, 0, 'C'
}
,
{
L"line", no_argument, 0, 'L'
}
,
{
L"search-mode", no_argument, 0, 'S'
}
,
{
0, 0, 0, 0
}
}
;
int opt_index = 0;
int opt = wgetopt_long(argc,
argv,
L"abijpctwforhI:CLS",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
case 0:
if (long_options[opt_index].flag != 0)
break;
append_format(stderr_buffer,
BUILTIN_ERR_UNKNOWN,
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
case L'a':
append_mode = APPEND_MODE;
break;
case L'b':
buffer_part = STRING_MODE;
break;
case L'i':
append_mode = INSERT_MODE;
break;
case L'r':
append_mode = REPLACE_MODE;
break;
case 'c':
cut_at_cursor=1;
break;
case 't':
buffer_part = TOKEN_MODE;
break;
case 'j':
buffer_part = JOB_MODE;
break;
case 'p':
buffer_part = PROCESS_MODE;
break;
case 'f':
function_mode=1;
break;
case 'o':
tokenize=1;
break;
case 'I':
current_buffer = woptarg;
current_cursor_pos = wcslen(woptarg);
break;
case 'C':
cursor_mode = 1;
break;
case 'L':
line_mode = 1;
break;
case 'S':
search_mode = 1;
break;
case 'h':
builtin_print_help(parser, argv[0], stdout_buffer);
return 0;
case L'?':
builtin_unknown_option(parser, argv[0], argv[woptind-1]);
return 1;
}
}
if (function_mode)
{
int i;
/*
Check for invalid switch combinations
*/
if (buffer_part || cut_at_cursor || append_mode || tokenize || cursor_mode || line_mode || search_mode)
{
append_format(stderr_buffer,
BUILTIN_ERR_COMBO,
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
if (argc == woptind)
{
append_format(stderr_buffer,
BUILTIN_ERR_MISSING,
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
for (i=woptind; i<argc; i++)
{
wchar_t c = input_function_get_code(argv[i]);
if (c != (wchar_t)(-1))
{
/*
input_unreadch inserts the specified keypress or
readline function at the top of the stack of unused
keypresses
*/
input_unreadch(c);
}
else
{
append_format(stderr_buffer,
_(L"%ls: Unknown input function '%ls'\n"),
argv[0],
argv[i]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
}
return 0;
}
/*
Check for invalid switch combinations
*/
if ((search_mode || line_mode || cursor_mode) && (argc-woptind > 1))
{
append_format(stderr_buffer,
argv[0],
L": Too many arguments\n",
NULL);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
if ((buffer_part || tokenize || cut_at_cursor) && (cursor_mode || line_mode || search_mode))
{
append_format(stderr_buffer,
BUILTIN_ERR_COMBO,
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
if ((tokenize || cut_at_cursor) && (argc-woptind))
{
append_format(stderr_buffer,
BUILTIN_ERR_COMBO2,
argv[0],
L"--cut-at-cursor and --tokenize can not be used when setting the commandline");
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
if (append_mode && !(argc-woptind))
{
append_format(stderr_buffer,
BUILTIN_ERR_COMBO2,
argv[0],
L"insertion mode switches can not be used when not in insertion mode");
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
/*
Set default modes
*/
if (!append_mode)
{
append_mode = REPLACE_MODE;
}
if (!buffer_part)
{
buffer_part = STRING_MODE;
}
if (cursor_mode)
{
if (argc-woptind)
{
wchar_t *endptr;
long new_pos;
errno = 0;
new_pos = wcstol(argv[woptind], &endptr, 10);
if (*endptr || errno)
{
append_format(stderr_buffer,
BUILTIN_ERR_NOT_NUMBER,
argv[0],
argv[woptind]);
builtin_print_help(parser, argv[0], stderr_buffer);
}
current_buffer = reader_get_buffer();
new_pos = maxi(0L, mini(new_pos, (long)wcslen(current_buffer)));
reader_set_buffer(current_buffer, (size_t)new_pos);
return 0;
}
else
{
append_format(stdout_buffer, L"%lu\n", (unsigned long)reader_get_cursor_pos());
return 0;
}
}
if (line_mode)
{
size_t pos = reader_get_cursor_pos();
const wchar_t *buff = reader_get_buffer();
append_format(stdout_buffer, L"%lu\n", (unsigned long)parse_util_lineno(buff, pos));
return 0;
}
if (search_mode)
{
return !reader_search_mode();
}
switch (buffer_part)
{
case STRING_MODE:
{
begin = get_buffer();
end = begin+wcslen(begin);
break;
}
case PROCESS_MODE:
{
parse_util_process_extent(get_buffer(),
get_cursor_pos(),
&begin,
&end);
break;
}
case JOB_MODE:
{
parse_util_job_extent(get_buffer(),
get_cursor_pos(),
&begin,
&end);
break;
}
case TOKEN_MODE:
{
parse_util_token_extent(get_buffer(),
get_cursor_pos(),
&begin,
&end,
0, 0);
break;
}
}
switch (argc-woptind)
{
case 0:
{
write_part(begin, end, cut_at_cursor, tokenize);
break;
}
case 1:
{
replace_part(begin, end, argv[woptind], append_mode);
break;
}
default:
{
wcstring sb = argv[woptind];
int i;
for (i=woptind+1; i<argc; i++)
{
sb.push_back(L'\n');
sb.append(argv[i]);
}
replace_part(begin, end, sb.c_str(), append_mode);
break;
}
}
return 0;
}

626
builtin_complete.cpp Normal file
View File

@@ -0,0 +1,626 @@
/** \file builtin_complete.c Functions defining the complete builtin
Functions used for implementing the complete builtin.
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <wctype.h>
#include <sys/types.h>
#include <termios.h>
#include <signal.h>
#include "fallback.h"
#include "util.h"
#include "wutil.h"
#include "builtin.h"
#include "common.h"
#include "complete.h"
#include "wgetopt.h"
#include "parser.h"
#include "reader.h"
/**
Internal storage for the builtin_complete_get_temporary_buffer() function.
*/
static const wchar_t *temporary_buffer;
/*
builtin_complete_* are a set of rather silly looping functions that
make sure that all the proper combinations of complete_add or
complete_remove get called. This is needed since complete allows you
to specify multiple switches on a single commandline, like 'complete
-s a -s b -s c', but the complete_add function only accepts one
short switch and one long switch.
*/
/**
Silly function
*/
static void builtin_complete_add2(const wchar_t *cmd,
int cmd_type,
const wchar_t *short_opt,
const wcstring_list_t &gnu_opt,
const wcstring_list_t &old_opt,
int result_mode,
const wchar_t *condition,
const wchar_t *comp,
const wchar_t *desc,
int flags)
{
size_t i;
const wchar_t *s;
for (s=short_opt; *s; s++)
{
complete_add(cmd,
cmd_type,
*s,
0,
0,
result_mode,
condition,
comp,
desc,
flags);
}
for (i=0; i<gnu_opt.size(); i++)
{
complete_add(cmd,
cmd_type,
0,
gnu_opt.at(i).c_str(),
0,
result_mode,
condition,
comp,
desc,
flags);
}
for (i=0; i<old_opt.size(); i++)
{
complete_add(cmd,
cmd_type,
0,
old_opt.at(i).c_str(),
1,
result_mode,
condition,
comp,
desc,
flags);
}
if (old_opt.empty() && gnu_opt.empty() && wcslen(short_opt) == 0)
{
complete_add(cmd,
cmd_type,
0,
0,
0,
result_mode,
condition,
comp,
desc,
flags);
}
}
/**
Silly function
*/
static void builtin_complete_add(const wcstring_list_t &cmd,
const wcstring_list_t &path,
const wchar_t *short_opt,
wcstring_list_t &gnu_opt,
wcstring_list_t &old_opt,
int result_mode,
int authoritative,
const wchar_t *condition,
const wchar_t *comp,
const wchar_t *desc,
int flags)
{
for (size_t i=0; i<cmd.size(); i++)
{
builtin_complete_add2(cmd.at(i).c_str(),
COMMAND,
short_opt,
gnu_opt,
old_opt,
result_mode,
condition,
comp,
desc,
flags);
if (authoritative != -1)
{
complete_set_authoritative(cmd.at(i).c_str(),
COMMAND,
authoritative);
}
}
for (size_t i=0; i<path.size(); i++)
{
builtin_complete_add2(path.at(i).c_str(),
PATH,
short_opt,
gnu_opt,
old_opt,
result_mode,
condition,
comp,
desc,
flags);
if (authoritative != -1)
{
complete_set_authoritative(path.at(i).c_str(),
PATH,
authoritative);
}
}
}
/**
Silly function
*/
static void builtin_complete_remove3(const wchar_t *cmd,
int cmd_type,
wchar_t short_opt,
const wcstring_list_t &long_opt)
{
for (size_t i=0; i<long_opt.size(); i++)
{
complete_remove(cmd,
cmd_type,
short_opt,
long_opt.at(i).c_str());
}
}
/**
Silly function
*/
static void builtin_complete_remove2(const wchar_t *cmd,
int cmd_type,
const wchar_t *short_opt,
const wcstring_list_t &gnu_opt,
const wcstring_list_t &old_opt)
{
const wchar_t *s = (wchar_t *)short_opt;
if (*s)
{
for (; *s; s++)
{
if (old_opt.empty() && gnu_opt.empty())
{
complete_remove(cmd,
cmd_type,
*s,
0);
}
else
{
builtin_complete_remove3(cmd,
cmd_type,
*s,
gnu_opt);
builtin_complete_remove3(cmd,
cmd_type,
*s,
old_opt);
}
}
}
else
{
builtin_complete_remove3(cmd,
cmd_type,
0,
gnu_opt);
builtin_complete_remove3(cmd,
cmd_type,
0,
old_opt);
}
}
/**
Silly function
*/
static void builtin_complete_remove(const wcstring_list_t &cmd,
const wcstring_list_t &path,
const wchar_t *short_opt,
const wcstring_list_t &gnu_opt,
const wcstring_list_t &old_opt)
{
for (size_t i=0; i<cmd.size(); i++)
{
builtin_complete_remove2(cmd.at(i).c_str(),
COMMAND,
short_opt,
gnu_opt,
old_opt);
}
for (size_t i=0; i<path.size(); i++)
{
builtin_complete_remove2(path.at(i).c_str(),
PATH,
short_opt,
gnu_opt,
old_opt);
}
}
const wchar_t *builtin_complete_get_temporary_buffer()
{
ASSERT_IS_MAIN_THREAD();
return temporary_buffer;
}
/**
The complete builtin. Used for specifying programmable
tab-completions. Calls the functions in complete.c for any heavy
lifting. Defined in builtin_complete.c
*/
static int builtin_complete(parser_t &parser, wchar_t **argv)
{
ASSERT_IS_MAIN_THREAD();
bool res=false;
int argc=0;
int result_mode=SHARED;
int remove = 0;
int authoritative = -1;
int flags = COMPLETE_AUTO_SPACE;
wcstring short_opt;
wcstring_list_t gnu_opt, old_opt;
const wchar_t *comp=L"", *desc=L"", *condition=L"";
bool do_complete = false;
wcstring do_complete_param;
wcstring_list_t cmd;
wcstring_list_t path;
static int recursion_level=0;
argc = builtin_count_args(argv);
woptind=0;
while (! res)
{
static const struct woption
long_options[] =
{
{
L"exclusive", no_argument, 0, 'x'
}
,
{
L"no-files", no_argument, 0, 'f'
}
,
{
L"require-parameter", no_argument, 0, 'r'
}
,
{
L"path", required_argument, 0, 'p'
}
,
{
L"command", required_argument, 0, 'c'
}
,
{
L"short-option", required_argument, 0, 's'
}
,
{
L"long-option", required_argument, 0, 'l'
}
,
{
L"old-option", required_argument, 0, 'o'
}
,
{
L"description", required_argument, 0, 'd'
}
,
{
L"arguments", required_argument, 0, 'a'
}
,
{
L"erase", no_argument, 0, 'e'
}
,
{
L"unauthoritative", no_argument, 0, 'u'
}
,
{
L"authoritative", no_argument, 0, 'A'
}
,
{
L"condition", required_argument, 0, 'n'
}
,
{
L"do-complete", optional_argument, 0, 'C'
}
,
{
L"help", no_argument, 0, 'h'
}
,
{
0, 0, 0, 0
}
}
;
int opt_index = 0;
int opt = wgetopt_long(argc,
argv,
L"a:c:p:s:l:o:d:frxeuAn:C::h",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
case 0:
if (long_options[opt_index].flag != 0)
break;
append_format(stderr_buffer,
BUILTIN_ERR_UNKNOWN,
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, argv[0], stderr_buffer);
res = true;
break;
case 'x':
result_mode |= EXCLUSIVE;
break;
case 'f':
result_mode |= NO_FILES;
break;
case 'r':
result_mode |= NO_COMMON;
break;
case 'p':
case 'c':
{
wcstring tmp = woptarg;
if (unescape_string(tmp, 1))
{
if (opt=='p')
path.push_back(tmp);
else
cmd.push_back(tmp);
}
else
{
append_format(stderr_buffer, L"%ls: Invalid token '%ls'\n", argv[0], woptarg);
res = true;
}
break;
}
case 'd':
desc = woptarg;
break;
case 'u':
authoritative=0;
break;
case 'A':
authoritative=1;
break;
case 's':
short_opt.append(woptarg);
break;
case 'l':
gnu_opt.push_back(woptarg);
break;
case 'o':
old_opt.push_back(woptarg);
break;
case 'a':
comp = woptarg;
break;
case 'e':
remove = 1;
break;
case 'n':
condition = woptarg;
break;
case 'C':
do_complete = true;
do_complete_param = woptarg ? woptarg : reader_get_buffer();
break;
case 'h':
builtin_print_help(parser, argv[0], stdout_buffer);
return 0;
case '?':
builtin_unknown_option(parser, argv[0], argv[woptind-1]);
res = true;
break;
}
}
if (!res)
{
if (condition && wcslen(condition))
{
if (parser.test(condition))
{
append_format(stderr_buffer,
L"%ls: Condition '%ls' contained a syntax error\n",
argv[0],
condition);
parser.test(condition, NULL, &stderr_buffer, argv[0]);
res = true;
}
}
}
if (!res)
{
if (comp && wcslen(comp))
{
if (parser.test_args(comp, 0, 0))
{
append_format(stderr_buffer,
L"%ls: Completion '%ls' contained a syntax error\n",
argv[0],
comp);
parser.test_args(comp, &stderr_buffer, argv[0]);
res = true;
}
}
}
if (!res)
{
if (do_complete)
{
const wchar_t *token;
parse_util_token_extent(do_complete_param.c_str(), do_complete_param.size(), &token, 0, 0, 0);
const wchar_t *prev_temporary_buffer = temporary_buffer;
temporary_buffer = do_complete_param.c_str();
if (recursion_level < 1)
{
recursion_level++;
std::vector<completion_t> comp;
complete(do_complete_param, comp, COMPLETION_REQUEST_DEFAULT);
for (size_t i=0; i< comp.size() ; i++)
{
const completion_t &next = comp.at(i);
const wchar_t *prepend;
if (next.flags & COMPLETE_REPLACES_TOKEN)
{
prepend = L"";
}
else
{
prepend = token;
}
if (!(next.description).empty())
{
append_format(stdout_buffer, L"%ls%ls\t%ls\n", prepend, next.completion.c_str(), next.description.c_str());
}
else
{
append_format(stdout_buffer, L"%ls%ls\n", prepend, next.completion.c_str());
}
}
recursion_level--;
}
temporary_buffer = prev_temporary_buffer;
}
else if (woptind != argc)
{
append_format(stderr_buffer,
_(L"%ls: Too many arguments\n"),
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
res = true;
}
else if (cmd.empty() && path.empty())
{
/* No arguments specified, meaning we print the definitions of
* all specified completions to stdout.*/
complete_print(stdout_buffer);
}
else
{
if (remove)
{
builtin_complete_remove(cmd,
path,
short_opt.c_str(),
gnu_opt,
old_opt);
}
else
{
builtin_complete_add(cmd,
path,
short_opt.c_str(),
gnu_opt,
old_opt,
result_mode,
authoritative,
condition,
comp,
desc,
flags);
}
}
}
return res ? 1 : 0;
}

351
builtin_jobs.cpp Normal file
View File

@@ -0,0 +1,351 @@
/** \file builtin_jobs.c
Functions for executing the jobs builtin.
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <wctype.h>
#include "fallback.h"
#include "util.h"
#include "wutil.h"
#include "builtin.h"
#include "proc.h"
#include "parser.h"
#include "common.h"
#include "wgetopt.h"
/**
Print modes for the jobs builtin
*/
enum
{
JOBS_DEFAULT, /**< Print lots of general info */
JOBS_PRINT_PID, /**< Print pid of each process in job */
JOBS_PRINT_COMMAND, /**< Print command name of each process in job */
JOBS_PRINT_GROUP, /**< Print group id of job */
}
;
#ifdef HAVE__PROC_SELF_STAT
/**
Calculates the cpu usage (in percent) of the specified job.
*/
static int cpu_use(const job_t *j)
{
double u=0;
process_t *p;
for (p=j->first_process; p; p=p->next)
{
struct timeval t;
int jiffies;
gettimeofday(&t, 0);
jiffies = proc_get_jiffies(p);
double t1 = 1000000.0*p->last_time.tv_sec+p->last_time.tv_usec;
double t2 = 1000000.0*t.tv_sec+t.tv_usec;
/* fwprintf( stderr, L"t1 %f t2 %f p1 %d p2 %d\n",
t1, t2, jiffies, p->last_jiffies );
*/
u += ((double)(jiffies-p->last_jiffies))/(t2-t1);
}
return u*1000000;
}
#endif
/**
Print information about the specified job
*/
static void builtin_jobs_print(const job_t *j, int mode, int header)
{
process_t *p;
switch (mode)
{
case JOBS_DEFAULT:
{
if (header)
{
/*
Print table header before first job
*/
stdout_buffer.append(_(L"Job\tGroup\t"));
#ifdef HAVE__PROC_SELF_STAT
stdout_buffer.append(_(L"CPU\t"));
#endif
stdout_buffer.append(_(L"State\tCommand\n"));
}
append_format(stdout_buffer, L"%d\t%d\t", j->job_id, j->pgid);
#ifdef HAVE__PROC_SELF_STAT
append_format(stdout_buffer, L"%d%%\t", cpu_use(j));
#endif
stdout_buffer.append(job_is_stopped(j)?_(L"stopped"):_(L"running"));
stdout_buffer.append(L"\t");
stdout_buffer.append(j->command_wcstr());
stdout_buffer.append(L"\n");
break;
}
case JOBS_PRINT_GROUP:
{
if (header)
{
/*
Print table header before first job
*/
stdout_buffer.append(_(L"Group\n"));
}
append_format(stdout_buffer, L"%d\n", j->pgid);
break;
}
case JOBS_PRINT_PID:
{
if (header)
{
/*
Print table header before first job
*/
stdout_buffer.append(_(L"Procces\n"));
}
for (p=j->first_process; p; p=p->next)
{
append_format(stdout_buffer, L"%d\n", p->pid);
}
break;
}
case JOBS_PRINT_COMMAND:
{
if (header)
{
/*
Print table header before first job
*/
stdout_buffer.append(_(L"Command\n"));
}
for (p=j->first_process; p; p=p->next)
{
append_format(stdout_buffer, L"%ls\n", p->argv0());
}
break;
}
}
}
/**
The jobs builtin. Used fopr printing running jobs. Defined in builtin_jobs.c.
*/
static int builtin_jobs(parser_t &parser, wchar_t **argv)
{
int argc=0;
int found=0;
int mode=JOBS_DEFAULT;
int print_last = 0;
const job_t *j;
argc = builtin_count_args(argv);
woptind=0;
while (1)
{
static const struct woption
long_options[] =
{
{
L"pid", no_argument, 0, 'p'
}
,
{
L"command", no_argument, 0, 'c'
}
,
{
L"group", no_argument, 0, 'g'
}
,
{
L"last", no_argument, 0, 'l'
}
,
{
L"help", no_argument, 0, 'h'
}
,
{
0, 0, 0, 0
}
}
;
int opt_index = 0;
int opt = wgetopt_long(argc,
argv,
L"pclgh",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
case 0:
if (long_options[opt_index].flag != 0)
break;
append_format(stderr_buffer,
BUILTIN_ERR_UNKNOWN,
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
case 'p':
mode=JOBS_PRINT_PID;
break;
case 'c':
mode=JOBS_PRINT_COMMAND;
break;
case 'g':
mode=JOBS_PRINT_GROUP;
break;
case 'l':
{
print_last = 1;
break;
}
case 'h':
builtin_print_help(parser, argv[0], stdout_buffer);
return 0;
case '?':
builtin_unknown_option(parser, argv[0], argv[woptind-1]);
return 1;
}
}
/*
Do not babble if not interactive
*/
if (builtin_out_redirect)
{
found=1;
}
if (print_last)
{
/*
Ignore unconstructed jobs, i.e. ourself.
*/
job_iterator_t jobs;
const job_t *j;
while ((j = jobs.next()))
{
if ((j->flags & JOB_CONSTRUCTED) && !job_is_completed(j))
{
builtin_jobs_print(j, mode, !found);
return 0;
}
}
}
else
{
if (woptind < argc)
{
int i;
found = 1;
for (i=woptind; i<argc; i++)
{
int pid;
wchar_t *end;
errno=0;
pid=fish_wcstoi(argv[i], &end, 10);
if (errno || *end)
{
append_format(stderr_buffer,
_(L"%ls: '%ls' is not a job\n"),
argv[0],
argv[i]);
return 1;
}
j = job_get_from_pid(pid);
if (j && !job_is_completed(j))
{
builtin_jobs_print(j, mode, !found);
}
else
{
append_format(stderr_buffer,
_(L"%ls: No suitable job: %d\n"),
argv[0],
pid);
return 1;
}
}
}
else
{
job_iterator_t jobs;
const job_t *j;
while ((j = jobs.next()))
{
/*
Ignore unconstructed jobs, i.e. ourself.
*/
if ((j->flags & JOB_CONSTRUCTED) && !job_is_completed(j))
{
builtin_jobs_print(j, mode, !found);
found = 1;
}
}
}
}
if (!found)
{
append_format(stdout_buffer,
_(L"%ls: There are no jobs\n"),
argv[0]);
}
return 0;
}

777
builtin_printf.cpp Normal file
View File

@@ -0,0 +1,777 @@
/* printf - format and print data
Copyright (C) 1990-2007 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
/* Usage: printf format [argument...]
A front end to the printf function that lets it be used from the shell.
Backslash escapes:
\" = double quote
\\ = backslash
\a = alert (bell)
\b = backspace
\c = produce no further output
\f = form feed
\n = new line
\r = carriage return
\t = horizontal tab
\v = vertical tab
\ooo = octal number (ooo is 1 to 3 digits)
\xhh = hexadecimal number (hhh is 1 to 2 digits)
\uhhhh = 16-bit Unicode character (hhhh is 4 digits)
\Uhhhhhhhh = 32-bit Unicode character (hhhhhhhh is 8 digits)
Additional directive:
%b = print an argument string, interpreting backslash escapes,
except that octal escapes are of the form \0 or \0ooo.
The `format' argument is re-used as many times as necessary
to convert all of the given arguments.
David MacKenzie <djm@gnu.ai.mit.edu> */
/* This file has been imported from source code of printf command in GNU Coreutils version 6.9 */
#include <stdio.h>
#include <sys/types.h>
#include <inttypes.h>
#include "common.h"
struct builtin_printf_state_t
{
/* The status of the operation */
int exit_code;
/* Whether we should stop outputting. This gets set in the case of an error, and also with the \c escape. */
bool early_exit;
builtin_printf_state_t() : exit_code(0), early_exit(false)
{
}
void verify_numeric(const wchar_t *s, const wchar_t *end, int errcode);
void print_direc(const wchar_t *start, size_t length, wchar_t conversion,
bool have_field_width, int field_width,
bool have_precision, int precision,
wchar_t const *argument);
int print_formatted(const wchar_t *format, int argc, wchar_t **argv);
void fatal_error(const wchar_t *format, ...);
long print_esc(const wchar_t *escstart, bool octal_0);
void print_esc_string(const wchar_t *str);
void print_esc_char(wchar_t c);
void append_output(wchar_t c);
void append_output(const wchar_t *c);
void append_format_output(const wchar_t *fmt, ...);
};
static bool is_octal_digit(wchar_t c)
{
return c != L'\0' && wcschr(L"01234567", c) != NULL;
}
static bool is_hex_digit(wchar_t c)
{
return c != L'\0' && wcschr(L"0123456789ABCDEFabcdef", c) != NULL;
}
static int hex_to_bin(const wchar_t &c)
{
switch (c)
{
case L'0':
return 0;
case L'1':
return 1;
case L'2':
return 2;
case L'3':
return 3;
case L'4':
return 4;
case L'5':
return 5;
case L'6':
return 6;
case L'7':
return 7;
case L'8':
return 8;
case L'9':
return 9;
case L'a':
case L'A':
return 10;
case L'b':
case L'B':
return 11;
case L'c':
case L'C':
return 12;
case L'd':
case L'D':
return 13;
case L'e':
case L'E':
return 14;
case L'f':
case L'F':
return 15;
default:
return -1;
}
}
static int octal_to_bin(wchar_t c)
{
switch (c)
{
case L'0':
return 0;
case L'1':
return 1;
case L'2':
return 2;
case L'3':
return 3;
case L'4':
return 4;
case L'5':
return 5;
case L'6':
return 6;
case L'7':
return 7;
default:
return -1;
}
}
/* This message appears in N_() here rather than just in _() below because
the sole use would have been in a #define. */
static wchar_t const *const cfcc_msg =
N_(L"warning: %ls: character(s) following character constant have been ignored");
double C_STRTOD(wchar_t const *nptr, wchar_t **endptr)
{
double r;
const wcstring saved_locale = wsetlocale(LC_NUMERIC, NULL);
if (!saved_locale.empty())
{
wsetlocale(LC_NUMERIC, L"C");
}
r = wcstod(nptr, endptr);
if (!saved_locale.empty())
{
wsetlocale(LC_NUMERIC, saved_locale.c_str());
}
return r;
}
static inline unsigned wchar_t to_uwchar_t(wchar_t ch)
{
return ch;
}
void builtin_printf_state_t::fatal_error(const wchar_t *fmt, ...)
{
// Don't error twice
if (early_exit)
return;
va_list va;
va_start(va, fmt);
wcstring errstr = vformat_string(fmt, va);
va_end(va);
stderr_buffer.append(errstr);
if (! string_suffixes_string(L"\n", errstr))
stderr_buffer.push_back(L'\n');
this->exit_code = STATUS_BUILTIN_ERROR;
this->early_exit = true;
}
void builtin_printf_state_t::append_output(wchar_t c)
{
// Don't output if we're done
if (early_exit)
return;
stdout_buffer.push_back(c);
}
void builtin_printf_state_t::append_output(const wchar_t *c)
{
// Don't output if we're done
if (early_exit)
return;
stdout_buffer.append(c);
}
void builtin_printf_state_t::append_format_output(const wchar_t *fmt, ...)
{
// Don't output if we're done
if (early_exit)
return;
va_list va;
va_start(va, fmt);
append_formatv(stdout_buffer, fmt, va);
va_end(va);
}
void builtin_printf_state_t::verify_numeric(const wchar_t *s, const wchar_t *end, int errcode)
{
if (errcode != 0)
{
this->fatal_error(L"%ls: %s", s, strerror(errcode));
}
else if (*end)
{
if (s == end)
this->fatal_error(_(L"%ls: expected a numeric value"), s);
else
this->fatal_error(_(L"%ls: value not completely converted"), s);
}
}
template<typename T>
static T raw_string_to_scalar_type(const wchar_t *s, wchar_t ** end);
// we use wcstoll instead of wcstoimax because FreeBSD 8 has busted wcstoumax and wcstoimax - see #626
template<>
intmax_t raw_string_to_scalar_type(const wchar_t *s, wchar_t ** end)
{
return wcstoll(s, end, 0);
}
template<>
uintmax_t raw_string_to_scalar_type(const wchar_t *s, wchar_t ** end)
{
return wcstoull(s, end, 0);
}
template<>
long double raw_string_to_scalar_type(const wchar_t *s, wchar_t ** end)
{
return C_STRTOD(s, end);
}
template<typename T>
static T string_to_scalar_type(const wchar_t *s, builtin_printf_state_t *state)
{
T val;
if (*s == L'\"' || *s == L'\'')
{
unsigned wchar_t ch = *++s;
val = ch;
}
else
{
wchar_t *end = NULL;
errno = 0;
val = raw_string_to_scalar_type<T>(s, &end);
state->verify_numeric(s, end, errno);
}
return val;
}
/* Output a single-character \ escape. */
void builtin_printf_state_t::print_esc_char(wchar_t c)
{
switch (c)
{
case L'a': /* Alert. */
this->append_output(L'\a');
break;
case L'b': /* Backspace. */
this->append_output(L'\b');
break;
case L'c': /* Cancel the rest of the output. */
this->early_exit = true;
break;
case L'f': /* Form feed. */
this->append_output(L'\f');
break;
case L'n': /* New line. */
this->append_output(L'\n');
break;
case L'r': /* Carriage return. */
this->append_output(L'\r');
break;
case L't': /* Horizontal tab. */
this->append_output(L'\t');
break;
case L'v': /* Vertical tab. */
this->append_output(L'\v');
break;
default:
this->append_output(c);
break;
}
}
/* Print a \ escape sequence starting at ESCSTART.
Return the number of characters in the escape sequence
besides the backslash.
If OCTAL_0 is nonzero, octal escapes are of the form \0ooo, where o
is an octal digit; otherwise they are of the form \ooo. */
long builtin_printf_state_t::print_esc(const wchar_t *escstart, bool octal_0)
{
const wchar_t *p = escstart + 1;
int esc_value = 0; /* Value of \nnn escape. */
int esc_length; /* Length of \nnn escape. */
if (*p == L'x')
{
/* A hexadecimal \xhh escape sequence must have 1 or 2 hex. digits. */
for (esc_length = 0, ++p; esc_length < 2 && is_hex_digit(*p); ++esc_length, ++p)
esc_value = esc_value * 16 + hex_to_bin(*p);
if (esc_length == 0)
this->fatal_error(_(L"missing hexadecimal number in escape"));
this->append_format_output(L"%lc", esc_value);
}
else if (is_octal_digit(*p))
{
/* Parse \0ooo (if octal_0 && *p == L'0') or \ooo (otherwise).
Allow \ooo if octal_0 && *p != L'0'; this is an undocumented
extension to POSIX that is compatible with Bash 2.05b. */
for (esc_length = 0, p += octal_0 && *p == L'0'; esc_length < 3 && is_octal_digit(*p); ++esc_length, ++p)
esc_value = esc_value * 8 + octal_to_bin(*p);
this->append_format_output(L"%c", esc_value);
}
else if (*p && wcschr(L"\"\\abcfnrtv", *p))
print_esc_char(*p++);
else if (*p == L'u' || *p == L'U')
{
wchar_t esc_char = *p;
p++;
uint32_t uni_value = 0;
for (size_t esc_length = 0; esc_length < (esc_char == L'u' ? 4 : 8); esc_length++)
{
if (! is_hex_digit(*p))
{
/* Escape sequence must be done. Complain if we didn't get anything */
if (esc_length == 0)
{
this->fatal_error(_(L"Missing hexadecimal number in Unicode escape"));
}
break;
}
uni_value = uni_value * 16 + hex_to_bin(*p);
p++;
}
/* PCA GNU printf respects the limitations described in ISO N717, about which universal characters "shall not" be specified. I believe this limitation is for the benefit of compilers; I see no reason to impose it in builtin_printf.
If __STDC_ISO_10646__ is defined, then it means wchar_t can and does hold Unicode code points, so just use that. If not defined, use the %lc printf conversion; this probably won't do anything good if your wide character set is not Unicode, but such platforms are exceedingly rare.
*/
if (uni_value > 0x10FFFF)
{
this->fatal_error(_(L"Unicode character out of range: \\%c%0*x"), esc_char, (esc_char == L'u' ? 4 : 8), uni_value);
}
else
{
#if defined(__STDC_ISO_10646__)
this->append_output(uni_value);
#else
this->append_format_output(L"%lc", uni_value);
#endif
}
}
else
{
this->append_output(L'\\');
if (*p)
{
this->append_output(*p);
p++;
}
}
return p - escstart - 1;
}
/* Print string STR, evaluating \ escapes. */
void builtin_printf_state_t::print_esc_string(const wchar_t *str)
{
for (; *str; str++)
if (*str == L'\\')
str += print_esc(str, true);
else
this->append_output(*str);
}
/* Evaluate a printf conversion specification. START is the start of
the directive, LENGTH is its length, and CONVERSION specifies the
type of conversion. LENGTH does not include any length modifier or
the conversion specifier itself. FIELD_WIDTH and PRECISION are the
field width and precision for '*' values, if HAVE_FIELD_WIDTH and
HAVE_PRECISION are true, respectively. ARGUMENT is the argument to
be formatted. */
void builtin_printf_state_t::print_direc(const wchar_t *start, size_t length, wchar_t conversion,
bool have_field_width, int field_width,
bool have_precision, int precision,
wchar_t const *argument)
{
// Start with everything except the conversion specifier
wcstring fmt(start, length);
/* Create a copy of the % directive, with an intmax_t-wide width modifier substituted for any existing integer length modifier. */
switch (conversion)
{
case L'd':
case L'i':
case L'u':
fmt.append(L"ll");
break;
case L'a':
case L'e':
case L'f':
case L'g':
case L'A':
case L'E':
case L'F':
case L'G':
fmt.append(L"L");
break;
case L's':
fmt.append(L"l");
break;
default:
break;
}
// Append the conversion itself
fmt.push_back(conversion);
switch (conversion)
{
case L'd':
case L'i':
{
intmax_t arg = string_to_scalar_type<intmax_t>(argument, this);
if (! have_field_width)
{
if (! have_precision)
this->append_format_output(fmt.c_str(), arg);
else
this->append_format_output(fmt.c_str(), precision, arg);
}
else
{
if (! have_precision)
this->append_format_output(fmt.c_str(), field_width, arg);
else
this->append_format_output(fmt.c_str(), field_width, precision, arg);
}
}
break;
case L'o':
case L'u':
case L'x':
case L'X':
{
uintmax_t arg = string_to_scalar_type<uintmax_t>(argument, this);
if (!have_field_width)
{
if (!have_precision)
this->append_format_output(fmt.c_str(), arg);
else
this->append_format_output(fmt.c_str(), precision, arg);
}
else
{
if (!have_precision)
this->append_format_output(fmt.c_str(), field_width, arg);
else
this->append_format_output(fmt.c_str(), field_width, precision, arg);
}
}
break;
case L'a':
case L'A':
case L'e':
case L'E':
case L'f':
case L'F':
case L'g':
case L'G':
{
long double arg = string_to_scalar_type<long double>(argument, this);
if (!have_field_width)
{
if (!have_precision)
this->append_format_output(fmt.c_str(), arg);
else
this->append_format_output(fmt.c_str(), precision, arg);
}
else
{
if (!have_precision)
this->append_format_output(fmt.c_str(), field_width, arg);
else
this->append_format_output(fmt.c_str(), field_width, precision, arg);
}
}
break;
case L'c':
if (!have_field_width)
this->append_format_output(fmt.c_str(), *argument);
else
this->append_format_output(fmt.c_str(), field_width, *argument);
break;
case L's':
if (!have_field_width)
{
if (!have_precision)
{
this->append_format_output(fmt.c_str(), argument);
}
else
this->append_format_output(fmt.c_str(), precision, argument);
}
else
{
if (!have_precision)
this->append_format_output(fmt.c_str(), field_width, argument);
else
this->append_format_output(fmt.c_str(), field_width, precision, argument);
}
break;
}
}
/* Print the text in FORMAT, using ARGV (with ARGC elements) for
arguments to any `%' directives.
Return the number of elements of ARGV used. */
int builtin_printf_state_t::print_formatted(const wchar_t *format, int argc, wchar_t **argv)
{
int save_argc = argc; /* Preserve original value. */
const wchar_t *f; /* Pointer into `format'. */
const wchar_t *direc_start; /* Start of % directive. */
size_t direc_length; /* Length of % directive. */
bool have_field_width; /* True if FIELD_WIDTH is valid. */
int field_width = 0; /* Arg to first '*'. */
bool have_precision; /* True if PRECISION is valid. */
int precision = 0; /* Arg to second '*'. */
bool ok[UCHAR_MAX + 1] = { }; /* ok['x'] is true if %x is allowed. */
for (f = format; *f != L'\0'; ++f)
{
switch (*f)
{
case L'%':
direc_start = f++;
direc_length = 1;
have_field_width = have_precision = false;
if (*f == L'%')
{
this->append_output(L'%');
break;
}
if (*f == L'b')
{
/* FIXME: Field width and precision are not supported
for %b, even though POSIX requires it. */
if (argc > 0)
{
print_esc_string(*argv);
++argv;
--argc;
}
break;
}
ok['a'] = ok['A'] = ok['c'] = ok['d'] = ok['e'] = ok['E'] =
ok['f'] = ok['F'] = ok['g'] = ok['G'] = ok['i'] = ok['o'] =
ok['s'] = ok['u'] = ok['x'] = ok['X'] = true;
for (;; f++, direc_length++)
{
switch (*f)
{
case L'I':
case L'\'':
ok['a'] = ok['A'] = ok['c'] = ok['e'] = ok['E'] =
ok['o'] = ok['s'] = ok['x'] = ok['X'] = false;
break;
case '-':
case '+':
case ' ':
break;
case L'#':
ok['c'] = ok['d'] = ok['i'] = ok['s'] = ok['u'] = false;
break;
case '0':
ok['c'] = ok['s'] = false;
break;
default:
goto no_more_flag_characters;
}
}
no_more_flag_characters:
;
if (*f == L'*')
{
++f;
++direc_length;
if (argc > 0)
{
intmax_t width = string_to_scalar_type<intmax_t>(*argv, this);
if (INT_MIN <= width && width <= INT_MAX)
field_width = static_cast<int>(width);
else
this->fatal_error(_(L"invalid field width: %ls"), *argv);
++argv;
--argc;
}
else
{
field_width = 0;
}
have_field_width = true;
}
else
{
while (iswdigit(*f))
{
++f;
++direc_length;
}
}
if (*f == L'.')
{
++f;
++direc_length;
ok['c'] = false;
if (*f == L'*')
{
++f;
++direc_length;
if (argc > 0)
{
intmax_t prec = string_to_scalar_type<intmax_t>(*argv, this);
if (prec < 0)
{
/* A negative precision is taken as if the
precision were omitted, so -1 is safe
here even if prec < INT_MIN. */
precision = -1;
}
else if (INT_MAX < prec)
this->fatal_error(_(L"invalid precision: %ls"), *argv);
else
{
precision = static_cast<int>(prec);
}
++argv;
--argc;
}
else
{
precision = 0;
}
have_precision = true;
}
else
{
while (iswdigit(*f))
{
++f;
++direc_length;
}
}
}
while (*f == L'l' || *f == L'L' || *f == L'h' || *f == L'j' || *f == L't' || *f == L'z')
++f;
{
unsigned wchar_t conversion = *f;
if (! ok[conversion])
{
this->fatal_error(_(L"%.*ls: invalid conversion specification"), (int)(f + 1 - direc_start), direc_start);
return 0;
}
}
print_direc(direc_start, direc_length, *f,
have_field_width, field_width,
have_precision, precision,
(argc <= 0 ? L"" : (argc--, *argv++)));
break;
case L'\\':
f += print_esc(f, false);
break;
default:
this->append_output(*f);
}
}
return save_argc - argc;
}
static int builtin_printf(parser_t &parser, wchar_t **argv)
{
builtin_printf_state_t state;
wchar_t *format;
int args_used;
int argc = builtin_count_args(argv);
if (argc <= 1)
{
state.fatal_error(_(L"printf: not enough arguments"));
return STATUS_BUILTIN_ERROR;
}
format = argv[1];
argc -= 2;
argv += 2;
do
{
args_used = state.print_formatted(format, argc, argv);
argc -= args_used;
argv += args_used;
}
while (args_used > 0 && argc > 0 && ! state.early_exit);
return state.exit_code;
}

811
builtin_set.cpp Normal file
View File

@@ -0,0 +1,811 @@
/** \file builtin_set.c Functions defining the set builtin
Functions used for implementing the set builtin.
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <wctype.h>
#include <sys/types.h>
#include <termios.h>
#include <signal.h>
#include <vector>
#include <algorithm>
#include "fallback.h"
#include "util.h"
#include "wutil.h"
#include "builtin.h"
#include "env.h"
#include "expand.h"
#include "common.h"
#include "wgetopt.h"
#include "proc.h"
#include "parser.h"
/* We know about these buffers */
extern wcstring stdout_buffer, stderr_buffer;
/**
Error message for invalid path operations
*/
#define BUILTIN_SET_PATH_ERROR L"%ls: Warning: path component %ls may not be valid in %ls.\n"
/**
Hint for invalid path operation with a colon
*/
#define BUILTIN_SET_PATH_HINT L"%ls: Did you mean 'set %ls $%ls %ls'?\n"
/**
Error for mismatch between index count and elements
*/
#define BUILTIN_SET_ARG_COUNT L"%ls: The number of variable indexes does not match the number of values\n"
/**
Test if the specified variable should be subject to path validation
*/
static int is_path_variable(const wchar_t *env)
{
return contains(env, L"PATH", L"CDPATH");
}
/**
Call env_set. If this is a path variable, e.g. PATH, validate the
elements. On error, print a description of the problem to stderr.
*/
static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope)
{
size_t i;
int retcode = 0;
const wchar_t *val_str=NULL;
if (is_path_variable(key))
{
/* Fix for https://github.com/fish-shell/fish-shell/issues/199 . Return success if any path setting succeeds. */
bool any_success = false;
/* Don't bother validating (or complaining about) values that are already present */
wcstring_list_t existing_values;
const env_var_t existing_variable = env_get_string(key);
if (! existing_variable.missing_or_empty())
tokenize_variable_array(existing_variable, existing_values);
for (i=0; i< val.size() ; i++)
{
const wcstring &dir = val.at(i);
if (list_contains_string(existing_values, dir))
{
any_success = true;
continue;
}
bool show_perror = false;
int show_hint = 0;
bool error = false;
struct stat buff;
if (wstat(dir, &buff))
{
error = true;
show_perror = true;
}
if (!(S_ISDIR(buff.st_mode)))
{
error = true;
}
if (!error)
{
any_success = true;
}
else
{
append_format(stderr_buffer, _(BUILTIN_SET_PATH_ERROR), L"set", dir.c_str(), key);
const wchar_t *colon = wcschr(dir.c_str(), L':');
if (colon && *(colon+1))
{
show_hint = 1;
}
}
if (show_perror)
{
builtin_wperror(L"set");
}
if (show_hint)
{
append_format(stderr_buffer, _(BUILTIN_SET_PATH_HINT), L"set", key, key, wcschr(dir.c_str(), L':')+1);
}
}
/* Fail at setting the path if we tried to set it to something non-empty, but it wound up empty. */
if (! val.empty() && ! any_success)
{
return 1;
}
}
wcstring sb;
if (! val.empty())
{
for (i=0; i< val.size() ; i++)
{
sb.append(val[i]);
if (i<val.size() - 1)
{
sb.append(ARRAY_SEP_STR);
}
}
val_str = sb.c_str();
}
switch (env_set(key, val_str, scope | ENV_USER))
{
case ENV_PERM:
{
append_format(stderr_buffer, _(L"%ls: Tried to change the read-only variable '%ls'\n"), L"set", key);
retcode=1;
break;
}
case ENV_INVALID:
{
append_format(stderr_buffer, _(L"%ls: Unknown error"), L"set");
retcode=1;
break;
}
}
return retcode;
}
/**
Extract indexes from a destination argument of the form name[index1 index2...]
\param indexes the list to insert the new indexes into
\param src the source string to parse
\param name the name of the element. Return null if the name in \c src does not match this name
\param var_count the number of elements in the array to parse.
\return the total number of indexes parsed, or -1 on error
*/
static int parse_index(std::vector<long> &indexes,
const wchar_t *src,
const wchar_t *name,
size_t var_count)
{
size_t len;
int count = 0;
const wchar_t *src_orig = src;
if (src == 0)
{
return 0;
}
while (*src != L'\0' && (iswalnum(*src) || *src == L'_'))
{
src++;
}
if (*src != L'[')
{
append_format(stderr_buffer, _(BUILTIN_SET_ARG_COUNT), L"set");
return 0;
}
len = src-src_orig;
if ((wcsncmp(src_orig, name, len)!=0) || (wcslen(name) != (len)))
{
append_format(stderr_buffer,
_(L"%ls: Multiple variable names specified in single call (%ls and %.*ls)\n"),
L"set",
name,
len,
src_orig);
return 0;
}
src++;
while (iswspace(*src))
{
src++;
}
while (*src != L']')
{
wchar_t *end;
long l_ind;
errno = 0;
l_ind = wcstol(src, &end, 10);
if (end==src || errno)
{
append_format(stderr_buffer, _(L"%ls: Invalid index starting at '%ls'\n"), L"set", src);
return 0;
}
if (l_ind < 0)
{
l_ind = var_count+l_ind+1;
}
src = end;
if (*src==L'.' && *(src+1)==L'.')
{
src+=2;
long l_ind2 = wcstol(src, &end, 10);
if (end==src || errno)
{
return 1;
}
src = end;
if (l_ind2 < 0)
{
l_ind2 = var_count+l_ind2+1;
}
int direction = l_ind2<l_ind ? -1 : 1 ;
for (long jjj = l_ind; jjj*direction <= l_ind2*direction; jjj+=direction)
{
// debug(0, L"Expand range [set]: %i\n", jjj);
indexes.push_back(jjj);
count++;
}
}
else
{
indexes.push_back(l_ind);
count++;
}
while (iswspace(*src)) src++;
}
return count;
}
static int update_values(wcstring_list_t &list,
std::vector<long> &indexes,
wcstring_list_t &values)
{
size_t i;
/* Replace values where needed */
for (i = 0; i < indexes.size(); i++)
{
/*
The '- 1' below is because the indices in fish are
one-based, but the vector uses zero-based indices
*/
long ind = indexes[i] - 1;
const wcstring newv = values[ i ];
if (ind < 0)
{
return 1;
}
if ((size_t)ind >= list.size())
{
list.resize(ind+1);
}
// free((void *) al_get(list, ind));
list[ ind ] = newv;
}
return 0;
}
/**
Erase from a list of wcstring values at specified indexes
*/
static void erase_values(wcstring_list_t &list, const std::vector<long> &indexes)
{
// Make a set of indexes.
// This both sorts them into ascending order and removes duplicates.
const std::set<long> indexes_set(indexes.begin(), indexes.end());
// Now walk the set backwards, so we encounter larger indexes first, and remove elements at the given (1-based) indexes.
std::set<long>::const_reverse_iterator iter;
for (iter = indexes_set.rbegin(); iter != indexes_set.rend(); ++iter)
{
long val = *iter;
if (val > 0 && (size_t)val <= list.size())
{
// One-based indexing!
list.erase(list.begin() + val - 1);
}
}
}
/**
Print the names of all environment variables in the scope, with or without shortening,
with or without values, with or without escaping
*/
static void print_variables(int include_values, int esc, bool shorten_ok, int scope)
{
wcstring_list_t names = env_get_names(scope);
sort(names.begin(), names.end());
for (size_t i = 0; i < names.size(); i++)
{
const wcstring key = names.at(i);
const wcstring e_key = escape_string(key, 0);
stdout_buffer.append(e_key);
if (include_values)
{
env_var_t value = env_get_string(key);
if (!value.missing())
{
int shorten = 0;
if (shorten_ok && value.length() > 64)
{
shorten = 1;
value.resize(60);
}
wcstring e_value = esc ? expand_escape_variable(value) : value;
stdout_buffer.append(L" ");
stdout_buffer.append(e_value);
if (shorten)
{
stdout_buffer.push_back(ellipsis_char);
}
}
}
stdout_buffer.append(L"\n");
}
}
/**
The set builtin. Creates, updates and erases environment variables
and environemnt variable arrays.
*/
static int builtin_set(parser_t &parser, wchar_t **argv)
{
/** Variables used for parsing the argument list */
const struct woption long_options[] =
{
{ L"export", no_argument, 0, 'x' },
{ L"global", no_argument, 0, 'g' },
{ L"local", no_argument, 0, 'l' },
{ L"erase", no_argument, 0, 'e' },
{ L"names", no_argument, 0, 'n' },
{ L"unexport", no_argument, 0, 'u' },
{ L"universal", no_argument, 0, 'U' },
{ L"long", no_argument, 0, 'L' },
{ L"query", no_argument, 0, 'q' },
{ L"help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
} ;
const wchar_t *short_options = L"+xglenuULqh";
int argc = builtin_count_args(argv);
/*
Flags to set the work mode
*/
int local = 0, global = 0, exportv = 0;
int erase = 0, list = 0, unexport=0;
int universal = 0, query=0;
bool shorten_ok = true;
bool preserve_incoming_failure_exit_status = true;
const int incoming_exit_status = proc_get_last_status();
/*
Variables used for performing the actual work
*/
wchar_t *dest = 0;
int retcode=0;
int scope;
int slice=0;
int i;
wchar_t *bad_char;
/* Parse options to obtain the requested operation and the modifiers */
woptind = 0;
while (1)
{
int c = wgetopt_long(argc, argv, short_options, long_options, 0);
if (c == -1)
{
break;
}
switch (c)
{
case 0:
break;
case 'e':
erase = 1;
preserve_incoming_failure_exit_status = false;
break;
case 'n':
list = 1;
preserve_incoming_failure_exit_status = false;
break;
case 'x':
exportv = 1;
break;
case 'l':
local = 1;
break;
case 'g':
global = 1;
break;
case 'u':
unexport = 1;
break;
case 'U':
universal = 1;
break;
case 'L':
shorten_ok = false;
break;
case 'q':
query = 1;
preserve_incoming_failure_exit_status = false;
break;
case 'h':
builtin_print_help(parser, argv[0], stdout_buffer);
return 0;
case '?':
builtin_unknown_option(parser, argv[0], argv[woptind-1]);
return 1;
default:
break;
}
}
/*
Ok, all arguments have been parsed, let's validate them
*/
/*
If we are checking the existance of a variable (-q) we can not
also specify scope
*/
if (query && (erase || list))
{
append_format(stderr_buffer,
BUILTIN_ERR_COMBO,
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
/* We can't both list and erase varaibles */
if (erase && list)
{
append_format(stderr_buffer,
BUILTIN_ERR_COMBO,
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
/*
Variables can only have one scope
*/
if (local + global + universal > 1)
{
append_format(stderr_buffer,
BUILTIN_ERR_GLOCAL,
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
/*
Variables can only have one export status
*/
if (exportv && unexport)
{
append_format(stderr_buffer,
BUILTIN_ERR_EXPUNEXP,
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
/*
Calculate the scope value for variable assignement
*/
scope = (local ? ENV_LOCAL : 0) | (global ? ENV_GLOBAL : 0) | (exportv ? ENV_EXPORT : 0) | (unexport ? ENV_UNEXPORT : 0) | (universal ? ENV_UNIVERSAL:0) | ENV_USER;
if (query)
{
/*
Query mode. Return the number of variables that do not exist
out of the specified variables.
*/
int i;
for (i=woptind; i<argc; i++)
{
wchar_t *arg = argv[i];
int slice=0;
if (!(dest = wcsdup(arg)))
{
DIE_MEM();
}
if (wcschr(dest, L'['))
{
slice = 1;
*wcschr(dest, L'[')=0;
}
if (slice)
{
std::vector<long> indexes;
wcstring_list_t result;
size_t j;
env_var_t dest_str = env_get_string(dest);
if (! dest_str.missing())
tokenize_variable_array(dest_str, result);
if (!parse_index(indexes, arg, dest, result.size()))
{
builtin_print_help(parser, argv[0], stderr_buffer);
retcode = 1;
break;
}
for (j=0; j < indexes.size() ; j++)
{
long idx = indexes[j];
if (idx < 1 || (size_t)idx > result.size())
{
retcode++;
}
}
}
else
{
if (!env_exist(arg, scope))
{
retcode++;
}
}
free(dest);
}
return retcode;
}
if (list)
{
/* Maybe we should issue an error if there are any other arguments? */
print_variables(0, 0, shorten_ok, scope);
return 0;
}
if (woptind == argc)
{
/*
Print values of variables
*/
if (erase)
{
append_format(stderr_buffer,
_(L"%ls: Erase needs a variable name\n"),
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
retcode = 1;
}
else
{
print_variables(1, 1, shorten_ok, scope);
}
return retcode;
}
if (!(dest = wcsdup(argv[woptind])))
{
DIE_MEM();
}
if (wcschr(dest, L'['))
{
slice = 1;
*wcschr(dest, L'[')=0;
}
if (!wcslen(dest))
{
free(dest);
append_format(stderr_buffer, BUILTIN_ERR_VARNAME_ZERO, argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
if ((bad_char = wcsvarname(dest)))
{
append_format(stderr_buffer, BUILTIN_ERR_VARCHAR, argv[0], *bad_char);
builtin_print_help(parser, argv[0], stderr_buffer);
free(dest);
return 1;
}
if (slice && erase && (scope != ENV_USER))
{
free(dest);
append_format(stderr_buffer, _(L"%ls: Can not specify scope when erasing array slice\n"), argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
/*
set assignment can work in two modes, either using slices or
using the whole array. We detect which mode is used here.
*/
if (slice)
{
/*
Slice mode
*/
size_t idx_count, val_count;
wcstring_list_t values;
std::vector<long> indexes;
wcstring_list_t result;
const env_var_t dest_str = env_get_string(dest);
if (! dest_str.missing())
tokenize_variable_array(dest_str, result);
for (; woptind<argc; woptind++)
{
if (!parse_index(indexes, argv[woptind], dest, result.size()))
{
builtin_print_help(parser, argv[0], stderr_buffer);
retcode = 1;
break;
}
val_count = argc-woptind-1;
idx_count = indexes.size();
if (!erase)
{
if (val_count < idx_count)
{
append_format(stderr_buffer, _(BUILTIN_SET_ARG_COUNT), argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
retcode=1;
break;
}
if (val_count == idx_count)
{
woptind++;
break;
}
}
}
if (!retcode)
{
/*
Slice indexes have been calculated, do the actual work
*/
if (erase)
{
erase_values(result, indexes);
my_env_set(dest, result, scope);
}
else
{
wcstring_list_t value;
while (woptind < argc)
{
value.push_back(argv[woptind++]);
}
if (update_values(result,
indexes,
value))
{
append_format(stderr_buffer, L"%ls: ", argv[0]);
append_format(stderr_buffer, ARRAY_BOUNDS_ERR);
stderr_buffer.push_back(L'\n');
}
my_env_set(dest, result, scope);
}
}
}
else
{
woptind++;
/*
No slicing
*/
if (erase)
{
if (woptind != argc)
{
append_format(stderr_buffer,
_(L"%ls: Values cannot be specfied with erase\n"),
argv[0]);
builtin_print_help(parser, argv[0], stderr_buffer);
retcode=1;
}
else
{
retcode = env_remove(dest, scope);
}
}
else
{
wcstring_list_t val;
for (i=woptind; i<argc; i++)
val.push_back(argv[i]);
retcode = my_env_set(dest, val, scope);
}
}
free(dest);
if (retcode == STATUS_BUILTIN_OK && preserve_incoming_failure_exit_status)
retcode = incoming_exit_status;
return retcode;
}

243
builtin_set_color.cpp Normal file
View File

@@ -0,0 +1,243 @@
/** \file builtin_set_color.cpp Functions defining the set_color builtin
Functions used for implementing the set_color builtin.
*/
#include "config.h"
#include "builtin.h"
#include "color.h"
#include "output.h"
#if HAVE_NCURSES_H
#include <ncurses.h>
#else
#include <curses.h>
#endif
#if HAVE_TERM_H
#include <term.h>
#elif HAVE_NCURSES_TERM_H
#include <ncurses/term.h>
#endif
/* We know about these buffers */
extern wcstring stdout_buffer, stderr_buffer;
/**
Error message for invalid path operations
*/
#define BUILTIN_SET_PATH_ERROR L"%ls: Warning: path component %ls may not be valid in %ls.\n"
/**
Hint for invalid path operation with a colon
*/
#define BUILTIN_SET_PATH_HINT L"%ls: Did you mean 'set %ls $%ls %ls'?\n"
/**
Error for mismatch between index count and elements
*/
#define BUILTIN_SET_ARG_COUNT L"%ls: The number of variable indexes does not match the number of values\n"
static void print_colors(void)
{
const wcstring_list_t result = rgb_color_t::named_color_names();
size_t i;
for (i=0; i < result.size(); i++)
{
stdout_buffer.append(result.at(i));
stdout_buffer.push_back(L'\n');
}
}
/* function we set as the output writer */
static std::string builtin_set_color_output;
static int set_color_builtin_outputter(char c)
{
ASSERT_IS_MAIN_THREAD();
builtin_set_color_output.push_back(c);
return 0;
}
/**
set_color builtin
*/
static int builtin_set_color(parser_t &parser, wchar_t **argv)
{
/** Variables used for parsing the argument list */
const struct woption long_options[] =
{
{ L"background", required_argument, 0, 'b'},
{ L"help", no_argument, 0, 'h' },
{ L"bold", no_argument, 0, 'o' },
{ L"underline", no_argument, 0, 'u' },
{ L"version", no_argument, 0, 'v' },
{ L"print-colors", no_argument, 0, 'c' },
{ 0, 0, 0, 0 }
};
const wchar_t *short_options = L"b:hvocu";
int argc = builtin_count_args(argv);
const wchar_t *bgcolor = NULL;
bool bold = false, underline=false;
int errret;
/* Parse options to obtain the requested operation and the modifiers */
woptind = 0;
while (1)
{
int c = wgetopt_long(argc, argv, short_options, long_options, 0);
if (c == -1)
{
break;
}
switch (c)
{
case 0:
break;
case 'b':
bgcolor = woptarg;
break;
case 'h':
builtin_print_help(parser, argv[0], stdout_buffer);
return STATUS_BUILTIN_OK;
case 'o':
bold = true;
break;
case 'u':
underline = true;
break;
case 'c':
print_colors();
return STATUS_BUILTIN_OK;
case '?':
return STATUS_BUILTIN_ERROR;
}
}
/* Remaining argument is foreground color */
const wchar_t *fgcolor = NULL;
if (woptind < argc)
{
if (woptind + 1 == argc)
{
fgcolor = argv[woptind];
}
else
{
append_format(stderr_buffer,
_(L"%ls: Too many arguments\n"),
argv[0]);
return STATUS_BUILTIN_ERROR;
}
}
if (fgcolor == NULL && bgcolor == NULL && !bold && !underline)
{
append_format(stderr_buffer,
_(L"%ls: Expected an argument\n"),
argv[0]);
return STATUS_BUILTIN_ERROR;
}
const rgb_color_t fg = rgb_color_t(fgcolor ? fgcolor : L"");
if (fgcolor && fg.is_none())
{
append_format(stderr_buffer, _(L"%ls: Unknown color '%ls'\n"), argv[0], fgcolor);
return STATUS_BUILTIN_ERROR;
}
const rgb_color_t bg = rgb_color_t(bgcolor ? bgcolor : L"");
if (bgcolor && bg.is_none())
{
append_format(stderr_buffer, _(L"%ls: Unknown color '%ls'\n"), argv[0], bgcolor);
return STATUS_BUILTIN_ERROR;
}
/* Make sure that the term exists */
if (cur_term == NULL && setupterm(0, STDOUT_FILENO, &errret) == ERR)
{
append_format(stderr_buffer, _(L"%ls: Could not set up terminal\n"), argv[0]);
return STATUS_BUILTIN_ERROR;
}
/*
Test if we have at least basic support for setting fonts, colors
and related bits - otherwise just give up...
*/
if (! exit_attribute_mode)
{
return STATUS_BUILTIN_ERROR;
}
/* Save old output function so we can restore it */
int (* const saved_writer_func)(char) = output_get_writer();
/* Set our output function, which writes to a std::string */
builtin_set_color_output.clear();
output_set_writer(set_color_builtin_outputter);
if (bold)
{
if (enter_bold_mode)
writembs(tparm(enter_bold_mode));
}
if (underline)
{
if (enter_underline_mode)
writembs(enter_underline_mode);
}
if (bgcolor != NULL)
{
if (bg.is_normal())
{
write_background_color(0);
writembs(tparm(exit_attribute_mode));
}
}
if (fgcolor != NULL)
{
if (fg.is_normal())
{
write_foreground_color(0);
writembs(tparm(exit_attribute_mode));
}
else
{
write_foreground_color(index_for_color(fg));
}
}
if (bgcolor != NULL)
{
if (! bg.is_normal())
{
write_background_color(index_for_color(bg));
}
}
/* Restore saved writer function */
output_set_writer(saved_writer_func);
/* Output the collected string */
std::string local_output;
std::swap(builtin_set_color_output, local_output);
stdout_buffer.append(str2wcstring(local_output));
return STATUS_BUILTIN_OK;
}

970
builtin_test.cpp Normal file
View File

@@ -0,0 +1,970 @@
/** \file builtin_test.cpp Functions defining the test builtin
Functions used for implementing the test builtin.
Implemented from scratch (yes, really) by way of IEEE 1003.1 as reference.
*/
#include "config.h"
#include "common.h"
#include "builtin.h"
#include "wutil.h"
#include "proc.h"
#include <sys/stat.h>
#include <memory>
enum
{
BUILTIN_TEST_SUCCESS = STATUS_BUILTIN_OK,
BUILTIN_TEST_FAIL = STATUS_BUILTIN_ERROR
};
int builtin_test(parser_t &parser, wchar_t **argv);
static const wchar_t * const condstr[] =
{
L"!", L"&&", L"||", L"==", L"!=", L"<", L">", L"-nt", L"-ot", L"-ef", L"-eq",
L"-ne", L"-lt", L"-gt", L"-le", L"-ge", L"=~"
};
namespace test_expressions
{
enum token_t
{
test_unknown, // arbitrary string
test_bang, // "!", inverts sense
test_filetype_b, // "-b", for block special files
test_filetype_c, // "-c" for character special files
test_filetype_d, // "-d" for directories
test_filetype_e, // "-e" for files that exist
test_filetype_f, // "-f" for for regular files
test_filetype_g, // "-g" for set-group-id
test_filetype_h, // "-h" for symbolic links
test_filetype_L, // "-L", same as -h
test_filetype_p, // "-p", for FIFO
test_filetype_S, // "-S", socket
test_filesize_s, // "-s", size greater than zero
test_filedesc_t, // "-t", whether the fd is associated with a terminal
test_fileperm_r, // "-r", read permission
test_fileperm_u, // "-u", whether file is setuid
test_fileperm_w, // "-w", whether file write permission is allowed
test_fileperm_x, // "-x", whether file execute/search is allowed
test_string_n, // "-n", non-empty string
test_string_z, // "-z", true if length of string is 0
test_string_equal, // "=", true if strings are identical
test_string_not_equal, // "!=", true if strings are not identical
test_number_equal, // "-eq", true if numbers are equal
test_number_not_equal, // "-ne", true if numbers are not equal
test_number_greater, // "-gt", true if first number is larger than second
test_number_greater_equal, // "-ge", true if first number is at least second
test_number_lesser, // "-lt", true if first number is smaller than second
test_number_lesser_equal, // "-le", true if first number is at most second
test_combine_and, // "-a", true if left and right are both true
test_combine_or, // "-o", true if either left or right is true
test_paren_open, // "(", open paren
test_paren_close, // ")", close paren
};
static bool binary_primary_evaluate(test_expressions::token_t token, const wcstring &left, const wcstring &right, wcstring_list_t &errors);
static bool unary_primary_evaluate(test_expressions::token_t token, const wcstring &arg, wcstring_list_t &errors);
enum
{
UNARY_PRIMARY = 1 << 0,
BINARY_PRIMARY = 1 << 1
};
static const struct token_info_t
{
token_t tok;
const wchar_t *string;
unsigned int flags;
} token_infos[] =
{
{test_unknown, L"", 0},
{test_bang, L"!", 0},
{test_filetype_b, L"-b", UNARY_PRIMARY},
{test_filetype_c, L"-c", UNARY_PRIMARY},
{test_filetype_d, L"-d", UNARY_PRIMARY},
{test_filetype_e, L"-e", UNARY_PRIMARY},
{test_filetype_f, L"-f", UNARY_PRIMARY},
{test_filetype_g, L"-g", UNARY_PRIMARY},
{test_filetype_h, L"-h", UNARY_PRIMARY},
{test_filetype_L, L"-L", UNARY_PRIMARY},
{test_filetype_p, L"-p", UNARY_PRIMARY},
{test_filetype_S, L"-S", UNARY_PRIMARY},
{test_filesize_s, L"-s", UNARY_PRIMARY},
{test_filedesc_t, L"-t", UNARY_PRIMARY},
{test_fileperm_r, L"-r", UNARY_PRIMARY},
{test_fileperm_u, L"-u", UNARY_PRIMARY},
{test_fileperm_w, L"-w", UNARY_PRIMARY},
{test_fileperm_x, L"-x", UNARY_PRIMARY},
{test_string_n, L"-n", UNARY_PRIMARY},
{test_string_z, L"-z", UNARY_PRIMARY},
{test_string_equal, L"=", BINARY_PRIMARY},
{test_string_not_equal, L"!=", BINARY_PRIMARY},
{test_number_equal, L"-eq", BINARY_PRIMARY},
{test_number_not_equal, L"-ne", BINARY_PRIMARY},
{test_number_greater, L"-gt", BINARY_PRIMARY},
{test_number_greater_equal, L"-ge", BINARY_PRIMARY},
{test_number_lesser, L"-lt", BINARY_PRIMARY},
{test_number_lesser_equal, L"-le", BINARY_PRIMARY},
{test_combine_and, L"-a", 0},
{test_combine_or, L"-o", 0},
{test_paren_open, L"(", 0},
{test_paren_close, L")", 0}
};
const token_info_t *token_for_string(const wcstring &str)
{
for (size_t i=0; i < sizeof token_infos / sizeof *token_infos; i++)
{
if (str == token_infos[i].string)
{
return &token_infos[i];
}
}
return &token_infos[0]; //unknown
}
/* Grammar.
<expr> = <combining_expr>
<combining_expr> = <unary_expr> and/or <combining_expr> |
<unary_expr>
<unary_expr> = bang <unary_expr> |
<primary>
<primary> = <unary_primary> arg |
arg <binary_primary> arg |
'(' <expr> ')'
*/
class expression;
class test_parser
{
private:
wcstring_list_t strings;
wcstring_list_t errors;
expression *error(const wchar_t *fmt, ...);
void add_error(const wchar_t *fmt, ...);
const wcstring &arg(unsigned int idx)
{
return strings.at(idx);
}
public:
test_parser(const wcstring_list_t &val) : strings(val)
{ }
expression *parse_expression(unsigned int start, unsigned int end);
expression *parse_3_arg_expression(unsigned int start, unsigned int end);
expression *parse_4_arg_expression(unsigned int start, unsigned int end);
expression *parse_combining_expression(unsigned int start, unsigned int end);
expression *parse_unary_expression(unsigned int start, unsigned int end);
expression *parse_primary(unsigned int start, unsigned int end);
expression *parse_parenthentical(unsigned int start, unsigned int end);
expression *parse_unary_primary(unsigned int start, unsigned int end);
expression *parse_binary_primary(unsigned int start, unsigned int end);
expression *parse_just_a_string(unsigned int start, unsigned int end);
static expression *parse_args(const wcstring_list_t &args, wcstring &err);
};
struct range_t
{
unsigned int start;
unsigned int end;
range_t(unsigned s, unsigned e) : start(s), end(e) { }
};
/* Base class for expressions */
class expression
{
protected:
expression(token_t what, range_t where) : token(what), range(where) { }
public:
const token_t token;
range_t range;
virtual ~expression() { }
// evaluate returns true if the expression is true (i.e. BUILTIN_TEST_SUCCESS)
virtual bool evaluate(wcstring_list_t &errors) = 0;
};
typedef std::auto_ptr<expression> expr_ref_t;
/* Single argument like -n foo or "just a string" */
class unary_primary : public expression
{
public:
wcstring arg;
unary_primary(token_t tok, range_t where, const wcstring &what) : expression(tok, where), arg(what) { }
bool evaluate(wcstring_list_t &errors);
};
/* Two argument primary like foo != bar */
class binary_primary : public expression
{
public:
wcstring arg_left;
wcstring arg_right;
binary_primary(token_t tok, range_t where, const wcstring &left, const wcstring &right) : expression(tok, where), arg_left(left), arg_right(right)
{ }
bool evaluate(wcstring_list_t &errors);
};
/* Unary operator like bang */
class unary_operator : public expression
{
public:
expr_ref_t subject;
unary_operator(token_t tok, range_t where, expr_ref_t &exp) : expression(tok, where), subject(exp) { }
bool evaluate(wcstring_list_t &errors);
};
/* Combining expression. Contains a list of AND or OR expressions. It takes more than two so that we don't have to worry about precedence in the parser. */
class combining_expression : public expression
{
public:
const std::vector<expression *> subjects;
const std::vector<token_t> combiners;
combining_expression(token_t tok, range_t where, const std::vector<expression *> &exprs, const std::vector<token_t> &combs) : expression(tok, where), subjects(exprs), combiners(combs)
{
/* We should have one more subject than combiner */
assert(subjects.size() == combiners.size() + 1);
}
/* We are responsible for destroying our expressions */
virtual ~combining_expression()
{
for (size_t i=0; i < subjects.size(); i++)
{
delete subjects[i];
}
}
bool evaluate(wcstring_list_t &errors);
};
/* Parenthetical expression */
class parenthetical_expression : public expression
{
public:
expr_ref_t contents;
parenthetical_expression(token_t tok, range_t where, expr_ref_t &expr) : expression(tok, where), contents(expr) { }
virtual bool evaluate(wcstring_list_t &errors);
};
void test_parser::add_error(const wchar_t *fmt, ...)
{
assert(fmt != NULL);
va_list va;
va_start(va, fmt);
this->errors.push_back(vformat_string(fmt, va));
va_end(va);
}
expression *test_parser::error(const wchar_t *fmt, ...)
{
assert(fmt != NULL);
va_list va;
va_start(va, fmt);
this->errors.push_back(vformat_string(fmt, va));
va_end(va);
return NULL;
}
expression *test_parser::parse_unary_expression(unsigned int start, unsigned int end)
{
if (start >= end)
{
return error(L"Missing argument at index %u", start);
}
token_t tok = token_for_string(arg(start))->tok;
if (tok == test_bang)
{
expr_ref_t subject(parse_unary_expression(start + 1, end));
if (subject.get())
{
return new unary_operator(tok, range_t(start, subject->range.end), subject);
}
else
{
return NULL;
}
}
else
{
return parse_primary(start, end);
}
}
/* Parse a combining expression (AND, OR) */
expression *test_parser::parse_combining_expression(unsigned int start, unsigned int end)
{
if (start >= end)
return NULL;
std::vector<expression *> subjects;
std::vector<token_t> combiners;
unsigned int idx = start;
bool first = true;
while (idx < end)
{
if (! first)
{
/* This is not the first expression, so we expect a combiner. */
token_t combiner = token_for_string(arg(idx))->tok;
if (combiner != test_combine_and && combiner != test_combine_or)
{
/* Not a combiner, we're done */
this->errors.insert(this->errors.begin(), format_string(L"Expected a combining operator like '-a' at index %u", idx));
break;
}
combiners.push_back(combiner);
idx++;
}
/* Parse another expression */
expression *expr = parse_unary_expression(idx, end);
if (! expr)
{
add_error(L"Missing argument at index %u", idx);
if (! first)
{
/* Clean up the dangling combiner, since it never got its right hand expression */
combiners.pop_back();
}
break;
}
/* Go to the end of this expression */
idx = expr->range.end;
subjects.push_back(expr);
first = false;
}
if (! subjects.empty())
{
/* Our new expression takes ownership of all expressions we created. The token we pass is irrelevant. */
return new combining_expression(test_combine_and, range_t(start, idx), subjects, combiners);
}
else
{
/* No subjects */
return NULL;
}
}
expression *test_parser::parse_unary_primary(unsigned int start, unsigned int end)
{
/* We need two arguments */
if (start >= end)
{
return error(L"Missing argument at index %u", start);
}
if (start + 1 >= end)
{
return error(L"Missing argument at index %u", start + 1);
}
/* All our unary primaries are prefix, so the operator is at start. */
const token_info_t *info = token_for_string(arg(start));
if (!(info->flags & UNARY_PRIMARY))
return NULL;
return new unary_primary(info->tok, range_t(start, start + 2), arg(start + 1));
}
expression *test_parser::parse_just_a_string(unsigned int start, unsigned int end)
{
/* Handle a string as a unary primary that is not a token of any other type.
e.g. 'test foo -a bar' should evaluate to true
We handle this with a unary primary of test_string_n
*/
/* We need one arguments */
if (start >= end)
{
return error(L"Missing argument at index %u", start);
}
const token_info_t *info = token_for_string(arg(start));
if (info->tok != test_unknown)
{
return error(L"Unexpected argument type at index %u", start);
}
/* This is hackish; a nicer way to implement this would be with a "just a string" expression type */
return new unary_primary(test_string_n, range_t(start, start + 1), arg(start));
}
#if 0
expression *test_parser::parse_unary_primary(unsigned int start, unsigned int end)
{
/* We need either one or two arguments */
if (start >= end)
{
return error(L"Missing argument at index %u", start);
}
/* The index of the argument to the unary primary */
unsigned int arg_idx;
/* All our unary primaries are prefix, so any operator is at start. But it also may just be a string, with no operator. */
const token_info_t *info = token_for_string(arg(start));
if (info->flags & UNARY_PRIMARY)
{
/* We have an operator. Skip the operator argument */
arg_idx = start + 1;
/* We have some freedom here...do we allow other tokens for the argument to operate on?
For example, should 'test -n =' work? I say yes. So no typechecking on the next token. */
}
else if (info->tok == test_unknown)
{
/* "Just a string. */
arg_idx = start;
}
else
{
/* Here we don't allow arbitrary tokens as "just a string." I.e. 'test = -a =' should have a parse error. We could relax this at some point. */
return error(L"Parse error at argument index %u", start);
}
/* Verify we have the argument we want, i.e. test -n should fail to parse */
if (arg_idx >= end)
{
return error(L"Missing argument at index %u", arg_idx);
}
return new unary_primary(info->tok, range_t(start, arg_idx + 1), arg(arg_idx));
}
#endif
expression *test_parser::parse_binary_primary(unsigned int start, unsigned int end)
{
/* We need three arguments */
for (unsigned int idx = start; idx < start + 3; idx++)
{
if (idx >= end)
{
return error(L"Missing argument at index %u", idx);
}
}
/* All our binary primaries are infix, so the operator is at start + 1. */
const token_info_t *info = token_for_string(arg(start + 1));
if (!(info->flags & BINARY_PRIMARY))
return NULL;
return new binary_primary(info->tok, range_t(start, start + 3), arg(start), arg(start + 2));
}
expression *test_parser::parse_parenthentical(unsigned int start, unsigned int end)
{
/* We need at least three arguments: open paren, argument, close paren */
if (start + 3 >= end)
return NULL;
/* Must start with an open expression */
const token_info_t *open_paren = token_for_string(arg(start));
if (open_paren->tok != test_paren_open)
return NULL;
/* Parse a subexpression */
expression *subexr_ptr = parse_expression(start + 1, end);
if (! subexr_ptr)
return NULL;
expr_ref_t subexpr(subexr_ptr);
/* Parse a close paren */
unsigned close_index = subexpr->range.end;
assert(close_index <= end);
if (close_index == end)
{
return error(L"Missing close paren at index %u", close_index);
}
const token_info_t *close_paren = token_for_string(arg(close_index));
if (close_paren->tok != test_paren_close)
{
return error(L"Expected close paren at index %u", close_index);
}
/* Success */
return new parenthetical_expression(test_paren_open, range_t(start, close_index+1), subexpr);
}
expression *test_parser::parse_primary(unsigned int start, unsigned int end)
{
if (start >= end)
{
return error(L"Missing argument at index %u", start);
}
expression *expr = NULL;
if (! expr) expr = parse_parenthentical(start, end);
if (! expr) expr = parse_unary_primary(start, end);
if (! expr) expr = parse_binary_primary(start, end);
if (! expr) expr = parse_just_a_string(start, end);
return expr;
}
// See IEEE 1003.1 breakdown of the behavior for different parameter counts
expression *test_parser::parse_3_arg_expression(unsigned int start, unsigned int end)
{
assert(end - start == 3);
expression *result = NULL;
const token_info_t *center_token = token_for_string(arg(start + 1));
if (center_token->flags & BINARY_PRIMARY)
{
result = parse_binary_primary(start, end);
}
else if (center_token->tok == test_combine_and || center_token->tok == test_combine_or)
{
expr_ref_t left(parse_unary_expression(start, start + 1));
expr_ref_t right(parse_unary_expression(start + 2, start + 3));
if (left.get() && right.get())
{
// Transfer ownership to the vector of subjects
std::vector<token_t> combiners(1, center_token->tok);
std::vector<expression *> subjects;
subjects.push_back(left.release());
subjects.push_back(right.release());
result = new combining_expression(center_token->tok, range_t(start, end), subjects, combiners);
}
}
else
{
result = parse_unary_expression(start, end);
}
return result;
}
expression *test_parser::parse_4_arg_expression(unsigned int start, unsigned int end)
{
assert(end - start == 4);
expression *result = NULL;
token_t first_token = token_for_string(arg(start))->tok;
if (first_token == test_bang)
{
expr_ref_t subject(parse_3_arg_expression(start + 1, end));
if (subject.get())
{
result = new unary_operator(first_token, range_t(start, subject->range.end), subject);
}
}
else if (first_token == test_paren_open)
{
result = parse_parenthentical(start, end);
}
else
{
result = parse_combining_expression(start, end);
}
return result;
}
expression *test_parser::parse_expression(unsigned int start, unsigned int end)
{
if (start >= end)
{
return error(L"Missing argument at index %u", start);
}
unsigned int argc = end - start;
switch (argc)
{
case 0:
assert(0); //should have been caught by the above test
return NULL;
case 1:
{
return error(L"Missing argument at index %u", start + 1);
}
case 2:
{
return parse_unary_expression(start, end);
}
case 3:
{
return parse_3_arg_expression(start, end);
}
case 4:
{
return parse_4_arg_expression(start, end);
}
default:
{
return parse_combining_expression(start, end);
}
}
}
expression *test_parser::parse_args(const wcstring_list_t &args, wcstring &err)
{
/* Empty list and one-arg list should be handled by caller */
assert(args.size() > 1);
test_parser parser(args);
expression *result = parser.parse_expression(0, (unsigned int)args.size());
/* Handle errors */
bool errored = false;
for (size_t i = 0; i < parser.errors.size(); i++)
{
err.append(L"test: ");
err.append(parser.errors.at(i));
err.push_back(L'\n');
errored = true;
// For now we only show the first error
break;
}
if (result)
{
/* It's also an error if there are any unused arguments. This is not detected by parse_expression() */
assert(result->range.end <= args.size());
if (result->range.end < args.size())
{
if (err.empty())
{
append_format(err, L"test: unexpected argument at index %lu: '%ls'\n", (unsigned long)result->range.end, args.at(result->range.end).c_str());
}
errored = true;
delete result;
result = NULL;
}
}
return result;
}
bool unary_primary::evaluate(wcstring_list_t &errors)
{
return unary_primary_evaluate(token, arg, errors);
}
bool binary_primary::evaluate(wcstring_list_t &errors)
{
return binary_primary_evaluate(token, arg_left, arg_right, errors);
}
bool unary_operator::evaluate(wcstring_list_t &errors)
{
switch (token)
{
case test_bang:
assert(subject.get());
return ! subject->evaluate(errors);
default:
errors.push_back(format_string(L"Unknown token type in %s", __func__));
return false;
}
}
bool combining_expression::evaluate(wcstring_list_t &errors)
{
switch (token)
{
case test_combine_and:
case test_combine_or:
{
/* One-element case */
if (subjects.size() == 1)
return subjects.at(0)->evaluate(errors);
/* Evaluate our lists, remembering that AND has higher precedence than OR. We can visualize this as a sequence of OR expressions of AND expressions. */
assert(combiners.size() + 1 == subjects.size());
assert(! subjects.empty());
size_t idx = 0, max = subjects.size();
bool or_result = false;
while (idx < max)
{
if (or_result)
{
/* Short circuit */
break;
}
/* Evaluate a stream of AND starting at given subject index. It may only have one element. */
bool and_result = true;
for (; idx < max; idx++)
{
/* Evaluate it, short-circuiting */
and_result = and_result && subjects.at(idx)->evaluate(errors);
/* If the combiner at this index (which corresponding to how we combine with the next subject) is not AND, then exit the loop */
if (idx + 1 < max && combiners.at(idx) != test_combine_and)
{
idx++;
break;
}
}
/* OR it in */
or_result = or_result || and_result;
}
return or_result;
}
default:
errors.push_back(format_string(L"Unknown token type in %s", __func__));
return BUILTIN_TEST_FAIL;
}
}
bool parenthetical_expression::evaluate(wcstring_list_t &errors)
{
return contents->evaluate(errors);
}
/* IEEE 1003.1 says nothing about what it means for two strings to be "algebraically equal". For example, should we interpret 0x10 as 0, 10, or 16? Here we use only base 10 and use wcstoll, which allows for leading + and -, and leading whitespace. This matches bash. */
static bool parse_number(const wcstring &arg, long long *out)
{
const wchar_t *str = arg.c_str();
wchar_t *endptr = NULL;
*out = wcstoll(str, &endptr, 10);
return endptr && *endptr == L'\0';
}
static bool binary_primary_evaluate(test_expressions::token_t token, const wcstring &left, const wcstring &right, wcstring_list_t &errors)
{
using namespace test_expressions;
long long left_num, right_num;
switch (token)
{
case test_string_equal:
return left == right;
case test_string_not_equal:
return left != right;
case test_number_equal:
return parse_number(left, &left_num) && parse_number(right, &right_num) && left_num == right_num;
case test_number_not_equal:
return parse_number(left, &left_num) && parse_number(right, &right_num) && left_num != right_num;
case test_number_greater:
return parse_number(left, &left_num) && parse_number(right, &right_num) && left_num > right_num;
case test_number_greater_equal:
return parse_number(left, &left_num) && parse_number(right, &right_num) && left_num >= right_num;
case test_number_lesser:
return parse_number(left, &left_num) && parse_number(right, &right_num) && left_num < right_num;
case test_number_lesser_equal:
return parse_number(left, &left_num) && parse_number(right, &right_num) && left_num <= right_num;
default:
errors.push_back(format_string(L"Unknown token type in %s", __func__));
return false;
}
}
static bool unary_primary_evaluate(test_expressions::token_t token, const wcstring &arg, wcstring_list_t &errors)
{
using namespace test_expressions;
struct stat buf;
long long num;
switch (token)
{
case test_filetype_b: // "-b", for block special files
return !wstat(arg, &buf) && S_ISBLK(buf.st_mode);
case test_filetype_c: // "-c" for character special files
return !wstat(arg, &buf) && S_ISCHR(buf.st_mode);
case test_filetype_d: // "-d" for directories
return !wstat(arg, &buf) && S_ISDIR(buf.st_mode);
case test_filetype_e: // "-e" for files that exist
return !wstat(arg, &buf);
case test_filetype_f: // "-f" for for regular files
return !wstat(arg, &buf) && S_ISREG(buf.st_mode);
case test_filetype_g: // "-g" for set-group-id
return !wstat(arg, &buf) && (S_ISGID & buf.st_mode);
case test_filetype_h: // "-h" for symbolic links
case test_filetype_L: // "-L", same as -h
return !lwstat(arg, &buf) && S_ISLNK(buf.st_mode);
case test_filetype_p: // "-p", for FIFO
return !wstat(arg, &buf) && S_ISFIFO(buf.st_mode);
case test_filetype_S: // "-S", socket
return !wstat(arg, &buf) && S_ISSOCK(buf.st_mode);
case test_filesize_s: // "-s", size greater than zero
return !wstat(arg, &buf) && buf.st_size > 0;
case test_filedesc_t: // "-t", whether the fd is associated with a terminal
return parse_number(arg, &num) && num == (int)num && isatty((int)num);
case test_fileperm_r: // "-r", read permission
return !waccess(arg, R_OK);
case test_fileperm_u: // "-u", whether file is setuid
return !wstat(arg, &buf) && (S_ISUID & buf.st_mode);
case test_fileperm_w: // "-w", whether file write permission is allowed
return !waccess(arg, W_OK);
case test_fileperm_x: // "-x", whether file execute/search is allowed
return !waccess(arg, X_OK);
case test_string_n: // "-n", non-empty string
return ! arg.empty();
case test_string_z: // "-z", true if length of string is 0
return arg.empty();
default:
errors.push_back(format_string(L"Unknown token type in %s", __func__));
return false;
}
}
};
/*
* Evaluate a conditional expression given the arguments.
* If fromtest is set, the caller is the test or [ builtin;
* with the pointer giving the name of the command.
* for POSIX conformance this supports a more limited range
* of functionality.
*
* Return status is the final shell status, i.e. 0 for true,
* 1 for false and 2 for error.
*/
int builtin_test(parser_t &parser, wchar_t **argv)
{
using namespace test_expressions;
/* The first argument should be the name of the command ('test') */
if (! argv[0])
return BUILTIN_TEST_FAIL;
/* Whether we are invoked with bracket '[' or not */
const bool is_bracket = ! wcscmp(argv[0], L"[");
size_t argc = 0;
while (argv[argc + 1])
argc++;
/* If we're bracket, the last argument ought to be ]; we ignore it. Note that argc is the number of arguments after the command name; thus argv[argc] is the last argument. */
if (is_bracket)
{
if (! wcscmp(argv[argc], L"]"))
{
/* Ignore the closing bracketp */
argc--;
}
else
{
builtin_show_error(L"[: the last argument must be ']'\n");
return BUILTIN_TEST_FAIL;
}
}
/* Collect the arguments into a list */
const wcstring_list_t args(argv + 1, argv + 1 + argc);
switch (argc)
{
case 0:
{
// Per 1003.1, exit false
return BUILTIN_TEST_FAIL;
}
case 1:
{
// Per 1003.1, exit true if the arg is non-empty
return args.at(0).empty() ? BUILTIN_TEST_FAIL : BUILTIN_TEST_SUCCESS;
}
default:
{
// Try parsing. If expr is not nil, we are responsible for deleting it.
wcstring err;
expression *expr = test_parser::parse_args(args, err);
if (! expr)
{
#if 0
printf("Oops! test was given args:\n");
for (size_t i=0; i < argc; i++)
{
printf("\t%ls\n", args.at(i).c_str());
}
printf("and returned parse error: %ls\n", err.c_str());
#endif
builtin_show_error(err);
return BUILTIN_TEST_FAIL;
}
else
{
wcstring_list_t eval_errors;
bool result = expr->evaluate(eval_errors);
if (! eval_errors.empty())
{
printf("test returned eval errors:\n");
for (size_t i=0; i < eval_errors.size(); i++)
{
printf("\t%ls\n", eval_errors.at(i).c_str());
}
}
delete expr;
return result ? BUILTIN_TEST_SUCCESS : BUILTIN_TEST_FAIL;
}
}
}
return 1;
}

512
builtin_ulimit.cpp Normal file
View File

@@ -0,0 +1,512 @@
/** \file builtin_ulimit.c Functions defining the ulimit builtin
Functions used for implementing the ulimit builtin.
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <wctype.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <errno.h>
#include "fallback.h"
#include "util.h"
#include "builtin.h"
#include "common.h"
#include "wgetopt.h"
/**
Struct describing a resource limit
*/
struct resource_t
{
/**
Resource id
*/
int resource;
/**
Description of resource
*/
const wchar_t *desc;
/**
Switch used on commandline to specify resource
*/
wchar_t switch_char;
/**
The implicit multiplier used when setting getting values
*/
int multiplier;
}
;
/**
Array of resource_t structs, describing all known resource types.
*/
static const struct resource_t resource_arr[] =
{
{
RLIMIT_CORE, L"Maximum size of core files created", L'c', 1024
}
,
{
RLIMIT_DATA, L"Maximum size of a processs data segment", L'd', 1024
}
,
{
RLIMIT_FSIZE, L"Maximum size of files created by the shell", L'f', 1024
}
,
#ifdef RLIMIT_MEMLOCK
{
RLIMIT_MEMLOCK, L"Maximum size that may be locked into memory", L'l', 1024
}
,
#endif
#ifdef RLIMIT_RSS
{
RLIMIT_RSS, L"Maximum resident set size", L'm', 1024
}
,
#endif
{
RLIMIT_NOFILE, L"Maximum number of open file descriptors", L'n', 1
}
,
{
RLIMIT_STACK, L"Maximum stack size", L's', 1024
}
,
{
RLIMIT_CPU, L"Maximum amount of cpu time in seconds", L't', 1
}
,
#ifdef RLIMIT_NPROC
{
RLIMIT_NPROC, L"Maximum number of processes available to a single user", L'u', 1
}
,
#endif
#ifdef RLIMIT_AS
{
RLIMIT_AS, L"Maximum amount of virtual memory available to the shell", L'v', 1024
}
,
#endif
{
0, 0, 0, 0
}
}
;
/**
Get the implicit multiplication factor for the specified resource limit
*/
static int get_multiplier(int what)
{
int i;
for (i=0; resource_arr[i].desc; i++)
{
if (resource_arr[i].resource == what)
{
return resource_arr[i].multiplier;
}
}
return -1;
}
/**
Return the value for the specified resource limit. This function
does _not_ multiply the limit value by the multiplier constant used
by the commandline ulimit.
*/
static rlim_t get(int resource, int hard)
{
struct rlimit ls;
getrlimit(resource, &ls);
return hard ? ls.rlim_max:ls.rlim_cur;
}
/**
Print the value of the specified resource limit
*/
static void print(int resource, int hard)
{
rlim_t l = get(resource, hard);
if (l == RLIM_INFINITY)
stdout_buffer.append(L"unlimited\n");
else
append_format(stdout_buffer, L"%d\n", l / get_multiplier(resource));
}
/**
Print values of all resource limits
*/
static void print_all(int hard)
{
int i;
int w=0;
for (i=0; resource_arr[i].desc; i++)
{
w=maxi(w, my_wcswidth(resource_arr[i].desc));
}
for (i=0; resource_arr[i].desc; i++)
{
struct rlimit ls;
rlim_t l;
getrlimit(resource_arr[i].resource, &ls);
l = hard ? ls.rlim_max:ls.rlim_cur;
const wchar_t *unit = ((resource_arr[i].resource==RLIMIT_CPU)?L"(seconds, ":(get_multiplier(resource_arr[i].resource)==1?L"(":L"(kB, "));
append_format(stdout_buffer,
L"%-*ls %10ls-%lc) ",
w,
resource_arr[i].desc,
unit,
resource_arr[i].switch_char);
if (l == RLIM_INFINITY)
{
stdout_buffer.append(L"unlimited\n");
}
else
{
append_format(stdout_buffer, L"%d\n", l/get_multiplier(resource_arr[i].resource));
}
}
}
/**
Returns the description for the specified resource limit
*/
static const wchar_t *get_desc(int what)
{
int i;
for (i=0; resource_arr[i].desc; i++)
{
if (resource_arr[i].resource == what)
{
return resource_arr[i].desc;
}
}
return L"Not a resource";
}
/**
Set the new value of the specified resource limit. This function
does _not_ multiply the limit value by the multiplier constant used
by the commandline ulimit.
*/
static int set(int resource, int hard, int soft, rlim_t value)
{
struct rlimit ls;
getrlimit(resource, &ls);
if (hard)
{
ls.rlim_max = value;
}
if (soft)
{
ls.rlim_cur = value;
/*
Do not attempt to set the soft limit higher than the hard limit
*/
if ((value == RLIM_INFINITY && ls.rlim_max != RLIM_INFINITY) ||
(value != RLIM_INFINITY && ls.rlim_max != RLIM_INFINITY && value > ls.rlim_max))
{
ls.rlim_cur = ls.rlim_max;
}
}
if (setrlimit(resource, &ls))
{
if (errno == EPERM)
append_format(stderr_buffer, L"ulimit: Permission denied when changing resource of type '%ls'\n", get_desc(resource));
else
builtin_wperror(L"ulimit");
return 1;
}
return 0;
}
/**
The ulimit builtin, used for setting resource limits. Defined in
builtin_ulimit.c.
*/
static int builtin_ulimit(parser_t &parser, wchar_t ** argv)
{
int hard=0;
int soft=0;
int what = RLIMIT_FSIZE;
int report_all = 0;
int argc = builtin_count_args(argv);
woptind=0;
while (1)
{
static const struct woption
long_options[] =
{
{
L"all", no_argument, 0, 'a'
}
,
{
L"hard", no_argument, 0, 'H'
}
,
{
L"soft", no_argument, 0, 'S'
}
,
{
L"core-size", no_argument, 0, 'c'
}
,
{
L"data-size", no_argument, 0, 'd'
}
,
{
L"file-size", no_argument, 0, 'f'
}
,
{
L"lock-size", no_argument, 0, 'l'
}
,
{
L"resident-set-size", no_argument, 0, 'm'
}
,
{
L"file-descriptor-count", no_argument, 0, 'n'
}
,
{
L"stack-size", no_argument, 0, 's'
}
,
{
L"cpu-time", no_argument, 0, 't'
}
,
{
L"process-count", no_argument, 0, 'u'
}
,
{
L"virtual-memory-size", no_argument, 0, 'v'
}
,
{
L"help", no_argument, 0, 'h'
}
,
{
0, 0, 0, 0
}
}
;
int opt_index = 0;
int opt = wgetopt_long(argc,
argv,
L"aHScdflmnstuvh",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
case 0:
if (long_options[opt_index].flag != 0)
break;
append_format(stderr_buffer,
BUILTIN_ERR_UNKNOWN,
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
case L'a':
report_all=1;
break;
case L'H':
hard=1;
break;
case L'S':
soft=1;
break;
case L'c':
what=RLIMIT_CORE;
break;
case L'd':
what=RLIMIT_DATA;
break;
case L'f':
what=RLIMIT_FSIZE;
break;
#ifdef RLIMIT_MEMLOCK
case L'l':
what=RLIMIT_MEMLOCK;
break;
#endif
#ifdef RLIMIT_RSS
case L'm':
what=RLIMIT_RSS;
break;
#endif
case L'n':
what=RLIMIT_NOFILE;
break;
case L's':
what=RLIMIT_STACK;
break;
case L't':
what=RLIMIT_CPU;
break;
#ifdef RLIMIT_NPROC
case L'u':
what=RLIMIT_NPROC;
break;
#endif
#ifdef RLIMIT_AS
case L'v':
what=RLIMIT_AS;
break;
#endif
case L'h':
builtin_print_help(parser, argv[0], stdout_buffer);
return 0;
case L'?':
builtin_unknown_option(parser, argv[0], argv[woptind-1]);
return 1;
}
}
if (report_all)
{
if (argc - woptind == 0)
{
print_all(hard);
}
else
{
stderr_buffer.append(argv[0]);
stderr_buffer.append(L": Too many arguments\n");
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
return 0;
}
switch (argc - woptind)
{
case 0:
{
/*
Show current limit value
*/
print(what, hard);
break;
}
case 1:
{
/*
Change current limit value
*/
rlim_t new_limit;
wchar_t *end;
/*
Set both hard and soft limits if nothing else was specified
*/
if (!(hard+soft))
{
hard=soft=1;
}
if (wcscasecmp(argv[woptind], L"unlimited")==0)
{
new_limit = RLIM_INFINITY;
}
else if (wcscasecmp(argv[woptind], L"hard")==0)
{
new_limit = get(what, 1);
}
else if (wcscasecmp(argv[woptind], L"soft")==0)
{
new_limit = get(what, soft);
}
else
{
errno=0;
new_limit = wcstol(argv[woptind], &end, 10);
if (errno || *end)
{
append_format(stderr_buffer,
L"%ls: Invalid limit '%ls'\n",
argv[0],
argv[woptind]);
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
new_limit *= get_multiplier(what);
}
return set(what, hard, soft, new_limit);
}
default:
{
stderr_buffer.append(argv[0]);
stderr_buffer.append(L": Too many arguments\n");
builtin_print_help(parser, argv[0], stderr_buffer);
return 1;
}
}
return 0;
}

View File

@@ -1,6 +0,0 @@
# Support for benchmarking fish.
add_custom_target(benchmark
COMMAND ${CMAKE_SOURCE_DIR}/benchmarks/driver.sh $<TARGET_FILE:fish>
USES_TERMINAL
)

View File

@@ -1,123 +0,0 @@
# Distributed under the OSI-approved BSD 3-Clause License. See full license information in
# doc_src/license.hdr or https://cmake.org/licensing for details.
#.rst:
# CheckIncludeFiles
# -----------------
#
# Provides a macro to check if a list of one or more header files can
# be included together in ``C``.
#
# .. command:: CHECK_INCLUDE_FILES
#
# ::
#
# CHECK_INCLUDE_FILES("<includes>" <variable> [LANGUAGE <language>])
#
# Check if the given ``<includes>`` list may be included together
# in a ``C`` source file and store the result in an internal cache
# entry named ``<variable>``. Specify the ``<includes>`` argument
# as a :ref:`;-list <CMake Language Lists>` of header file names.
#
# If LANGUAGE is set, the specified compiler will be used to perform the
# check. Acceptable values are C and CXX.
#
# The following variables may be set before calling this macro to modify
# the way the check is run:
#
# ``CMAKE_REQUIRED_FLAGS``
# string of compile command line flags
# ``CMAKE_REQUIRED_DEFINITIONS``
# list of macros to define (-DFOO=bar)
# ``CMAKE_REQUIRED_INCLUDES``
# list of include directories
# ``CMAKE_REQUIRED_QUIET``
# execute quietly without messages
#
# See modules :module:`CheckIncludeFile` and :module:`CheckIncludeFileCXX`
# to check for a single header file in ``C`` or ``CXX`` languages.
macro(CHECK_INCLUDE_FILES INCLUDE VARIABLE)
if(NOT DEFINED "${VARIABLE}")
set(CMAKE_CONFIGURABLE_FILE_CONTENT "/* */\n")
if("x${ARGN}" STREQUAL "x")
if(CMAKE_C_COMPILER_LOADED)
set(_lang C)
elseif(CMAKE_CXX_COMPILER_LOADED)
set(_lang CXX)
else()
message(FATAL_ERROR "CHECK_INCLUDE_FILES needs either C or CXX language enabled")
endif()
elseif("x${ARGN}" MATCHES "^xLANGUAGE;([a-zA-Z]+)$")
set(_lang "${CMAKE_MATCH_1}")
else()
message(FATAL_ERROR "Unknown arguments:\n ${ARGN}\n")
endif()
if(_lang STREQUAL "C")
set(src ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CheckIncludeFiles/${VARIABLE}.c)
elseif(_lang STREQUAL "CXX")
set(src ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CheckIncludeFiles/${VARIABLE}.cpp)
else()
message(FATAL_ERROR "Unknown language:\n ${_lang}\nSupported languages: C, CXX.\n")
endif()
if(CMAKE_REQUIRED_INCLUDES)
set(CHECK_INCLUDE_FILES_INCLUDE_DIRS "-DINCLUDE_DIRECTORIES=${CMAKE_REQUIRED_INCLUDES}")
else()
set(CHECK_INCLUDE_FILES_INCLUDE_DIRS)
endif()
set(CHECK_INCLUDE_FILES_CONTENT "/* */\n")
set(MACRO_CHECK_INCLUDE_FILES_FLAGS ${CMAKE_REQUIRED_FLAGS})
foreach(FILE ${INCLUDE})
string(APPEND CMAKE_CONFIGURABLE_FILE_CONTENT
"#include <${FILE}>\n")
endforeach()
string(APPEND CMAKE_CONFIGURABLE_FILE_CONTENT
"\n\nint main(void){return 0;}\n")
configure_file("${CMAKE_ROOT}/Modules/CMakeConfigurableFile.in"
"${src}" @ONLY)
set(_INCLUDE ${INCLUDE}) # remove empty elements
if("${_INCLUDE}" MATCHES "^([^;]+);.+;([^;]+)$")
list(LENGTH _INCLUDE _INCLUDE_LEN)
set(_description "${_INCLUDE_LEN} include files ${CMAKE_MATCH_1}, ..., ${CMAKE_MATCH_2}")
elseif("${_INCLUDE}" MATCHES "^([^;]+);([^;]+)$")
set(_description "include files ${CMAKE_MATCH_1}, ${CMAKE_MATCH_2}")
else()
set(_description "include file ${_INCLUDE}")
endif()
if(NOT CMAKE_REQUIRED_QUIET)
message(STATUS "Looking for ${_description}")
endif()
try_compile(${VARIABLE}
${CMAKE_BINARY_DIR}
${src}
COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}
CMAKE_FLAGS
-DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_INCLUDE_FILES_FLAGS}
"${CHECK_INCLUDE_FILES_INCLUDE_DIRS}"
OUTPUT_VARIABLE OUTPUT)
if(${VARIABLE})
if(NOT CMAKE_REQUIRED_QUIET)
message(STATUS "Looking for ${_description} - found")
endif()
set(${VARIABLE} 1 CACHE INTERNAL "Have include ${INCLUDE}")
file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
"Determining if files ${INCLUDE} "
"exist passed with the following output:\n"
"${OUTPUT}\n\n")
else()
if(NOT CMAKE_REQUIRED_QUIET)
message(STATUS "Looking for ${_description} - not found")
endif()
set(${VARIABLE} "" CACHE INTERNAL "Have includes ${INCLUDE}")
file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
"Determining if files ${INCLUDE} "
"exist failed with the following output:\n"
"${OUTPUT}\nSource:\n${CMAKE_CONFIGURABLE_FILE_CONTENT}\n")
endif()
endif()
endmacro()

View File

@@ -1,302 +0,0 @@
# The following defines affect the environment configuration tests are run in:
# CMAKE_REQUIRED_DEFINITIONS, CMAKE_REQUIRED_FLAGS, CMAKE_REQUIRED_LIBRARIES,
# and CMAKE_REQUIRED_INCLUDES
# `wcstod_l` is a GNU-extension, sometimes hidden behind GNU-related defines.
# This is the case for at least Cygwin and Newlib.
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE=1)
include(CheckCXXCompilerFlag)
include(CMakePushCheckState)
if(APPLE)
check_cxx_compiler_flag("-Werror=unguarded-availability" REQUIRES_UNGUARDED_AVAILABILITY)
if(REQUIRES_UNGUARDED_AVAILABILITY)
list(APPEND CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS} "-Werror=unguarded-availability")
endif()
endif()
# An unrecognized flag is usually a warning and not an error, which CMake apparently does
# not pick up on. Combine it with -Werror to determine if it's actually supported.
# This is not bulletproof; old versions of GCC only emit a warning about unrecognized warning
# options when there are other warnings to emit :rolleyes:
# See https://github.com/fish-shell/fish-shell/commit/fe2da0a9#commitcomment-47431659
# GCC supports -Wno-redundant-move from GCC9 onwards
check_cxx_compiler_flag("-Werror=no-redundant-move" HAS_NO_REDUNDANT_MOVE)
if (HAS_NO_REDUNDANT_MOVE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-redundant-move")
endif()
# Clang once supported -Wno-redundant-move but replaced it with a Wredundant-move option instead
# (and it is functionally different from its older version of GCC's Wno-redundant-move).
check_cxx_compiler_flag("-Werror=redundant-move" HAS_REDUNDANT_MOVE)
if (HAS_REDUNDANT_MOVE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wredundant-move")
endif()
# Defeat bogus warnings about missing field initializers for `var{}` initialization.
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
cmake_push_check_state()
list(APPEND CMAKE_REQUIRED_FLAGS "-W")
check_cxx_source_compiles("
struct sr_t { int x; };
int main(void) {
sr_t sr{};
return sr.x;
}"
EMPTY_VALUE_INIT_ACCEPTED
FAIL_REGEX "-Wmissing-field-initializers"
)
if (NOT EMPTY_VALUE_INIT_ACCEPTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-field-initializers")
endif()
cmake_pop_check_state()
endif()
# Disable static destructors if we can.
check_cxx_compiler_flag("-fno-c++-static-destructors" DISABLE_STATIC_DESTRUCTORS)
if (DISABLE_STATIC_DESTRUCTORS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-c++-static-destructors")
endif()
# Try using CMake's own logic to locate curses/ncurses
find_package(Curses)
if(NOT ${CURSES_FOUND})
# CMake has trouble finding platform-specific system libraries
# installed to multiarch paths (e.g. /usr/lib/x86_64-linux-gnu)
# if not symlinked or passed in as a manual define.
message("Falling back to pkg-config for (n)curses detection")
include(FindPkgConfig)
pkg_search_module(CURSES REQUIRED ncurses curses)
set(CURSES_CURSES_LIBRARY ${CURSES_LIBRARIES})
set(CURSES_LIBRARY ${CURSES_LIBRARIES})
endif()
# Set up extra include directories for CheckIncludeFile
list(APPEND CMAKE_REQUIRED_INCLUDES ${CURSES_INCLUDE_DIRS})
# Fix undefined reference to tparm on RHEL 6 and potentially others
# If curses is found via CMake, it also links against tinfo if it exists. But if we use our
# fallback pkg-config logic above, we need to do this manually.
find_library(CURSES_TINFO tinfo)
if (CURSES_TINFO)
set(CURSES_LIBRARY ${CURSES_LIBRARY} ${CURSES_TINFO})
endif()
# Get threads.
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
# Detect WSL. Does not match against native Windows/WIN32.
if (CMAKE_HOST_SYSTEM_VERSION MATCHES ".*-Microsoft")
set(WSL 1)
endif()
# Set up the config.h file.
set(PACKAGE_NAME "fish")
set(PACKAGE_TARNAME "fish")
include(CheckCXXSymbolExists)
include(CheckIncludeFileCXX)
include(CheckIncludeFiles)
include(CheckStructHasMember)
include(CheckCXXSourceCompiles)
include(CheckTypeSize)
check_cxx_symbol_exists(backtrace_symbols execinfo.h HAVE_BACKTRACE_SYMBOLS)
# workaround for lousy mtime precision on a Linux kernel
if (CMAKE_SYSTEM_NAME MATCHES "Linux|Android")
check_cxx_symbol_exists(clock_gettime time.h HAVE_CLOCK_GETTIME)
check_cxx_symbol_exists(futimens sys/stat.h HAVE_FUTIMENS)
if ((HAVE_CLOCK_GETTIME) AND (HAVE_FUTIMENS))
set(UVAR_FILE_SET_MTIME_HACK 1)
endif()
endif()
check_cxx_symbol_exists(ctermid_r stdio.h HAVE_CTERMID_R)
check_struct_has_member("struct dirent" d_type dirent.h HAVE_STRUCT_DIRENT_D_TYPE LANGUAGE CXX)
check_cxx_symbol_exists(dirfd "sys/types.h;dirent.h" HAVE_DIRFD)
check_include_file_cxx(execinfo.h HAVE_EXECINFO_H)
check_cxx_symbol_exists(flock sys/file.h HAVE_FLOCK)
check_cxx_symbol_exists(getpwent pwd.h HAVE_GETPWENT)
check_cxx_symbol_exists(getrusage sys/resource.h HAVE_GETRUSAGE)
check_cxx_symbol_exists(gettext libintl.h HAVE_GETTEXT)
check_cxx_symbol_exists(killpg "sys/types.h;signal.h" HAVE_KILLPG)
# mkostemp is in stdlib in glibc and FreeBSD, but unistd on macOS
check_cxx_symbol_exists(mkostemp "stdlib.h;unistd.h" HAVE_MKOSTEMP)
set(HAVE_CURSES_H ${CURSES_HAVE_CURSES_H})
set(HAVE_NCURSES_CURSES_H ${CURSES_HAVE_NCURSES_CURSES_H})
set(HAVE_NCURSES_H ${CURSES_HAVE_NCURSES_H})
if(HAVE_CURSES_H)
check_include_files("curses.h;term.h" HAVE_TERM_H)
endif()
if(NOT HAVE_TERM_H)
check_include_file_cxx("ncurses/term.h" HAVE_NCURSES_TERM_H)
endif()
check_include_file_cxx(siginfo.h HAVE_SIGINFO_H)
check_include_file_cxx(spawn.h HAVE_SPAWN_H)
check_struct_has_member("struct stat" st_ctime_nsec "sys/stat.h" HAVE_STRUCT_STAT_ST_CTIME_NSEC
LANGUAGE CXX)
check_struct_has_member("struct stat" st_mtimespec.tv_nsec "sys/stat.h"
HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC LANGUAGE CXX)
check_struct_has_member("struct stat" st_mtim.tv_nsec "sys/stat.h" HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
LANGUAGE CXX)
check_include_file_cxx(sys/ioctl.h HAVE_SYS_IOCTL_H)
check_include_file_cxx(sys/select.h HAVE_SYS_SELECT_H)
# glibc 2.30 deprecated <sys/sysctl.h> because that's what glibc does.
# Checking for that here rather than hardcoding a check on the glibc
# version in the C++ sources at point of use makes more sense.
SET(OLD_CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
check_include_files("sys/types.h;sys/sysctl.h" HAVE_SYS_SYSCTL_H)
SET(CMAKE_C_FLAGS "${OLD_CMAKE_C_FLAGS}")
check_cxx_symbol_exists(eventfd sys/eventfd.h HAVE_EVENTFD)
check_cxx_symbol_exists(pipe2 unistd.h HAVE_PIPE2)
check_cxx_symbol_exists(wcscasecmp wchar.h HAVE_WCSCASECMP)
check_cxx_symbol_exists(wcsncasecmp wchar.h HAVE_WCSNCASECMP)
# These are for compatibility with Solaris 10, which places the following
# in the std namespace.
if(NOT HAVE_WCSNCASECMP)
check_cxx_symbol_exists(std::wcscasecmp wchar.h HAVE_STD__WCSCASECMP)
check_cxx_symbol_exists(std::wcsncasecmp wchar.h HAVE_STD__WCSNCASECMP)
endif()
# `xlocale.h` is required to find `wcstod_l` in `wchar.h` under FreeBSD,
# but it's not present under Linux.
check_include_files("xlocale.h" HAVE_XLOCALE_H)
if(HAVE_XLOCALE_H)
list(APPEND WCSTOD_L_INCLUDES "xlocale.h")
endif()
list(APPEND WCSTOD_L_INCLUDES "wchar.h")
check_cxx_symbol_exists(wcstod_l "${WCSTOD_L_INCLUDES}" HAVE_WCSTOD_L)
check_cxx_symbol_exists(uselocale "locale.h;xlocale.h" HAVE_USELOCALE)
cmake_push_check_state()
check_struct_has_member("struct winsize" ws_row "termios.h;sys/ioctl.h" _HAVE_WINSIZE)
check_cxx_symbol_exists("TIOCGWINSZ" "termios.h;sys/ioctl.h" HAVE_TIOCGWINSZ)
if(_HAVE_WINSIZE EQUAL 1 AND HAVE_TIOCGWINSZ EQUAL 1)
set(HAVE_WINSIZE 1)
endif()
cmake_pop_check_state()
check_type_size("wchar_t[8]" WCHAR_T_BITS LANGUAGE CXX)
set(TPARM_INCLUDES)
if(HAVE_NCURSES_H)
set(TPARM_INCLUDES "${TPARM_INCLUDES}#include <ncurses.h>\n")
elseif(HAVE_NCURSES_CURSES_H)
set(TPARM_INCLUDES "${TPARM_INCLUDES}#include <ncurses/curses.h>\n")
else()
set(TPARM_INCLUDES "${TPARM_INCLUDES}#include <curses.h>\n")
endif()
if(HAVE_TERM_H)
set(TPARM_INCLUDES "${TPARM_INCLUDES}#include <term.h>\n")
elseif(HAVE_NCURSES_TERM_H)
set(TPARM_INCLUDES "${TPARM_INCLUDES}#include <ncurses/term.h>\n")
endif()
cmake_push_check_state()
list(APPEND CMAKE_REQUIRED_LIBRARIES ${CURSES_LIBRARY})
# Solaris and X/Open-conforming systems have a fixed-args tparm
check_cxx_source_compiles("
#define TPARM_VARARGS
${TPARM_INCLUDES}
int main () {
tparm( \"\" );
}
"
TPARM_TAKES_VARARGS
)
# Check if tputs needs a function reading an int or char.
# The only curses I can find that needs a char is OpenIndiana.
check_cxx_source_compiles("
#include <curses.h>
#include <term.h>
static int writer(int b) {
return b;
}
int main() {
return tputs(\"foo\", 5, writer);
}"
TPUTS_USES_INT_ARG
)
if(TPARM_TAKES_VARARGS)
set(TPARM_VARARGS 1)
else()
set(TPARM_SOLARIS_KLUDGE 1)
endif()
cmake_pop_check_state()
# Work around the fact that cmake does not propagate the language standard flag into
# the CHECK_CXX_SOURCE_COMPILES function. See CMake issue #16456.
# Ensure we do this after the FIND_PACKAGE calls which use C, and will error on a C++
# standards flag.
# Also see https://github.com/fish-shell/fish-shell/issues/5865
if(NOT POLICY CMP0067)
list(APPEND CMAKE_REQUIRED_FLAGS "${CMAKE_CXX${CMAKE_CXX_STANDARD}_EXTENSION_COMPILE_OPTION}")
endif()
check_cxx_source_compiles("
#include <memory>
int main () {
std::unique_ptr<int> foo = std::make_unique<int>();
}
"
HAVE_STD__MAKE_UNIQUE
)
# Detect support for thread_local.
check_cxx_source_compiles("
int main () {
static thread_local int x = 3;
(void)x;
}
"
HAVE_CX11_THREAD_LOCAL
)
check_cxx_source_compiles("
#include <atomic>
#include <cstdint>
std::atomic<uint8_t> n8 (0);
std::atomic<uint64_t> n64 (0);
int main() {
uint8_t i = n8.load(std::memory_order_relaxed);
uint64_t j = n64.load(std::memory_order_relaxed);
return std::atomic_is_lock_free(&n8)
& std::atomic_is_lock_free(&n64);
}"
LIBATOMIC_NOT_NEEDED)
IF (NOT LIBATOMIC_NOT_NEEDED)
set(ATOMIC_LIBRARY "atomic")
endif()
check_cxx_source_compiles("
#include <sys/wait.h>
int main() {
static_assert(WEXITSTATUS(0x007f) == 0x7f, \"This is our message we need to add because C++ is terrible\");
return 0;
}
"
HAVE_WAITSTATUS_SIGNAL_RET)
IF (APPLE)
# Check if mbrtowc implementation attempts to encode invalid UTF-8 sequences
# Known culprits: at least some versions of macOS (confirmed Snow Leopard and Yosemite)
try_run(mbrtowc_invalid_utf8_exit mbrtowc_invalid_utf8_compiles ${CMAKE_CURRENT_BINARY_DIR}
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/checks/mbrtowc_invalid_utf8.cpp")
IF ("${mbrtowc_invalid_utf8_compiles}" AND ("${mbrtowc_invalid_utf8_exit}" EQUAL 1))
SET(HAVE_BROKEN_MBRTOWC_UTF8 1)
ENDIF()
ENDIF()

View File

@@ -1,86 +0,0 @@
find_program(SPHINX_EXECUTABLE NAMES sphinx-build
HINTS
$ENV{SPHINX_DIR}
PATH_SUFFIXES bin
DOC "Sphinx documentation generator")
include(FeatureSummary)
set(SPHINX_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/doc_src")
set(SPHINX_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/user_doc")
set(SPHINX_BUILD_DIR "${SPHINX_ROOT_DIR}/build")
set(SPHINX_HTML_DIR "${SPHINX_ROOT_DIR}/html")
set(SPHINX_MANPAGE_DIR "${SPHINX_ROOT_DIR}/man")
# sphinx-docs uses fish_indent for highlighting.
# Prepend the output dir of fish_indent to PATH.
add_custom_target(sphinx-docs
mkdir -p ${SPHINX_HTML_DIR}/_static/
COMMAND env PATH="$<TARGET_FILE_DIR:fish_indent>:$$PATH"
${SPHINX_EXECUTABLE}
-j auto
-q -b html
-c "${SPHINX_SRC_DIR}"
-d "${SPHINX_ROOT_DIR}/.doctrees-html"
"${SPHINX_SRC_DIR}"
"${SPHINX_HTML_DIR}"
DEPENDS ${SPHINX_SRC_DIR}/fish_indent_lexer.py fish_indent
COMMENT "Building HTML documentation with Sphinx")
# sphinx-manpages needs the fish_indent binary for the version number
add_custom_target(sphinx-manpages
env PATH="$<TARGET_FILE_DIR:fish_indent>:$$PATH"
${SPHINX_EXECUTABLE}
-j auto
-q -b man
-c "${SPHINX_SRC_DIR}"
-d "${SPHINX_ROOT_DIR}/.doctrees-man"
"${SPHINX_SRC_DIR}"
# TODO: This only works if we only have section 1 manpages.
"${SPHINX_MANPAGE_DIR}/man1"
DEPENDS fish_indent
COMMENT "Building man pages with Sphinx")
if(SPHINX_EXECUTABLE)
option(BUILD_DOCS "build documentation (requires Sphinx)" ON)
else(SPHINX_EXECUTABLE)
option(BUILD_DOCS "build documentation (requires Sphinx)" OFF)
endif(SPHINX_EXECUTABLE)
if(BUILD_DOCS AND NOT SPHINX_EXECUTABLE)
message(FATAL_ERROR "build documentation selected, but sphinx-build could not be found")
endif()
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/user_doc/html
AND IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/user_doc/man)
set(HAVE_PREBUILT_DOCS TRUE)
else()
set(HAVE_PREBUILT_DOCS FALSE)
endif()
if(BUILD_DOCS OR HAVE_PREBUILT_DOCS)
set(INSTALL_DOCS ON)
else()
set(INSTALL_DOCS OFF)
endif()
add_feature_info(Documentation INSTALL_DOCS "user manual and documentation")
if(BUILD_DOCS)
configure_file("${SPHINX_SRC_DIR}/conf.py" "${SPHINX_BUILD_DIR}/conf.py" @ONLY)
add_custom_target(doc ALL
DEPENDS sphinx-docs sphinx-manpages)
# Group docs targets into a DocsTargets folder
set_property(TARGET doc sphinx-docs sphinx-manpages
PROPERTY FOLDER cmake/DocTargets)
elseif(HAVE_PREBUILT_DOCS)
if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
# Out of tree build - link the prebuilt documentation to the build tree
add_custom_target(link_doc ALL)
add_custom_command(TARGET link_doc
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/user_doc ${CMAKE_CURRENT_BINARY_DIR}/user_doc
POST_BUILD)
endif()
endif(BUILD_DOCS)

View File

@@ -1,185 +0,0 @@
# -DLOCALEDIR="${CMAKE_INSTALL_FULL_LOCALEDIR}"
# -DPREFIX=L"${CMAKE_INSTALL_PREFIX}"
# -DDATADIR=L"${CMAKE_INSTALL_FULL_DATADIR}"
# -DSYSCONFDIR=L"${CMAKE_INSTALL_FULL_SYSCONFDIR}"
# -DBINDIR=L"${CMAKE_INSTALL_FULL_BINDIR}"
# -DDOCDIR=L"${CMAKE_INSTALL_FULL_DOCDIR}")
set(CMAKE_INSTALL_MESSAGE NEVER)
set(PROGRAMS fish fish_indent fish_key_reader)
set(prefix ${CMAKE_INSTALL_PREFIX})
set(bindir ${CMAKE_INSTALL_BINDIR})
set(sysconfdir ${CMAKE_INSTALL_SYSCONFDIR})
set(mandir ${CMAKE_INSTALL_MANDIR})
set(datadir ${CMAKE_INSTALL_FULL_DATADIR})
file(RELATIVE_PATH rel_datadir ${CMAKE_INSTALL_PREFIX} ${datadir})
set(docdir ${CMAKE_INSTALL_DOCDIR})
# Comment at the top of some .in files
set(configure_input
"This file was generated from a corresponding .in file.\
DO NOT MANUALLY EDIT THIS FILE!")
set(rel_completionsdir "fish/vendor_completions.d")
set(rel_functionsdir "fish/vendor_functions.d")
set(rel_confdir "fish/vendor_conf.d")
set(extra_completionsdir
"${datadir}/${rel_completionsdir}"
CACHE STRING "Path for extra completions")
set(extra_functionsdir
"${datadir}/${rel_functionsdir}"
CACHE STRING "Path for extra functions")
set(extra_confdir
"${datadir}/${rel_confdir}"
CACHE STRING "Path for extra configuration")
# These are the man pages that go in system manpath; all manpages go in the fish-specific manpath.
set(MANUALS ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish_indent.1
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish_key_reader.1)
# Determine which man page we don't want to install.
# On OS X, don't install a man page for open, since we defeat fish's open
# function on OS X.
# On other operating systems, don't install a realpath man page, as they almost all have a realpath
# command, while macOS does not.
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(CONDEMNED_PAGE "open.1")
else()
set(CONDEMNED_PAGE "realpath.1")
endif()
# Define a function to help us create directories.
function(FISH_CREATE_DIRS)
foreach(dir ${ARGV})
install(DIRECTORY DESTINATION ${dir})
endforeach(dir)
endfunction(FISH_CREATE_DIRS)
function(FISH_TRY_CREATE_DIRS)
foreach(dir ${ARGV})
if(NOT IS_ABSOLUTE ${dir})
set(abs_dir "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${dir}")
else()
set(abs_dir "\$ENV{DESTDIR}${dir}")
endif()
install(SCRIPT CODE "EXECUTE_PROCESS(COMMAND mkdir -p ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
execute_process(COMMAND chmod 755 ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
")
endforeach()
endfunction(FISH_TRY_CREATE_DIRS)
install(TARGETS ${PROGRAMS}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ
GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
DESTINATION ${bindir})
fish_create_dirs(${sysconfdir}/fish/conf.d ${sysconfdir}/fish/completions
${sysconfdir}/fish/functions)
install(FILES etc/config.fish DESTINATION ${sysconfdir}/fish/)
fish_create_dirs(${rel_datadir}/fish ${rel_datadir}/fish/completions
${rel_datadir}/fish/functions ${rel_datadir}/fish/groff
${rel_datadir}/fish/man/man1 ${rel_datadir}/fish/tools
${rel_datadir}/fish/tools/web_config
${rel_datadir}/fish/tools/web_config/js
${rel_datadir}/fish/tools/web_config/partials
${rel_datadir}/fish/tools/web_config/sample_prompts
${rel_datadir}/fish/tools/web_config/themes
)
configure_file(share/__fish_build_paths.fish.in share/__fish_build_paths.fish)
install(FILES share/config.fish
${CMAKE_CURRENT_BINARY_DIR}/share/__fish_build_paths.fish
DESTINATION ${rel_datadir}/fish)
# Create only the vendor directories inside the prefix (#5029 / #6508)
fish_create_dirs(${rel_datadir}/fish/vendor_completions.d ${rel_datadir}/fish/vendor_functions.d
${rel_datadir}/fish/vendor_conf.d)
fish_try_create_dirs(${rel_datadir}/pkgconfig)
configure_file(fish.pc.in fish.pc.noversion @ONLY)
add_custom_command(OUTPUT fish.pc
COMMAND sed '/Version/d' fish.pc.noversion > fish.pc
COMMAND printf "Version: " >> fish.pc
COMMAND sed 's/FISH_BUILD_VERSION=//\;s/\"//g' ${FBVF} >> fish.pc
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS CHECK-FISH-BUILD-VERSION-FILE ${CMAKE_CURRENT_BINARY_DIR}/fish.pc.noversion)
add_custom_target(build_fish_pc ALL DEPENDS fish.pc)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/fish.pc
DESTINATION ${rel_datadir}/pkgconfig)
install(DIRECTORY share/completions/
DESTINATION ${rel_datadir}/fish/completions
FILES_MATCHING PATTERN "*.fish")
install(DIRECTORY share/functions/
DESTINATION ${rel_datadir}/fish/functions
FILES_MATCHING PATTERN "*.fish")
install(DIRECTORY share/groff
DESTINATION ${rel_datadir}/fish)
# CONDEMNED_PAGE is managed by the conditional above
# Building the man pages is optional: if sphinx isn't installed, they're not built
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/
DESTINATION ${rel_datadir}/fish/man/man1
FILES_MATCHING
PATTERN "*.1"
PATTERN ${CONDEMNED_PAGE} EXCLUDE)
install(PROGRAMS share/tools/create_manpage_completions.py share/tools/deroff.py
DESTINATION ${rel_datadir}/fish/tools/)
install(DIRECTORY share/tools/web_config
DESTINATION ${rel_datadir}/fish/tools/
FILES_MATCHING
PATTERN "*.png"
PATTERN "*.css"
PATTERN "*.html"
PATTERN "*.py"
PATTERN "*.js"
PATTERN "*.theme"
PATTERN "*.fish")
# Building the man pages is optional: if Sphinx isn't installed, they're not built
install(FILES ${MANUALS} DESTINATION ${mandir}/man1/ OPTIONAL)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/user_doc/html/ # Trailing slash is important!
DESTINATION ${docdir} OPTIONAL)
install(FILES CHANGELOG.rst DESTINATION ${docdir})
# These files are built by cmake/gettext.cmake, but using GETTEXT_PROCESS_PO_FILES's
# INSTALL_DESTINATION leads to them being installed as ${lang}.gmo, not fish.mo
# The ${languages} array comes from cmake/gettext.cmake
if(GETTEXT_FOUND)
foreach(lang ${languages})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${lang}.gmo DESTINATION
${CMAKE_INSTALL_LOCALEDIR}/${lang}/LC_MESSAGES/ RENAME fish.mo)
endforeach()
endif()
if (NOT APPLE)
install(FILES fish.desktop DESTINATION ${rel_datadir}/applications)
install(FILES ${SPHINX_SRC_DIR}/python_docs_theme/static/fish.png DESTINATION ${rel_datadir}/pixmaps)
endif()
# Group install targets into a InstallTargets folder
set_property(TARGET build_fish_pc CHECK-FISH-BUILD-VERSION-FILE
tests_buildroot_target
PROPERTY FOLDER cmake/InstallTargets)
# Make a target build_root that installs into the buildroot directory, for testing.
set(BUILDROOT_DIR ${CMAKE_CURRENT_BINARY_DIR}/buildroot)
add_custom_target(build_root
COMMAND DESTDIR=${BUILDROOT_DIR} ${CMAKE_COMMAND}
--build ${CMAKE_CURRENT_BINARY_DIR} --target install)

View File

@@ -1,44 +0,0 @@
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.10" CACHE STRING "Minimum OS X deployment version")
# Code signing ID on Mac.
# If this is falsey, codesigning is disabled.
# '-' is ad-hoc codesign.
set(MAC_CODESIGN_ID "" CACHE STRING "Mac code-signing identity")
# Whether to inject the "get-task-allow" entitlement, which permits debugging
# on the Mac.
set(MAC_INJECT_GET_TASK_ALLOW ON CACHE BOOL "Inject get-task-allow on Mac")
# When building a Mac build, it is common for fish to link against a
# pcre2 built for the host platform (e.g. macOS 10.15) while fish wants
# to link for macOS 10.9. This warning would be of interest for releases,
# but is just noise for daily development. Unfortunately it has no flag
# of its own, so suppress all linker warnings in debug builds.
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -w")
function(CODESIGN_ON_MAC target)
if((APPLE) AND (MAC_CODESIGN_ID))
execute_process(COMMAND sw_vers "-productVersion" OUTPUT_VARIABLE OSX_VERSION)
if(MAC_INJECT_GET_TASK_ALLOW)
set(ENTITLEMENTS "--entitlements" "${CMAKE_SOURCE_DIR}/osx/fish_debug.entitlements")
else()
set(ENTITLEMENTS "")
endif(MAC_INJECT_GET_TASK_ALLOW)
if(OSX_VERSION VERSION_LESS "10.13.6")
# `-options runtime` is only available in OS X from 10.13.6 and up
add_custom_command(
TARGET ${target}
POST_BUILD
COMMAND codesign --force --deep ${ENTITLEMENTS} --sign "${MAC_CODESIGN_ID}" $<TARGET_FILE:${target}>
VERBATIM
)
else()
add_custom_command(
TARGET ${target}
POST_BUILD
COMMAND codesign --force --deep --options runtime ${ENTITLEMENTS} --sign "${MAC_CODESIGN_ID}" $<TARGET_FILE:${target}>
VERBATIM
)
endif()
endif()
endfunction(CODESIGN_ON_MAC target)

View File

@@ -1,78 +0,0 @@
# This is Mac-only.
if (NOT APPLE)
return()
endif (NOT APPLE)
# The source tree containing certain macOS resources.
set(OSX_DIR ${CMAKE_CURRENT_SOURCE_DIR}/osx)
set(RESOURCE_FILES
${OSX_DIR}/launch_fish.scpt
${OSX_DIR}/fish_term_icon.icns
${CMAKE_CURRENT_SOURCE_DIR}/build_tools/osx_package_scripts/add-shell
${OSX_DIR}/install.sh
)
# Resource files must be present in the source list.
add_executable(fish_macapp EXCLUDE_FROM_ALL
${OSX_DIR}/osx_fish_launcher.m
${RESOURCE_FILES}
)
# Compute the version. Note this is done at generation time, not build time,
# so cmake must be re-run after version changes for the app to be updated. But
# generally this will be run by make_pkg.sh which always re-runs cmake.
execute_process(
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh --stdout
COMMAND cut -d- -f1
OUTPUT_VARIABLE FISH_SHORT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE)
# Note CMake appends .app, so the real output name will be fish.app.
# This target does not include the 'base' resource.
set_target_properties(fish_macapp PROPERTIES OUTPUT_NAME "fish")
find_library(FOUNDATION_LIB Foundation)
target_link_libraries(fish_macapp ${FOUNDATION_LIB})
set_target_properties(fish_macapp PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST ${OSX_DIR}/CMakeMacAppInfo.plist.in
MACOSX_BUNDLE_GUI_IDENTIFIER "com.ridiculousfish.fish-shell"
MACOSX_BUNDLE_SHORT_VERSION_STRING ${FISH_SHORT_VERSION}
RESOURCE "${RESOURCE_FILES}"
)
# The fish Mac app contains a fish installation inside the package.
# Here is where it gets built.
# Copy into the fish mac app after.
set(MACAPP_FISH_BUILDROOT ${CMAKE_CURRENT_BINARY_DIR}/macapp_buildroot/base)
add_custom_command(TARGET fish_macapp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${MACAPP_FISH_BUILDROOT}
COMMAND DESTDIR=${MACAPP_FISH_BUILDROOT} ${CMAKE_COMMAND}
--build ${CMAKE_CURRENT_BINARY_DIR} --target install
COMMAND ${CMAKE_COMMAND} -E copy_directory ${MACAPP_FISH_BUILDROOT}/..
$<TARGET_BUNDLE_CONTENT_DIR:fish_macapp>/Resources/
VERBATIM
)
# The entitlements file.
set(MACAPP_ENTITLEMENTS "${CMAKE_SOURCE_DIR}/osx/MacApp.entitlements")
# Target to sign the macapp.
# Note that a POST_BUILD step happens before resources are copied,
# and therefore would be too early.
add_custom_target(signed_fish_macapp
DEPENDS fish_macapp "${MACAPP_ENTITLEMENTS}"
COMMAND codesign --force --deep
--options runtime
--entitlements "${MACAPP_ENTITLEMENTS}"
--sign "${MAC_CODESIGN_ID}"
$<TARGET_BUNDLE_DIR:fish_macapp>
VERBATIM
)
# Group our targets in a folder.
set_property(TARGET fish_macapp signed_fish_macapp PROPERTY FOLDER macapp)

View File

@@ -1,77 +0,0 @@
# PCRE2 needs some settings.
set(PCRE2_WIDTH ${WCHAR_T_BITS})
set(PCRE2_BUILD_PCRE2_8 OFF CACHE BOOL "Build 8bit PCRE2 library")
set(PCRE2_BUILD_PCRE2_${PCRE2_WIDTH} ON CACHE BOOL "Build ${PCRE2_WIDTH}bit PCRE2 library")
set(PCRE2_SHOW_REPORT OFF CACHE BOOL "Show the final configuration report")
set(PCRE2_BUILD_TESTS OFF CACHE BOOL "Build tests")
set(PCRE2_BUILD_PCRE2GREP OFF CACHE BOOL "Build pcre2grep")
set(PCRE2_MIN_VERSION 10.21)
# Look for a system-installed PCRE2.
find_library(SYS_PCRE2_LIB pcre2-${PCRE2_WIDTH})
find_path(SYS_PCRE2_INCLUDE_DIR pcre2.h)
# We can either use the system-installed PCRE or our bundled version.
# This is controlled by the cache variable FISH_USE_SYSTEM_PCRE2.
# Here we compute the default value for that variable.
if ((APPLE) AND (MAC_CODESIGN_ID))
# On Mac, a codesigned fish will refuse to load a non-codesigned PCRE2
# (e.g. from Homebrew) so default to bundled PCRE2.
set(USE_SYS_PCRE2_DEFAULT OFF)
elseif((NOT SYS_PCRE2_LIB) OR (NOT SYS_PCRE2_INCLUDE_DIR))
# We did not find system PCRE2, so default to bundled.
set(USE_SYS_PCRE2_DEFAULT OFF)
else()
# Default to using the system PCRE2, which was found.
set(USE_SYS_PCRE2_DEFAULT ON)
endif()
set(FISH_USE_SYSTEM_PCRE2 ${USE_SYS_PCRE2_DEFAULT} CACHE BOOL
"Use PCRE2 from the system, instead of fetching and building it")
if(FISH_USE_SYSTEM_PCRE2)
set(PCRE2_LIB "${SYS_PCRE2_LIB}")
set(PCRE2_INCLUDE_DIR "${SYS_PCRE2_INCLUDE_DIR}")
message(STATUS "Using system PCRE2 library ${PCRE2_INCLUDE_DIR}")
else()
include(FetchContent RESULT_VARIABLE HAVE_FetchContent)
if (${HAVE_FetchContent} STREQUAL "NOTFOUND")
message(FATAL_ERROR "Please install PCRE2 headers, or CMake >= 3.11 so I can download PCRE")
endif()
set(CMAKE_TLS_VERIFY true)
set(PCRE2_REPO "https://github.com/PCRE2Project/pcre2.git")
message(STATUS "Fetching and configuring PCRE2 from ${PCRE2_REPO}")
Set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(
pcre2
GIT_REPOSITORY ${PCRE2_REPO}
GIT_TAG "72669190cb947f0cac1d038a8bb1820da59ef447" # tag: pcre2-10.36
GIT_SHALLOW ON
GIT_PROGRESS TRUE
)
# Don't try FetchContent_MakeAvailable, there's no way to add EXCLUDE_FROM_ALL
# so we end up installing all of PCRE2 including its headers, man pages, etc.
FetchContent_GetProperties(pcre2)
if (NOT pcre2_POPULATED)
# If GIT_WORK_TREE is set (by user or by git itself with e.g. git rebase), it
# will override the git directory which CMake tries to apply in FetchContent_Populate,
# resulting in a failed checkout.
# Ensure it is not set.
unset(ENV{GIT_WORK_TREE})
unset(ENV{GIT_DIR})
FetchContent_Populate(pcre2)
add_subdirectory(${pcre2_SOURCE_DIR} ${pcre2_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
set(PCRE2_INCLUDE_DIR ${pcre2_BINARY_DIR})
set(PCRE2_LIB pcre2-${PCRE2_WIDTH})
# Disable -Wunused-macros inside PCRE2, as it is noisy.
get_target_property(PCRE2_COMPILE_OPTIONS ${PCRE2_LIB} COMPILE_OPTIONS)
list(REMOVE_ITEM PCRE2_COMPILE_OPTIONS "-Wunused-macros")
set_property(TARGET ${PCRE2_LIB} PROPERTY COMPILE_OPTIONS ${PCRE2_COMPILE_OPTIONS})
endif(FISH_USE_SYSTEM_PCRE2)
include_directories(${PCRE2_INCLUDE_DIR})

View File

@@ -1,177 +0,0 @@
# This adds ctest support to the project
enable_testing()
# By default, ctest runs tests serially
if(NOT CTEST_PARALLEL_LEVEL)
include(ProcessorCount)
ProcessorCount(CORES)
set(CTEST_PARALLEL_LEVEL ${CORES})
endif()
# Put in a tests folder to reduce the top level targets in IDEs.
set(CMAKE_FOLDER tests)
# We will use 125 as a reserved exit code to indicate that a test has been skipped, i.e. it did not
# pass but it should not be considered a failed test run, either.
set(SKIP_RETURN_CODE 125)
# Even though we are using CMake's ctest for testing, we still define our own `make test` target
# rather than use its default for many reasons:
# * CMake doesn't run tests in-proc or even add each tests as an individual node in the ninja
# dependency tree, instead it just bundles all tests into a target called `test` that always just
# shells out to `ctest`, so there are no build-related benefits to not doing that ourselves.
# * CMake devs insist that it is appropriate for `make test` to never depend on `make all`, i.e.
# running `make test` does not require any of the binaries to be built before testing.
# * The only way to have a test depend on a binary is to add a fake test with a name like
# "build_fish" that executes CMake recursively to build the `fish` target.
# * It is not possible to set top-level CTest options/settings such as CTEST_PARALLEL_LEVEL from
# within the CMake configuration file.
# * Circling back to the point about individual tests not being actual Makefile targets, CMake does
# not offer any way to execute a named test via the `make`/`ninja`/whatever interface; the only
# way to manually invoke test `foo` is to to manually run `ctest` and specify a regex matching
# `foo` as an argument, e.g. `ctest -R ^foo$`... which is really crazy.
# The top-level test target is "fish_run_tests".
add_custom_target(fish_run_tests
COMMAND env CTEST_PARALLEL_LEVEL=${CTEST_PARALLEL_LEVEL} FISH_FORCE_COLOR=1
FISH_SOURCE_DIR=${CMAKE_SOURCE_DIR}
${CMAKE_CTEST_COMMAND} --force-new-ctest-process # --verbose
--output-on-failure --progress
DEPENDS fish_tests tests_buildroot_target
USES_TERMINAL
)
# If CMP0037 is available, also make an alias "test" target.
# Note that this policy may not be available, in which case definining such a target silently fails.
cmake_policy(PUSH)
if(POLICY CMP0037)
cmake_policy(SET CMP0037 OLD)
add_custom_target(test DEPENDS fish_run_tests)
endif()
cmake_policy(POP)
# Build the low-level tests code
add_executable(fish_tests EXCLUDE_FROM_ALL
src/fish_tests.cpp)
fish_link_deps_and_sign(fish_tests)
# The "test" directory.
set(TEST_DIR ${CMAKE_CURRENT_BINARY_DIR}/test)
# CMake doesn't really support dynamic test discovery where a test harness is executed to list the
# tests it contains, making fish_tests.cpp's tests opaque to CMake (whereas littlecheck tests can be
# enumerated from the filesystem). We used to compile fish_tests.cpp without linking against
# anything (-Wl,-undefined,dynamic_lookup,--unresolved-symbols=ignore-all) to get it to print its
# tests at configuration time, but that's a little too much dark CMake magic.
#
# We now identify tests by checking against a magic regex that's #define'd as a no-op C-side.
file(READ "${CMAKE_SOURCE_DIR}/src/fish_tests.cpp" FISH_TESTS_CPP)
string(REGEX MATCHALL "TEST_GROUP\\( *\"([^\"]+)\"" "LOW_LEVEL_TESTS" "${FISH_TESTS_CPP}")
string(REGEX REPLACE "TEST_GROUP\\( *\"([^\"]+)\"" "\\1" "LOW_LEVEL_TESTS" "${LOW_LEVEL_TESTS}")
list(REMOVE_DUPLICATES LOW_LEVEL_TESTS)
# The directory into which fish is installed.
set(TEST_INSTALL_DIR ${TEST_DIR}/buildroot)
# The directory where the tests expect to find the fish root (./bin, etc)
set(TEST_ROOT_DIR ${TEST_DIR}/root)
# Copy needed directories for out-of-tree builds
if(NOT FISH_IN_TREE_BUILD)
add_custom_target(funcs_dir)
add_custom_command(TARGET funcs_dir
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/share
# Don't run ln twice or it will create a new link in the link.
COMMAND test -e ${CMAKE_BINARY_DIR}/share/functions || ln -sf
${CMAKE_SOURCE_DIR}/share/functions/ ${CMAKE_BINARY_DIR}/share/functions
COMMENT "Symlinking fish functions to binary dir"
VERBATIM)
add_custom_target(tests_dir DEPENDS tests)
add_custom_command(TARGET tests_dir
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/tests/ ${CMAKE_BINARY_DIR}/tests/
COMMENT "Copying test files to binary dir"
VERBATIM)
add_dependencies(fish_tests tests_dir funcs_dir)
endif()
# Copy littlecheck.py
configure_file(build_tools/littlecheck.py littlecheck.py COPYONLY)
# Copy pexpect_helper.py
configure_file(build_tools/pexpect_helper.py pexpect_helper.py COPYONLY)
# Suppress generating Xcode schemes for all tests, there's too many.
set(CMAKE_XCODE_GENERATE_SCHEME 0)
# CMake being CMake, you can't just add a DEPENDS argument to add_test to make it depend on any of
# your binaries actually being built before `make test` is executed (requiring `make all` first),
# and the only dependency a test can have is on another test. So we make building fish and
# `fish_tests` prerequisites to our entire top-level `test` target.
function(add_test_target NAME)
string(REPLACE "/" "-" NAME ${NAME})
add_custom_target("test_${NAME}" COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -R "^${NAME}$$"
DEPENDS fish_tests tests_buildroot_target USES_TERMINAL )
endfunction()
add_custom_target(tests_buildroot_target
# Make the directory in which to run tests:
COMMAND ${CMAKE_COMMAND} -E make_directory ${TEST_INSTALL_DIR}
COMMAND env DESTDIR=${TEST_INSTALL_DIR} ${CMAKE_COMMAND}
--build ${CMAKE_CURRENT_BINARY_DIR} --target install
# Put fish_test_helper there too:
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/fish_test_helper
${TEST_INSTALL_DIR}/${CMAKE_INSTALL_PREFIX}/bin
# Also symlink fish to where the tests expect it to be:
COMMAND ${CMAKE_COMMAND} -E create_symlink
${TEST_INSTALL_DIR}/${CMAKE_INSTALL_PREFIX}
${TEST_ROOT_DIR}
DEPENDS fish fish_test_helper)
# CMake less than 3.9.0 "fully supports" setting an exit code to denote a skipped test, but then
# it just goes ahead and reports it as failed. Really?
if(${CMAKE_VERSION} VERSION_LESS "3.9.0")
set(CMAKE_SKIPPED_HACK "env" "CMAKE_SKIPPED_HACK=1")
else()
set(CMAKE_SKIPPED_HACK)
endif()
foreach(LTEST ${LOW_LEVEL_TESTS})
add_test(
NAME ${LTEST}
COMMAND sh ${CMAKE_CURRENT_BINARY_DIR}/tests/test_env.sh
${CMAKE_BINARY_DIR}/fish_tests ${LTEST}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
set_tests_properties(${LTEST} PROPERTIES SKIP_RETURN_CODE ${SKIP_RETURN_CODE})
add_test_target("${LTEST}")
endforeach(LTEST)
FILE(GLOB FISH_CHECKS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/checks/*.fish)
foreach(CHECK ${FISH_CHECKS})
get_filename_component(CHECK_NAME ${CHECK} NAME)
get_filename_component(CHECK ${CHECK} NAME_WE)
add_test(NAME ${CHECK_NAME}
COMMAND ${CMAKE_SKIPPED_HACK} sh ${CMAKE_CURRENT_BINARY_DIR}/tests/test_driver.sh
${CMAKE_CURRENT_BINARY_DIR}/tests/test.fish ${CHECK}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests
)
set_tests_properties(${CHECK_NAME} PROPERTIES SKIP_RETURN_CODE ${SKIP_RETURN_CODE})
set_tests_properties(${CHECK_NAME} PROPERTIES ENVIRONMENT FISH_FORCE_COLOR=1)
add_test_target("${CHECK_NAME}")
endforeach(CHECK)
FILE(GLOB PEXPECTS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/pexpects/*.py)
foreach(PEXPECT ${PEXPECTS})
get_filename_component(PEXPECT ${PEXPECT} NAME)
add_test(NAME ${PEXPECT}
COMMAND ${CMAKE_SKIPPED_HACK} sh ${CMAKE_CURRENT_BINARY_DIR}/tests/test_driver.sh
${CMAKE_CURRENT_BINARY_DIR}/tests/interactive.fish ${PEXPECT}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests
)
set_tests_properties(${PEXPECT} PROPERTIES SKIP_RETURN_CODE ${SKIP_RETURN_CODE})
set_tests_properties(${PEXPECT} PROPERTIES ENVIRONMENT FISH_FORCE_COLOR=1)
add_test_target("${PEXPECT}")
endforeach(PEXPECT)

View File

@@ -1,55 +0,0 @@
# This file adds commands to manage the FISH-BUILD-VERSION-FILE (hereafter
# FBVF). This file exists in the build directory and is used to populate the
# documentation and also the version string in fish_version.o (printed with
# `echo $version` and also fish --version). The essential idea is that we are
# going to invoke git_version_gen.sh, which will update the
# FISH-BUILD-VERSION-FILE only if it needs to change; this is what makes
# incremental rebuilds fast.
#
# This code is delicate, with the chief subtlety revolving around Ninja. A
# natural and naive approach would tell the generated build system that FBVF is
# a dependency of fish_version.o, and that git_version_gen.sh updates it. Make
# will then invoke the script, check the timestamp on fish_version.o and FBVF,
# see that FBVF is earlier, and then not rebuild fish_version.o. Ninja,
# however, decides what to build up-front and will unconditionally rebuild
# fish_version.o.
#
# To avoid this with Ninja, we want to hook into its 'restat' option which we
# can do through the BYPRODUCTS feature of CMake. See
# https://cmake.org/cmake/help/latest/policy/CMP0058.html
#
# Unfortunately BYPRODUCTS behaves strangely with the Makefile generator: it
# marks FBVF as generated and then CMake itself will `touch` it on every build,
# meaning that using BYPRODUCTS will cause fish_version.o to be rebuilt
# unconditionally with the Makefile generator. Thus we want to use the
# natural-and-naive approach for Makefiles.
# **IMPORTANT** If you touch these build rules, please test both Ninja and
# Makefile generators with both a clean and dirty git tree. Verify that both
# generated build systems rebuild fish when the git tree goes from dirty to
# clean (and vice versa), and verify they do NOT rebuild it when the git tree
# stays the same (incremental builds must be fast).
# Just a handy abbreviation.
set(FBVF FISH-BUILD-VERSION-FILE)
# TODO: find a cleaner way to do this.
IF (${CMAKE_GENERATOR} STREQUAL Ninja)
set(FBVF-OUTPUT fish-build-version-witness.txt)
set(CFBVF-BYPRODUCTS ${FBVF})
else(${CMAKE_GENERATOR} STREQUAL Ninja)
set(FBVF-OUTPUT ${FBVF})
set(CFBVF-BYPRODUCTS)
endif(${CMAKE_GENERATOR} STREQUAL Ninja)
# Set up the version targets
add_custom_target(CHECK-FISH-BUILD-VERSION-FILE
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh ${CMAKE_CURRENT_BINARY_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
BYPRODUCTS ${CFBVF-BYPRODUCTS})
add_custom_command(OUTPUT ${FBVF-OUTPUT}
DEPENDS CHECK-FISH-BUILD-VERSION-FILE)
# Abbreviation for the target.
set(CFBVF CHECK-FISH-BUILD-VERSION-FILE)

View File

@@ -1,18 +0,0 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cwchar>
// Check whether the runtime mbrtowc implementation attempts to encode
// invalid UTF-8 values.
int main() {
// TODO: I'm not sure how to enforce a UTF-8 locale without overriding the language
char sample[] = "hello world";
sample[0] |= 0xF8;
wchar_t wsample[100] {};
std::mbstate_t state = std::mbstate_t();
int res = std::mbrtowc(wsample, sample, strlen(sample), &state);
return res < 0 ? 0 : 1;
}

View File

@@ -1,56 +0,0 @@
set(languages de en fr pl pt_BR sv zh_CN)
include(FeatureSummary)
option(WITH_GETTEXT "translate messages if gettext is available" ON)
if(WITH_GETTEXT)
if(APPLE)
# Fix for https://github.com/fish-shell/fish-shell/issues/5244
# via https://gitlab.kitware.com/cmake/cmake/-/issues/18921
set(CMAKE_FIND_FRAMEWORK_OLD ${CMAKE_FIND_FRAMEWORK})
set(CMAKE_FIND_FRAMEWORK NEVER)
endif()
find_package(Intl QUIET)
find_package(Gettext)
if(GETTEXT_FOUND)
set(HAVE_GETTEXT 1)
include_directories(${Intl_INCLUDE_DIR})
endif()
if(APPLE)
set(CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK_OLD})
unset(CMAKE_FIND_FRAMEWORK_OLD)
endif()
endif()
add_feature_info(gettext GETTEXT_FOUND "translate messages with gettext")
# Define translations
if(GETTEXT_FOUND)
# Group pofile targets into their own folder, as there's a lot of them.
set(CMAKE_FOLDER pofiles)
foreach(lang ${languages})
# Our translations aren't set up entirely as CMake expects, so installation is done in
# cmake/Install.cmake instead of using INSTALL_DESTINATION
gettext_process_po_files(${lang} ALL
PO_FILES po/${lang}.po)
endforeach()
set(CMAKE_FOLDER)
endif()
cmake_push_check_state()
set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${Intl_INCLUDE_DIR})
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${Intl_LIBRARIES})
# libintl.h can be compiled into the stdlib on some GLibC systems
if(Intl_FOUND AND Intl_LIBRARIES)
set(LIBINTL_INCLUDE "#include <libintl.h>")
endif()
check_cxx_source_compiles("
${LIBINTL_INCLUDE}
#include <stdlib.h>
int main () {
extern int _nl_msg_cat_cntr;
int tmp = _nl_msg_cat_cntr;
exit(tmp);
}
"
HAVE__NL_MSG_CAT_CNTR)
cmake_pop_check_state()

365
color.cpp Normal file
View File

@@ -0,0 +1,365 @@
/** \file color.cpp Color class implementation
*/
#include "color.h"
#include "fallback.h"
bool rgb_color_t::try_parse_special(const wcstring &special)
{
bzero(&data, sizeof data);
const wchar_t *name = special.c_str();
if (! wcscasecmp(name, L"normal"))
{
this->type = type_normal;
}
else if (! wcscasecmp(name, L"reset"))
{
this->type = type_reset;
}
else if (! wcscasecmp(name, L"ignore"))
{
this->type = type_ignore;
}
else
{
this->type = type_none;
}
return this->type != type_none;
}
static int parse_hex_digit(wchar_t x)
{
switch (x)
{
case L'0':
return 0x0;
case L'1':
return 0x1;
case L'2':
return 0x2;
case L'3':
return 0x3;
case L'4':
return 0x4;
case L'5':
return 0x5;
case L'6':
return 0x6;
case L'7':
return 0x7;
case L'8':
return 0x8;
case L'9':
return 0x9;
case L'a':
case L'A':
return 0xA;
case L'b':
case L'B':
return 0xB;
case L'c':
case L'C':
return 0xC;
case L'd':
case L'D':
return 0xD;
case L'e':
case L'E':
return 0xE;
case L'f':
case L'F':
return 0xF;
default:
return -1;
}
}
static unsigned long squared_difference(long p1, long p2)
{
unsigned long diff = (unsigned long)labs(p1 - p2);
return diff * diff;
}
static unsigned char convert_color(const unsigned char rgb[3], const uint32_t *colors, size_t color_count)
{
long r = rgb[0], g = rgb[1], b = rgb[2];
unsigned long best_distance = (unsigned long)(-1);
unsigned char best_index = (unsigned char)(-1);
for (unsigned char idx = 0; idx < color_count; idx++)
{
uint32_t color = colors[idx];
long test_r = (color >> 16) & 0xFF, test_g = (color >> 8) & 0xFF, test_b = (color >> 0) & 0xFF;
unsigned long distance = squared_difference(r, test_r) + squared_difference(g, test_g) + squared_difference(b, test_b);
if (distance <= best_distance)
{
best_index = idx;
best_distance = distance;
}
}
return best_index;
}
bool rgb_color_t::try_parse_rgb(const wcstring &name)
{
bzero(&data, sizeof data);
/* We support the following style of rgb formats (case insensitive):
#FA3
#F3A035
FA3
F3A035
*/
size_t digit_idx = 0, len = name.size();
/* Skip any leading # */
if (len > 0 && name.at(0) == L'#')
digit_idx++;
bool success = false;
size_t i;
if (len - digit_idx == 3)
{
// type FA3
for (i=0; i < 3; i++)
{
int val = parse_hex_digit(name.at(digit_idx++));
if (val < 0) break;
data.rgb[i] = val*16+val;
}
success = (i == 3);
}
else if (len - digit_idx == 6)
{
// type F3A035
for (i=0; i < 3; i++)
{
int hi = parse_hex_digit(name.at(digit_idx++));
int lo = parse_hex_digit(name.at(digit_idx++));
if (lo < 0 || hi < 0) break;
data.rgb[i] = hi*16+lo;
}
success = (i == 3);
}
if (success)
{
this->type = type_rgb;
}
return success;
}
struct named_color_t
{
const wchar_t * name;
unsigned char idx;
unsigned char rgb[3];
};
static const named_color_t named_colors[11] =
{
{L"black", 0, {0, 0, 0}},
{L"red", 1, {0xFF, 0, 0}},
{L"green", 2, {0, 0xFF, 0}},
{L"brown", 3, {0x72, 0x50, 0}},
{L"yellow", 3, {0xFF, 0xFF, 0}},
{L"blue", 4, {0, 0, 0xFF}},
{L"magenta", 5, {0xFF, 0, 0xFF}},
{L"purple", 5, {0xFF, 0, 0xFF}},
{L"cyan", 6, {0, 0xFF, 0xFF}},
{L"white", 7, {0xFF, 0xFF, 0xFF}},
{L"normal", 8, {0xFF, 0xFF, 0XFF}}
};
wcstring_list_t rgb_color_t::named_color_names(void)
{
size_t count = sizeof named_colors / sizeof *named_colors;
wcstring_list_t result;
result.reserve(count);
for (size_t i=0; i < count; i++)
{
result.push_back(named_colors[i].name);
}
return result;
}
bool rgb_color_t::try_parse_named(const wcstring &str)
{
bzero(&data, sizeof data);
size_t max = sizeof named_colors / sizeof *named_colors;
for (size_t idx=0; idx < max; idx++)
{
if (0 == wcscasecmp(str.c_str(), named_colors[idx].name))
{
data.name_idx = named_colors[idx].idx;
this->type = type_named;
return true;
}
}
return false;
}
static const wchar_t *name_for_color_idx(unsigned char idx)
{
size_t max = sizeof named_colors / sizeof *named_colors;
for (size_t i=0; i < max; i++)
{
if (named_colors[i].idx == idx)
{
return named_colors[i].name;
}
}
return L"unknown";
}
rgb_color_t::rgb_color_t(unsigned char t, unsigned char i) : type(t), flags(), data()
{
data.name_idx = i;
}
rgb_color_t rgb_color_t::normal()
{
return rgb_color_t(type_normal);
}
rgb_color_t rgb_color_t::reset()
{
return rgb_color_t(type_reset);
}
rgb_color_t rgb_color_t::ignore()
{
return rgb_color_t(type_ignore);
}
rgb_color_t rgb_color_t::none()
{
return rgb_color_t(type_none);
}
rgb_color_t rgb_color_t::white()
{
return rgb_color_t(type_named, 7);
}
rgb_color_t rgb_color_t::black()
{
return rgb_color_t(type_named, 0);
}
static unsigned char term8_color_for_rgb(const unsigned char rgb[3])
{
const uint32_t kColors[] =
{
0x000000, //Black
0xFF0000, //Red
0x00FF00, //Green
0xFFFF00, //Yellow
0x0000FF, //Blue
0xFF00FF, //Magenta
0x00FFFF, //Cyan
0xFFFFFF, //White
};
return convert_color(rgb, kColors, sizeof kColors / sizeof *kColors);
}
static unsigned char term256_color_for_rgb(const unsigned char rgb[3])
{
const uint32_t kColors[240] =
{
0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af,
0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f,
0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af,
0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af,
0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f,
0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af,
0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af,
0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f,
0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af,
0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f,
0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af,
0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af,
0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e,
0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee
};
return 16 + convert_color(rgb, kColors, sizeof kColors / sizeof *kColors);
}
unsigned char rgb_color_t::to_term256_index() const
{
assert(type == type_rgb);
return term256_color_for_rgb(data.rgb);
}
unsigned char rgb_color_t::to_name_index() const
{
assert(type == type_named || type == type_rgb);
if (type == type_named)
{
return data.name_idx;
}
else if (type == type_rgb)
{
return term8_color_for_rgb(data.rgb);
}
else
{
/* This is an error */
return (unsigned char)(-1);
}
}
void rgb_color_t::parse(const wcstring &str)
{
bool success = false;
if (! success) success = try_parse_special(str);
if (! success) success = try_parse_named(str);
if (! success) success = try_parse_rgb(str);
if (! success)
{
bzero(this->data.rgb, sizeof this->data.rgb);
this->type = type_none;
}
}
rgb_color_t::rgb_color_t(const wcstring &str)
{
this->parse(str);
}
rgb_color_t::rgb_color_t(const std::string &str)
{
this->parse(str2wcstring(str));
}
wcstring rgb_color_t::description() const
{
switch (type)
{
case type_none:
return L"none";
case type_named:
return format_string(L"named(%d: %ls)", (int)data.name_idx, name_for_color_idx(data.name_idx));
case type_rgb:
return format_string(L"rgb(0x%02x%02x%02x)", data.rgb[0], data.rgb[1], data.rgb[2]);
case type_reset:
return L"reset";
case type_normal:
return L"normal";
case type_ignore:
return L"ignore";
default:
abort();
return L"";
}
}

177
color.h Normal file
View File

@@ -0,0 +1,177 @@
/** \file color.h Color class.
*/
#ifndef FISH_COLOR_H
#define FISH_COLOR_H
#include <stdint.h>
#include <cstddef>
#include "config.h"
#include "common.h"
/* A type that represents a color. We work hard to keep it at a size of 4 bytes. */
class rgb_color_t
{
/* Types */
enum
{
type_none,
type_named,
type_rgb,
type_normal,
type_reset,
type_ignore
};
unsigned char type:4;
/* Flags */
enum
{
flag_bold = 1 << 0,
flag_underline = 1 << 1
};
unsigned char flags:4;
union
{
unsigned char name_idx; //0-10
unsigned char rgb[3];
} data;
/** Try parsing a special color name like "normal" */
bool try_parse_special(const wcstring &str);
/** Try parsing an rgb color like "#F0A030" */
bool try_parse_rgb(const wcstring &str);
/** Try parsing an explicit color name like "magenta" */
bool try_parse_named(const wcstring &str);
/* Parsing entry point */
void parse(const wcstring &str);
/** Private constructor */
explicit rgb_color_t(unsigned char t, unsigned char i=0);
public:
/** Default constructor of type none */
explicit rgb_color_t() : type(type_none), flags(), data() {}
/** Parse a color from a string */
explicit rgb_color_t(const wcstring &str);
explicit rgb_color_t(const std::string &str);
/** Returns white */
static rgb_color_t white();
/** Returns black */
static rgb_color_t black();
/** Returns the reset special color */
static rgb_color_t reset();
/** Returns the normal special color */
static rgb_color_t normal();
/** Returns the ignore special color */
static rgb_color_t ignore();
/** Returns the none special color */
static rgb_color_t none();
/** Returns whether the color is the ignore special color */
bool is_ignore(void) const
{
return type == type_ignore;
}
/** Returns whether the color is the normal special color */
bool is_normal(void) const
{
return type == type_normal;
}
/** Returns whether the color is the reset special color */
bool is_reset(void) const
{
return type == type_reset;
}
/** Returns whether the color is the none special color */
bool is_none(void) const
{
return type == type_none;
}
/** Returns whether the color is a named color (like "magenta") */
bool is_named(void) const
{
return type == type_named;
}
/** Returns whether the color is specified via RGB components */
bool is_rgb(void) const
{
return type == type_rgb;
}
/** Returns whether the color is special, that is, not rgb or named */
bool is_special(void) const
{
return type != type_named && type != type_rgb;
}
/** Returns a description of the color */
wcstring description() const;
/** Returns the name index for the given color. Requires that the color be named or RGB. */
unsigned char to_name_index() const;
/** Returns the term256 index for the given color. Requires that the color be named or RGB. */
unsigned char to_term256_index() const;
/** Returns whether the color is bold */
bool is_bold() const
{
return !!(flags & flag_bold);
}
/** Set whether the color is bold */
void set_bold(bool x)
{
if (x) flags |= flag_bold;
else flags &= ~flag_bold;
}
/** Returns whether the color is underlined */
bool is_underline() const
{
return !!(flags & flag_underline);
}
/** Set whether the color is underlined */
void set_underline(bool x)
{
if (x) flags |= flag_underline;
else flags &= ~flag_underline;
}
/** Compare two colors for equality */
bool operator==(const rgb_color_t &other) const
{
return type == other.type && ! memcmp(&data, &other.data, sizeof data);
}
/** Compare two colors for inequality */
bool operator!=(const rgb_color_t &other) const
{
return !(*this == other);
}
/** Returns the names of all named colors */
static wcstring_list_t named_color_names(void);
};
#endif

2302
common.cpp Normal file

File diff suppressed because it is too large Load Diff

748
common.h Normal file
View File

@@ -0,0 +1,748 @@
/** \file common.h
Prototypes for various functions, mostly string utilities, that are used by most parts of fish.
*/
#ifndef FISH_COMMON_H
/**
Header guard
*/
#define FISH_COMMON_H
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <termios.h>
#include <string>
#include <sstream>
#include <vector>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include "util.h"
/**
Avoid writing the type name twice in a common "static_cast-initialization".
Caveat: This doesn't work with type names containing commas!
*/
#define CAST_INIT(type, dst, src) type dst = static_cast<type >(src)
class completion_t;
/* Common string type */
typedef std::wstring wcstring;
typedef std::vector<wcstring> wcstring_list_t;
/**
Maximum number of bytes used by a single utf-8 character
*/
#define MAX_UTF8_BYTES 6
/**
This is in the unicode private use area.
*/
#define ENCODE_DIRECT_BASE 0xf100
/**
Highest legal ascii value
*/
#define ASCII_MAX 127u
/**
Highest legal 16-bit unicode value
*/
#define UCS2_MAX 0xffffu
/**
Highest legal byte value
*/
#define BYTE_MAX 0xffu
/**
Escape special fish syntax characters like the semicolon
*/
#define UNESCAPE_SPECIAL 1
/**
Allow incomplete escape sequences
*/
#define UNESCAPE_INCOMPLETE 2
/* Flags for the escape() and escape_string() functions */
enum
{
/** Escape all characters, including magic characters like the semicolon */
ESCAPE_ALL = 1 << 0,
/** Do not try to use 'simplified' quoted escapes, and do not use empty quotes as the empty string */
ESCAPE_NO_QUOTED = 1 << 1,
/** Do not escape tildes */
ESCAPE_NO_TILDE = 1 << 2
};
typedef unsigned int escape_flags_t;
/**
Helper macro for errors
*/
#define VOMIT_ON_FAILURE(a) do { if (0 != (a)) { int err = errno; fprintf(stderr, "%s failed on line %d in file %s: %d (%s)\n", #a, __LINE__, __FILE__, err, strerror(err)); abort(); }} while (0)
/** Exits without invoking destructors (via _exit), useful for code after fork. */
void exit_without_destructors(int code) __attribute__((noreturn));
/**
Save the shell mode on startup so we can restore them on exit
*/
extern struct termios shell_modes;
/**
The character to use where the text has been truncated. Is an
ellipsis on unicode system and a $ on other systems.
*/
extern wchar_t ellipsis_char;
/* Character representing an omitted newline at the end of text */
extern wchar_t omitted_newline_char;
/**
The verbosity level of fish. If a call to debug has a severity
level higher than \c debug_level, it will not be printed.
*/
extern int debug_level;
/**
Profiling flag. True if commands should be profiled.
*/
extern char *profile;
/**
Name of the current program. Should be set at startup. Used by the
debug function.
*/
extern const wchar_t *program_name;
/**
This macro is used to check that an input argument is not null. It
is a bit lika a non-fatal form of assert. Instead of exit-ing on
failure, the current function is ended at once. The second
parameter is the return value of the current function on failure.
*/
#define CHECK( arg, retval ) \
if (!(arg)) \
{ \
debug( 0, \
"function %s called with null value for argument %s. ", \
__func__, \
#arg ); \
bugreport(); \
show_stackframe(); \
return retval; \
}
/**
Pause for input, then exit the program. If supported, print a backtrace first.
*/
#define FATAL_EXIT() \
{ \
char exit_read_buff; \
show_stackframe(); \
read( 0, &exit_read_buff, 1 ); \
exit_without_destructors( 1 ); \
} \
/**
Exit program at once, leaving an error message about running out of memory.
*/
#define DIE_MEM() \
{ \
fwprintf( stderr, \
L"fish: Out of memory on line %ld of file %s, shutting down fish\n", \
(long)__LINE__, \
__FILE__ ); \
FATAL_EXIT(); \
}
/**
Check if signals are blocked. If so, print an error message and
return from the function performing this check.
*/
#define CHECK_BLOCK(retval) \
if (signal_is_blocked()) \
{ \
debug( 0, \
"function %s called while blocking signals. ", \
__func__); \
bugreport(); \
show_stackframe(); \
return retval; \
}
/**
Shorthand for wgettext call
*/
#define _(wstr) wgettext(wstr)
/**
Noop, used to tell xgettext that a string should be translated,
even though it is not directly sent to wgettext.
*/
#define N_(wstr) wstr
/**
Check if the specified string element is a part of the specified string list
*/
#define contains( str,... ) contains_internal( str, __VA_ARGS__, NULL )
/**
Print a stack trace to stderr
*/
void show_stackframe();
/**
Read a line from the stream f into the string. Returns
the number of bytes read or -1 on failure.
If the carriage return character is encountered, it is
ignored. fgetws() considers the line to end if reading the file
results in either a newline (L'\n') character, the null (L'\\0')
character or the end of file (WEOF) character.
*/
int fgetws2(wcstring *s, FILE *f);
/**
Returns a wide character string equivalent of the
specified multibyte character string
This function encodes illegal character sequences in a reversible
way using the private use area.
*/
wcstring str2wcstring(const char *in);
wcstring str2wcstring(const char *in, size_t len);
wcstring str2wcstring(const std::string &in);
/**
Returns a newly allocated multibyte character string equivalent of
the specified wide character string
This function decodes illegal character sequences in a reversible
way using the private use area.
*/
char *wcs2str(const wchar_t *in);
char *wcs2str(const wcstring &in);
std::string wcs2string(const wcstring &input);
/** Test if a string prefixes another. Returns true if a is a prefix of b */
bool string_prefixes_string(const wcstring &proposed_prefix, const wcstring &value);
bool string_prefixes_string(const wchar_t *proposed_prefix, const wcstring &value);
/** Test if a string is a suffix of another */
bool string_suffixes_string(const wcstring &proposed_suffix, const wcstring &value);
bool string_suffixes_string(const wchar_t *proposed_suffix, const wcstring &value);
/** Test if a string prefixes another without regard to case. Returns true if a is a prefix of b */
bool string_prefixes_string_case_insensitive(const wcstring &proposed_prefix, const wcstring &value);
/** Test if a list contains a string using a linear search. */
bool list_contains_string(const wcstring_list_t &list, const wcstring &str);
void assert_is_main_thread(const char *who);
#define ASSERT_IS_MAIN_THREAD_TRAMPOLINE(x) assert_is_main_thread(x)
#define ASSERT_IS_MAIN_THREAD() ASSERT_IS_MAIN_THREAD_TRAMPOLINE(__FUNCTION__)
void assert_is_background_thread(const char *who);
#define ASSERT_IS_BACKGROUND_THREAD_TRAMPOLINE(x) assert_is_background_thread(x)
#define ASSERT_IS_BACKGROUND_THREAD() ASSERT_IS_BACKGROUND_THREAD_TRAMPOLINE(__FUNCTION__)
/* Useful macro for asserting that a lock is locked. This doesn't check whether this thread locked it, which it would be nice if it did, but here it is anyways. */
void assert_is_locked(void *mutex, const char *who, const char *caller);
#define ASSERT_IS_LOCKED(x) assert_is_locked((void *)(&x), #x, __FUNCTION__)
/** Format the specified size (in bytes, kilobytes, etc.) into the specified stringbuffer. */
wcstring format_size(long long sz);
/** Version of format_size that does not allocate memory. */
void format_size_safe(char buff[128], unsigned long long sz);
/** Our crappier versions of debug which is guaranteed to not allocate any memory, or do anything other than call write(). This is useful after a call to fork() with threads. */
void debug_safe(int level, const char *msg, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL, const char *param5 = NULL, const char *param6 = NULL, const char *param7 = NULL, const char *param8 = NULL, const char *param9 = NULL, const char *param10 = NULL, const char *param11 = NULL, const char *param12 = NULL);
/** Writes out a long safely */
void format_long_safe(char buff[128], long val);
void format_long_safe(wchar_t buff[128], long val);
template<typename T>
T from_string(const wcstring &x)
{
T result;
std::wstringstream stream(x);
stream >> result;
return result;
}
template<typename T>
T from_string(const std::string &x)
{
T result = T();
std::stringstream stream(x);
stream >> result;
return result;
}
template<typename T>
wcstring to_string(const T &x)
{
std::wstringstream stream;
stream << x;
return stream.str();
}
/* wstringstream is a huge memory pig. Let's provide some specializations where we can. */
template<>
inline wcstring to_string(const long &x)
{
wchar_t buff[128];
format_long_safe(buff, x);
return wcstring(buff);
}
template<>
inline bool from_string(const std::string &x)
{
return ! x.empty() && strchr("YTyt1", x.at(0));
}
template<>
inline bool from_string(const wcstring &x)
{
return ! x.empty() && wcschr(L"YTyt1", x.at(0));
}
template<>
inline wcstring to_string(const int &x)
{
return to_string(static_cast<long>(x));
}
wchar_t **make_null_terminated_array(const wcstring_list_t &lst);
char **make_null_terminated_array(const std::vector<std::string> &lst);
/* Helper class for managing a null-terminated array of null-terminated strings (of some char type) */
template <typename CharType_t>
class null_terminated_array_t
{
CharType_t **array;
/* No assignment or copying */
void operator=(null_terminated_array_t rhs);
null_terminated_array_t(const null_terminated_array_t &);
typedef std::vector<std::basic_string<CharType_t> > string_list_t;
size_t size() const
{
size_t len = 0;
if (array != NULL)
{
while (array[len] != NULL)
{
len++;
}
}
return len;
}
void free(void)
{
::free((void *)array);
array = NULL;
}
public:
null_terminated_array_t() : array(NULL) { }
null_terminated_array_t(const string_list_t &argv) : array(make_null_terminated_array(argv))
{
}
~null_terminated_array_t()
{
this->free();
}
void set(const string_list_t &argv)
{
this->free();
this->array = make_null_terminated_array(argv);
}
const CharType_t * const *get() const
{
return array;
}
void clear()
{
this->free();
}
};
/* Helper function to convert from a null_terminated_array_t<wchar_t> to a null_terminated_array_t<char_t> */
void convert_wide_array_to_narrow(const null_terminated_array_t<wchar_t> &arr, null_terminated_array_t<char> *output);
/* Helper class to cache a narrow version of a wcstring in a malloc'd buffer, so that we can read it after fork() */
class narrow_string_rep_t
{
private:
const char *str;
/* No copying */
narrow_string_rep_t &operator=(const narrow_string_rep_t &);
narrow_string_rep_t(const narrow_string_rep_t &x);
public:
~narrow_string_rep_t()
{
free((void *)str);
}
narrow_string_rep_t() : str(NULL) {}
void set(const wcstring &s)
{
free((void *)str);
str = wcs2str(s.c_str());
}
const char *get() const
{
return str;
}
};
bool is_forked_child();
/* Basic scoped lock class */
class scoped_lock
{
pthread_mutex_t *lock_obj;
bool locked;
/* No copying */
scoped_lock &operator=(const scoped_lock &);
scoped_lock(const scoped_lock &);
public:
void lock(void);
void unlock(void);
scoped_lock(pthread_mutex_t &mutex);
~scoped_lock();
};
/**
A scoped manager to save the current value of some variable, and optionally
set it to a new value. On destruction it restores the variable to its old
value.
This can be handy when there are multiple code paths to exit a block.
*/
template <typename T>
class scoped_push
{
T * const ref;
T saved_value;
bool restored;
public:
scoped_push(T *r): ref(r), saved_value(*r), restored(false)
{
}
scoped_push(T *r, const T &new_value) : ref(r), saved_value(*r), restored(false)
{
*r = new_value;
}
~scoped_push()
{
restore();
}
void restore()
{
if (!restored)
{
std::swap(*ref, saved_value);
restored = true;
}
}
};
/* Wrapper around wcstok */
class wcstokenizer
{
wchar_t *buffer, *str, *state;
const wcstring sep;
/* No copying */
wcstokenizer &operator=(const wcstokenizer &);
wcstokenizer(const wcstokenizer &);
public:
wcstokenizer(const wcstring &s, const wcstring &separator);
bool next(wcstring &result);
~wcstokenizer();
};
/**
Appends a path component, with a / if necessary
*/
void append_path_component(wcstring &path, const wcstring &component);
wcstring format_string(const wchar_t *format, ...);
wcstring vformat_string(const wchar_t *format, va_list va_orig);
void append_format(wcstring &str, const wchar_t *format, ...);
void append_formatv(wcstring &str, const wchar_t *format, va_list ap);
/**
Returns a newly allocated wide character string array equivalent of
the specified multibyte character string array
*/
char **wcsv2strv(const wchar_t * const *in);
/**
Test if the given string is a valid variable name.
\return null if this is a valid name, and a pointer to the first invalid character otherwise
*/
wchar_t *wcsvarname(const wchar_t *str);
/**
Test if the given string is a valid function name.
\return null if this is a valid name, and a pointer to the first invalid character otherwise
*/
const wchar_t *wcsfuncname(const wchar_t *str);
/**
Test if the given string is valid in a variable name
\return 1 if this is a valid name, 0 otherwise
*/
int wcsvarchr(wchar_t chr);
/**
A wcswidth workalike. Fish uses this since the regular wcswidth seems flaky.
*/
int my_wcswidth(const wchar_t *c);
/**
This functions returns the end of the quoted substring beginning at
\c in. The type of quoting character is detemrined by examining \c
in. Returns 0 on error.
\param in the position of the opening quote
*/
wchar_t *quote_end(const wchar_t *in);
/**
A call to this function will reset the error counter. Some
functions print out non-critical error messages. These should check
the error_count before, and skip printing the message if
MAX_ERROR_COUNT messages have been printed. The error_reset()
should be called after each interactive command executes, to allow
new messages to be printed.
*/
void error_reset();
/**
This function behaves exactly like a wide character equivalent of
the C function setlocale, except that it will also try to detect if
the user is using a Unicode character set, and if so, use the
unicode ellipsis character as ellipsis, instead of '$'.
*/
wcstring wsetlocale(int category, const wchar_t *locale);
/**
Checks if \c needle is included in the list of strings specified. A warning is printed if needle is zero.
\param needle the string to search for in the list
\return zero if needle is not found, of if needle is null, non-zero otherwise
*/
__sentinel bool contains_internal(const wchar_t *needle, ...);
__sentinel bool contains_internal(const wcstring &needle, ...);
/**
Call read while blocking the SIGCHLD signal. Should only be called
if you _know_ there is data available for reading, or the program
will hang until there is data.
*/
long read_blocked(int fd, void *buf, size_t count);
/**
Loop a write request while failure is non-critical. Return -1 and set errno
in case of critical error.
*/
ssize_t write_loop(int fd, const char *buff, size_t count);
/**
Loop a read request while failure is non-critical. Return -1 and set errno
in case of critical error.
*/
ssize_t read_loop(int fd, void *buff, size_t count);
/**
Issue a debug message with printf-style string formating and
automatic line breaking. The string will begin with the string \c
program_name, followed by a colon and a whitespace.
Because debug is often called to tell the user about an error,
before using wperror to give a specific error message, debug will
never ever modify the value of errno.
\param level the priority of the message. Lower number means higher priority. Messages with a priority_number higher than \c debug_level will be ignored..
\param msg the message format string.
Example:
<code>debug( 1, L"Pi = %.3f", M_PI );</code>
will print the string 'fish: Pi = 3.141', given that debug_level is 1 or higher, and that program_name is 'fish'.
*/
void debug(int level, const char *msg, ...);
void debug(int level, const wchar_t *msg, ...);
/**
Replace special characters with backslash escape sequences. Newline is
replaced with \n, etc.
\param in The string to be escaped
\param escape_all Whether all characters wich hold special meaning in fish (Pipe, semicolon, etc,) should be escaped, or only unprintable characters
\return The escaped string, or 0 if there is not enough memory
*/
wchar_t *escape(const wchar_t *in, escape_flags_t flags);
wcstring escape_string(const wcstring &in, escape_flags_t flags);
/**
Expand backslashed escapes and substitute them with their unescaped
counterparts. Also optionally change the wildcards, the tilde
character and a few more into constants which are defined in a
private use area of Unicode. This assumes wchar_t is a unicode
character set.
The result must be free()d. The original string is not modified. If
an invalid sequence is specified, 0 is returned.
*/
wchar_t *unescape(const wchar_t * in,
int escape_special);
bool unescape_string(wcstring &str,
int escape_special);
/**
Returns the width of the terminal window, so that not all
functions that use these values continually have to keep track of
it separately.
Only works if common_handle_winch is registered to handle winch signals.
*/
int common_get_width();
/**
Returns the height of the terminal window, so that not all
functions that use these values continually have to keep track of
it separatly.
Only works if common_handle_winch is registered to handle winch signals.
*/
int common_get_height();
/**
Handle a window change event by looking up the new window size and
saving it in an internal variable used by common_get_wisth and
common_get_height().
*/
void common_handle_winch(int signal);
/**
Write paragraph of output to the specified stringbuffer, and redo
the linebreaks to fit the current screen.
*/
void write_screen(const wcstring &msg, wcstring &buff);
/**
Tokenize the specified string into the specified wcstring_list_t.
\param val the input string. The contents of this string is not changed.
\param out the list in which to place the elements.
*/
void tokenize_variable_array(const wcstring &val, wcstring_list_t &out);
/**
Make sure the specified direcotry exists. If needed, try to create
it and any currently not existing parent directories..
\return 0 if, at the time of function return the directory exists, -1 otherwise.
*/
int create_directory(const wcstring &d);
/**
Print a short message about how to file a bug report to stderr
*/
void bugreport();
/**
Return the number of seconds from the UNIX epoch, with subsecond
precision. This function uses the gettimeofday function, and will
have the same precision as that function.
If an error occurs, NAN is returned.
*/
double timef();
/**
Call the following function early in main to set the main thread.
This is our replacement for pthread_main_np().
*/
void set_main_thread();
bool is_main_thread();
/** Configures thread assertions for testing */
void configure_thread_assertions_for_testing();
/** Set up a guard to complain if we try to do certain things (like take a lock) after calling fork */
void setup_fork_guards(void);
/** Save the value of tcgetpgrp so we can restore it on exit */
void save_term_foreground_process_group(void);
void restore_term_foreground_process_group(void);
/** Return whether we are the child of a fork */
bool is_forked_child(void);
void assert_is_not_forked_child(const char *who);
#define ASSERT_IS_NOT_FORKED_CHILD_TRAMPOLINE(x) assert_is_not_forked_child(x)
#define ASSERT_IS_NOT_FORKED_CHILD() ASSERT_IS_NOT_FORKED_CHILD_TRAMPOLINE(__FUNCTION__)
extern "C" {
__attribute__((noinline)) void debug_thread_error(void);
}
/** Return the path of an appropriate runtime data directory */
std::string common_get_runtime_path();
#endif

2102
complete.cpp Normal file

File diff suppressed because it is too large Load Diff

294
complete.h Normal file
View File

@@ -0,0 +1,294 @@
/** \file complete.h
Prototypes for functions related to tab-completion.
These functions are used for storing and retrieving tab-completion
data, as well as for performing tab-completion.
*/
#ifndef FISH_COMPLETE_H
/**
Header guard
*/
#define FISH_COMPLETE_H
#include <wchar.h>
#include "util.h"
#include "common.h"
/**
* Use all completions
*/
#define SHARED 0
/**
* Do not use file completion
*/
#define NO_FILES 1
/**
* Require a parameter after completion
*/
#define NO_COMMON 2
/**
* Only use the argument list specifies with completion after
* option. This is the same as (NO_FILES & NO_COMMON)
*/
#define EXCLUSIVE 3
/**
* Command is a path
*/
#define PATH 1
/**
* Command is not a path
*/
#define COMMAND 0
/**
* Separator between completion and description
*/
#define COMPLETE_SEP L'\004'
/**
* Separator between completion and description
*/
#define COMPLETE_SEP_STR L"\004"
/**
* Separator between completion items in fish_pager. This is used for
* completion grouping, e.g. when putting completions with the same
* descriptions on the same line.
*/
#define COMPLETE_ITEM_SEP L'\uf500'
/**
* Character that separates the completion and description on
* programmable completions
*/
#define PROG_COMPLETE_SEP L'\t'
enum
{
/**
Do not insert space afterwards if this is the only completion. (The
default is to try insert a space)
*/
COMPLETE_NO_SPACE = 1 << 0,
/** This completion is case insensitive. */
COMPLETE_CASE_INSENSITIVE = 1 << 1,
/** This is not the suffix of a token, but replaces it entirely */
COMPLETE_REPLACES_TOKEN = 1 << 2,
/**
This completion may or may not want a space at the end - guess by
checking the last character of the completion.
*/
COMPLETE_AUTO_SPACE = 1 << 3,
/** This completion should be inserted as-is, without escaping. */
COMPLETE_DONT_ESCAPE = 1 << 4,
/** If you do escape, don't escape tildes */
COMPLETE_DONT_ESCAPE_TILDES = 1 << 5
};
typedef int complete_flags_t;
class completion_t
{
private:
/* No public default constructor */
completion_t();
public:
/* Destructor. Not inlining it saves code size. */
~completion_t();
/**
The completion string
*/
wcstring completion;
/**
The description for this completion
*/
wcstring description;
/**
Flags determining the completion behaviour.
Determines whether a space should be inserted after this
completion if it is the only possible completion using the
COMPLETE_NO_SPACE flag.
The COMPLETE_NO_CASE can be used to signal that this completion
is case insensitive.
*/
int flags;
bool is_case_insensitive() const
{
return !!(flags & COMPLETE_CASE_INSENSITIVE);
}
/* Construction. Note: defining these so that they are not inlined reduces the executable size. */
completion_t(const wcstring &comp, const wcstring &desc = L"", int flags_val = 0);
completion_t(const completion_t &);
completion_t &operator=(const completion_t &);
/* The following are needed for sorting and uniquing completions */
bool operator < (const completion_t& rhs) const;
bool operator == (const completion_t& rhs) const;
bool operator != (const completion_t& rhs) const;
};
enum
{
COMPLETION_REQUEST_DEFAULT = 0,
COMPLETION_REQUEST_AUTOSUGGESTION = 1 << 0, // indicates the completion is for an autosuggestion
COMPLETION_REQUEST_DESCRIPTIONS = 1 << 1, // indicates that we want descriptions
COMPLETION_REQUEST_FUZZY_MATCH = 1 << 2 // indicates that we don't require a prefix match
};
typedef uint32_t completion_request_flags_t;
/** Given a list of completions, returns a list of their completion fields */
wcstring_list_t completions_to_wcstring_list(const std::vector<completion_t> &completions);
/** Sorts a list of completions */
void sort_completions(std::vector<completion_t> &completions);
/**
Add a completion.
All supplied values are copied, they should be freed by or otherwise
disposed by the caller.
Examples:
The command 'gcc -o' requires that a file follows it, so the
NO_COMMON option is suitable. This can be done using the following
line:
complete -c gcc -s o -r
The command 'grep -d' required that one of the strings 'read',
'skip' or 'recurse' is used. As such, it is suitable to specify that
a completion requires one of them. This can be done using the
following line:
complete -c grep -s d -x -a "read skip recurse"
\param cmd Command to complete.
\param cmd_type If cmd_type is PATH, cmd will be interpreted as the absolute
path of the program (optionally containing wildcards), otherwise it
will be interpreted as the command name.
\param short_opt The single character name of an option. (-a is a short option,
--all and -funroll are long options)
\param long_opt The multi character name of an option. (-a is a short option,
--all and -funroll are long options)
\param long_mode Whether to use old style, single dash long options.
\param result_mode Whether to search further completions when this
completion has been succesfully matched. If result_mode is SHARED,
any other completions may also be used. If result_mode is NO_FILES,
file completion should not be used, but other completions may be
used. If result_mode is NO_COMMON, on option may follow it - only a
parameter. If result_mode is EXCLUSIVE, no option may follow it, and
file completion is not performed.
\param comp A space separated list of completions which may contain subshells.
\param desc A description of the completion.
\param condition a command to be run to check it this completion should be used.
If \c condition is empty, the completion is always used.
\param flags A set of completion flags
*/
void complete_add(const wchar_t *cmd,
bool cmd_is_path,
wchar_t short_opt,
const wchar_t *long_opt,
int long_mode,
int result_mode,
const wchar_t *condition,
const wchar_t *comp,
const wchar_t *desc,
int flags);
/**
Sets whether the completion list for this command is complete. If
true, any options not matching one of the provided options will be
flagged as an error by syntax highlighting.
*/
void complete_set_authoritative(const wchar_t *cmd, bool cmd_type, bool authoritative);
/**
Remove a previously defined completion
*/
void complete_remove(const wchar_t *cmd,
bool cmd_is_path,
wchar_t short_opt,
const wchar_t *long_opt);
/** Find all completions of the command cmd, insert them into out. If to_load is
* not NULL, append all commands that we would autoload, but did not (presumably
* because this is not the main thread)
*/
void complete(const wcstring &cmd,
std::vector<completion_t> &comp,
completion_request_flags_t flags,
wcstring_list_t *to_load = NULL);
/**
Print a list of all current completions into the string.
\param out The string to write completions to
*/
void complete_print(wcstring &out);
/**
Tests if the specified option is defined for the specified command
*/
int complete_is_valid_option(const wcstring &str,
const wcstring &opt,
wcstring_list_t *inErrorsOrNull,
bool allow_autoload);
/**
Tests if the specified argument is valid for the specified option
and command
*/
bool complete_is_valid_argument(const wcstring &str,
const wcstring &opt,
const wcstring &arg);
/**
Load command-specific completions for the specified command. This
is done automatically whenever completing any given command, so
there is no need to call this except in the case of completions
with internal dependencies.
\param cmd the command for which to load command-specific completions
\param reload should the commands completions be reloaded, even if they where
previously loaded. (This is set to true on actual completions, so that
changed completion are updated in running shells)
*/
void complete_load(const wcstring &cmd, bool reload);
/**
Create a new completion entry
\param completions The array of completions to append to
\param comp The completion string
\param desc The description of the completion
\param flags completion flags
*/
void append_completion(std::vector<completion_t> &completions, const wcstring &comp, const wcstring &desc = L"", int flags = 0);
/* Function used for testing */
void complete_set_variable_names(const wcstring_list_t *names);
#endif

Some files were not shown because too many files have changed in this diff Show More