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
1823 changed files with 131209 additions and 780206 deletions

View File

@@ -1,24 +0,0 @@
image: alpine/edge
packages:
- cmake
- ninja
- ncurses-dev
- pcre2-dev
- expect
- python
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 SHOW_INTERACTIVE_LOG=1 ninja test

View File

@@ -1,22 +0,0 @@
image: archlinux
packages:
- cmake
- ninja
- expect
- python
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 SHOW_INTERACTIVE_LOG=1 ninja test

View File

@@ -1,26 +0,0 @@
image: freebsd/latest
packages:
- ncurses
- gcc
- gettext
- expect
- cmake
- gmake
- pcre2
- python
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 SHOW_INTERACTIVE_LOG=1

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-*,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'
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,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,12 +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

View File

@@ -1,22 +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

29
.gitattributes vendored
View File

@@ -1,31 +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
/.travis.yml 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
/doc_src/* linguist-documentation
*.fish linguist-language=fish
/tests/*.in linguist-language=fish

View File

@@ -1,14 +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.
-->

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,16 +0,0 @@
name: 'Lock threads'
on:
schedule:
- cron: '0 * * * *'
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: '90'
pr-lock-inactive-days: '90'
issue-exclude-labels: 'question'

View File

@@ -1,48 +0,0 @@
name: C/C++ CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo apt install expect gettext libncurses5-dev libpcre2-dev
- name: cmake
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: |
# brew install pcre2
# - name: cmake
# run: |
# mkdir build && cd build
# cmake ..
# - 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,122 +0,0 @@
language: cpp
dist: xenial
sudo: required
matrix:
include:
- os: linux
compiler: gcc
addons:
apt:
packages:
- expect
- gettext
- libncurses5-dev
- libpcre2-dev
- python3
- python3-pip
before_install:
- sudo pip3 install pexpect
env:
# Some warnings upgraded to errors to match Open Build Service platforms
- CXXFLAGS="-Werror=address -Werror=return-type"
- os: linux
compiler: gcc
addons:
apt:
packages: # Don't use libpcre2-dev here, so that one build uses the vendored code
- expect
- gettext
- lib32ncurses5-dev
- g++-multilib
- python3
- python3-pip
before_install:
- sudo pip3 install pexpect
env:
- CXXFLAGS="-m32 -Werror=address -Werror=return-type" CFLAGS="-m32"
- os: linux
compiler: clang
env:
- CXXFLAGS="-fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address"
- ASAN_OPTIONS=check_initialization_order=1:detect_stack_use_after_return=1:detect_leaks=1
- UBSAN_OPTIONS=print_stacktrace=1:report_error_type=1:suppressions=$TRAVIS_BUILD_DIR/build_tools/ubsan.blacklist
before_install:
- sudo pip3 install pexpect
addons:
apt:
packages:
- expect
- gettext
- libncurses5-dev
- libpcre2-dev
- python
- python3
- python3-pip
- os: linux
compiler: clang
env:
- CXXFLAGS="-fsanitize=thread"
before_install:
- sudo pip3 install pexpect
addons:
apt:
packages:
- expect
- gettext
- libncurses5-dev
- libpcre2-dev
- python3
- python3-pip
- os: linux
compiler: gcc
addons:
coverity_scan:
project:
name: "fish-shell/fish-shell"
description: "The friendly interactive shell"
notification_email: corydoras@ridiculousfish.com
build_command_prepend: "mkdir -p build; cd build; cmake -G Ninja .."
build_command: "ninja"
branch_pattern: coverity_scan_master
apt:
packages:
- expect
- gettext
- libncurses5-dev
- libpcre2-dev
- python3
- python3-pip
before_install:
- sudo pip3 install pexpect
- echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-
env:
- secure: "Q1AS5iEi17s+xsRaMwfkxmm62UDaV47uE39pvXsNL+DO9YWbMMuhTpIOeYhxLvFNL3LMUFU2TwVpVRYX2YFGhNNaMSmjQfyQ+7q7/oSEo0aSqvQkwelpK/pwuRAXdv1MU4aQ6FrCEQ4VMO45WRo0o5WD26pvxjqRyAQ6ry+serA="
# Some warnings upgraded to errors to match Open Build Service platforms
- CXXFLAGS="-Werror=address -Werror=return-type"
- os: osx
before_install:
- sudo pip3 install pexpect
fast_finish: true
script:
- cmake -DCMAKE_INSTALL_PREFIX=$HOME/prefix . || cat CMakeFiles/CMakeError.log &&
make -j2 &&
make install &&
make test SHOW_INTERACTIVE_LOG=1
notifications:
# Some items are encrypted so that notifications from other repositories
# don't flood the official repositories.
irc:
channels:
#- "irc.oftc.net#fish"
secure: "eRk9KGZ5+mrlD2SoI8yg2Sp8OYrh7YPyGe3WCDQUwTnNgNDII34rbM9a6UOA/l7AeWSNY8joLq5xVLCU4wpFgUcJ11SYIpMnLosZK29OW4ubDOHmdBDvJ971rLgAVG9cXngZtIxEVVxN/jnS1Qr8GKZx4DjkaTMgz1pemb4WxCc="
template:
- "%{repository}#%{build_number} (%{commit} on %{branch} by %{author}): %{message} Details at %{build_url}"
use_notice: true
skip_join: true
webhooks:
urls:
#- https://webhooks.gitter.im/e/61821cec3015bf0f8bb1
secure: fPfOmxnC3MCsfR1oocVFeWLawGcRZkn+8fNHlSOeZ+SqqoZfcCHgQTvQ22TqmVl1yvkXbNlaXjo6dbVzTOAh7r7H0bRMEKBVh3dQS7wqjB1sKivpXd8PAS3BTj5MQpGeJzdHnDuwVlwDktGtfHfhGeq1Go/4IosOq8u+6RTe28g=

View File

@@ -1,53 +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/build.ninja
.else
BUILDFILE=build/Makefile
.endif
PREFIX?=/usr/local
build/fish: build/$(BUILDFILE)
$(CMAKE) --build build
build:
mkdir -p build
build/$(BUILDFILE): 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,230 +0,0 @@
cmake_minimum_required(VERSION 3.2)
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")
# Use the default flags (#6296) but remove -DNDEBUG so that asserts remain enabled.
string(REPLACE "-DNDEBUG" ""
CMAKE_CXX_FLAGS_RELWITHDEBINFO
"${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
string(REPLACE "-DNDEBUG" ""
CMAKE_CXX_FLAGS_RELEASE
"${CMAKE_CXX_FLAGS_RELEASE}")
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()
# 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)))
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always")
endif()
# Enable a whole bunch of warnings, but turn off:
# - implicit fallthrough because that does not recognize some cases where it's desired (and I *really* want this one!)
# - 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`).
# - strict-aliasing, because on old GCCs (*Travis*) those are triggered by maybe.h, so you get it every time it is included.
# - redundant-move, because we have one that is required on old libc
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra \
-Wno-implicit-fallthrough \
-Wno-comment \
-Wno-address \
-Wno-strict-aliasing \
-Wno-redundant-move \
")
# Disable exception handling.
add_compile_options(-fno-exceptions)
# Prefer the gold linker because it doesn't emit useless warnings about sys_nerr and _sys_errlist.
if (UNIX AND NOT APPLE)
execute_process(COMMAND ${CMAKE_C_COMPILER} -fuse-ld=gold -Wl,--version
ERROR_QUIET OUTPUT_VARIABLE LD_VERSION)
if ("${LD_VERSION}" MATCHES "GNU gold")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold")
endif()
endif()
# 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 "^$")
source_group("Header Files" REGULAR_EXPRESSION "^$")
source_group("Builtins" REGULAR_EXPRESSION "builtin_.*")
# 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()
# All objects that the system needs to build fish, except fish.cpp
set(FISH_SRCS
src/autoload.cpp src/builtin.cpp src/builtin_bg.cpp src/builtin_bind.cpp
src/builtin_block.cpp src/builtin_builtin.cpp src/builtin_cd.cpp
src/builtin_command.cpp src/builtin_commandline.cpp
src/builtin_complete.cpp src/builtin_contains.cpp src/builtin_disown.cpp
src/builtin_echo.cpp src/builtin_emit.cpp src/builtin_exit.cpp
src/builtin_fg.cpp src/builtin_function.cpp src/builtin_functions.cpp
src/builtin_argparse.cpp src/builtin_history.cpp src/builtin_jobs.cpp
src/builtin_math.cpp src/builtin_printf.cpp src/builtin_pwd.cpp
src/builtin_random.cpp src/builtin_read.cpp src/builtin_realpath.cpp
src/builtin_return.cpp src/builtin_set.cpp src/builtin_set_color.cpp
src/builtin_source.cpp src/builtin_status.cpp src/builtin_string.cpp
src/builtin_test.cpp src/builtin_ulimit.cpp src/builtin_wait.cpp src/builtin_eval.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/fish_version.cpp src/function.cpp src/highlight.cpp
src/history.cpp src/history_file.cpp src/input.cpp src/input_common.cpp src/intern.cpp
src/io.cpp src/iothread.cpp src/kill.cpp src/output.cpp src/pager.cpp
src/parse_execution.cpp src/parse_productions.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/reader.cpp src/sanity.cpp src/screen.cpp
src/signal.cpp src/tinyexpr.cpp src/tnode.cpp src/tokenizer.cpp src/utf8.cpp src/util.cpp
src/wcstringutil.cpp src/wgetopt.cpp src/wildcard.cpp src/wutil.cpp
src/future_feature_flags.cpp src/redirection.cpp src/topic_monitor.cpp
src/flog.cpp src/trace.cpp src/timer.cpp src/null_terminated_array.cpp
src/operation_context.cpp src/fd_monitor.cpp src/termsize.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})
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})
# 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)
# 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,499 +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. Generally known as the style of the code. It
also includes recommended best practices such as creating a Travis CI
account so you can verify that your changes pass all the tests before
making a pull request.
See the bottom of this document for help on installing the linting and
style reformatting tools discussed in the following sections.
Fish source should limit the C++ features it uses to those available in
C++11. It should not use exceptions.
Before introducing a new dependency, please make it optional with
graceful failure if possible. Add any new dependencies to the README.rst
under the *Running* and/or *Building* sections.
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
Lint Free Code
--------------
Automated analysis tools like cppcheck and oclint 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.
Ultimately we want lint free code. However, at the moment a lot of
cleanup is required to reach that goal. For now simply try to avoid
introducing new lint.
To make linting the code easy there are two make targets: ``lint`` and
``lint-all``. The latter does exactly what the name implies. The former
will lint any modified but not committed ``*.cpp`` files. If there is no
uncommitted work it will lint the files in the most recent commit.
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.
Fish also depends on ``diff`` and ``expect`` for its tests.
Dealing With Lint Warnings
~~~~~~~~~~~~~~~~~~~~~~~~~~
You are strongly encouraged to address a lint warning by refactoring the
code, changing variable names, or whatever action is implied by the
warning.
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.
Suppressing oclint warnings is more complicated to describe so Ill
refer you to the `OCLint
HowTo <http://docs.oclint.org/en/latest/howto/suppress.html#annotations>`__
on the topic.
Ensuring Your Changes Conform to the Style Guides
-------------------------------------------------
The following sections discuss the specific rules for the style that
should be used when writing fish code. To ensure your changes conform to
the style rules you simply need to run
::
build_tools/style.fish
before committing your change. That will run ``git-clang-format`` to
rewrite only the lines youre modifying.
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 C++ Code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ViM
^^^
As of ViM 7.4 it does not recognize triple-slash comments as used by
Doxygen and the OS X Xcode IDE to flag comments that explain the
following C symbol. This means the ``gq`` key binding to reformat such
comments doesnt behave as expected. You can fix that by adding the
following to your vimrc:
::
autocmd Filetype c,cpp setlocal comments^=:///
If you use ViM I recommend the `vim-clang-format
plugin <https://github.com/rhysd/vim-clang-format>`__ by
[@rhysd](https://github.com/rhysd).
You can also get ViM to provide reasonably correct behavior by
installing
http://www.vim.org/scripts/script.php?script_id=2636
Emacs
^^^^^
If you use Emacs: TBD
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)))
Suppressing Reformatting of C++ Code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you have a good reason for doing so you can tell ``clang-format`` to
not reformat a block of code by enclosing it in comments like this:
::
// clang-format off
code to ignore
// clang-format on
However, as I write this there are no places in the code where we use
this and I cant think of any legitimate reasons for exempting blocks of
code from clang-format.
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 mandatory 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 the `Travis
CI <https://travis-ci.org/fish-shell/fish-shell>`__ service.
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).
Local testing
~~~~~~~~~~~~~
The tests can be run on your local computer on all operating systems.
::
cmake path/to/fish-shell
make test
Travis CI Build and Test
~~~~~~~~~~~~~~~~~~~~~~~~
The Travis Continuous Integration services can be used to test your
changes using multiple configurations. This is the same service that the
fish-shell project uses to ensure new changes havent broken anything.
Thus it is a really good idea that you leverage Travis CI before making
a pull request to avoid potential embarrassment at breaking the build.
You will need to `fork the fish-shell repository on
GitHub <https://help.github.com/articles/fork-a-repo/>`__, then setup
Travis to test your changes before making a pull request.
1. `Sign in to Travis CI <https://travis-ci.org/auth>`__ with your
GitHub account, accepting the GitHub access permissions confirmation.
2. Once youre signed in and your repositories are synchronized, go to
your `profile page <https://travis-ci.org/profile>`__ and enable the
fish-shell repository.
3. Push your changes to GitHub.
Youll receive an email when the tests are complete telling you whether
or not any tests failed.
Youll find the configuration used to control Travis in the
``.travis.yml`` file.
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 tap oclint/formulae
brew install oclint
brew install cppcheck
To install the lint checkers on Debian-based Linux distributions:
::
sudo apt-get install clang
sudo apt-get install oclint
sudo apt-get install cppcheck
Installing the Reformatting Tools
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Mac OS X:
::
brew install clang-format
Debian-based:
::
apt-cache search clang-format
Above will list all the versions available. Pick the newest one
available (3.9 for Ubuntu 16.10 as I write this) and install it:
::
sudo apt-get install clang-format-3.9
sudo ln -s /usr/bin/clang-format-3.9 /usr/bin/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.

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-2020 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.hdr.
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,69 +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)
build/fish: build/$(BUILDFILE)
$(CMAKE) --build build
build/$(BUILDFILE): build
cd build; $(CMAKE) .. -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -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,238 +0,0 @@
`fish <https://fishshell.com/>`__ - the friendly interactive shell |Build Status|
=================================================================================
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 more on fishs design philosophy, see the `design
document <https://fishshell.com/docs/current/design.html>`__.
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
You can quickly play with fish right in your browser by clicking the
button below:
|Try in browser|
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/>`__
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-get update
sudo apt-get 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* 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, sed
and awk is preferred)
- gettext (library and ``gettext`` command), 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 ``ul`` and either ``nroff`` or ``mandoc`` for
display
- automated completion generation from manual pages requires Python
(2.7+ or 3.3+) and possibly the ``backports.lzma`` module for Python
2.7
- the ``fish_config`` web configuration tool requires Python (2.7+ or
3.3 +) 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
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.2 or later)
- a curses implementation such as ncurses (headers and libraries)
- PCRE2 (headers and libraries) - a copy is included with fish
- gettext (headers and libraries) - optional, for translation support
Sphinx is also optionally required to build the documentation from a
cloned git repository.
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. 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``.
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-get 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
Stackoverflow <https://stackoverflow.com/questions/tagged/fish>`__ for
questions related to fish script and the `fish tag on
Superuser <https://superuser.com/questions/tagged/fish>`__ for all other
questions (e.g., customizing colors, changing key bindings).
Found a bug? Have an awesome idea? Please `open an
issue <https://github.com/fish-shell/fish-shell/issues/new>`__.
.. |Build Status| image:: https://travis-ci.org/fish-shell/fish-shell.svg?branch=master
:target: https://travis-ci.org/fish-shell/fish-shell
.. |Try in browser| image:: https://cdn.rawgit.com/rootnroll/library/assets/try.svg
:target: https://rootnroll.com/d/fish-shell/

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,4 +0,0 @@
for i in (seq 2000)
command true
end

View File

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

View File

@@ -1,17 +0,0 @@
#!/bin/sh
if [ "$#" -ne 1 ]; then
echo "Usage: driver.sh /path/to/fish"
fi
FISH_PATH=$1
BENCHMARKS_DIR=$(dirname "$0")/benchmarks
for benchmark in "$BENCHMARKS_DIR"/*; do
basename "$benchmark"
${FISH_PATH} --print-rusage-self "$benchmark" > /dev/null
if command -v hyperfine >/dev/null 2>&1; then
hyperfine "${FISH_PATH} $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,3 +0,0 @@
#!/usr/local/bin/fish
cppcheck --enable=all --std=posix --quiet ./src/

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,112 +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,55 +0,0 @@
#!/usr/bin/env fish
#
# Tool to generate messages.pot
# Extended to replace the old Makefile rule which did not port easily to CMak
# 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) *\).*'
rm -r /tmp/fish
mkdir -p /tmp/fish/implicit/share/completions /tmp/fish/implicit/share/functions
mkdir -p /tmp/fish/explicit/share/completions /tmp/fish/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 >/tmp/fish/explicit/$f.tmp ^/dev/null
while read description
echo 'N_ "'(string replace --all '"' '\\"' -- $description)'"'
end </tmp/fish/explicit/$f.tmp >/tmp/fish/explicit/$f
rm /tmp/fish/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 >/tmp/fish/implicit/$f.tmp ^/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 </tmp/fish/implicit/$f.tmp >/tmp/fish/implicit/$f
rm /tmp/fish/implicit/$f.tmp
end
xgettext -j -k -kN_ -LShell --from-code=UTF-8 -cDescription --no-wrap -o messages.pot /tmp/fish/explicit/share/*/*.fish
xgettext -j -k -kN_ -LShell --from-code=UTF-8 -cDescription --no-wrap -o messages.pot /tmp/fish/implicit/share/*/*.fish
rm -r /tmp/fish

View File

@@ -1,49 +0,0 @@
#!/usr/bin/env 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
# 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"
elif ! VN=$(git -C "$FISH_BASE_DIR" describe --always --dirty 2>/dev/null); then
VN="$DEF_VER"
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 -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,109 +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: ["<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_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>", "private", "<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: ["<strings.h>", "private", "<string.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>", "private", "<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: ["<>", "private", "<>", "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", "<stddef.h>", "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", "<stddef.h>", "public"] },
{ symbol: ["ssize_t", "private", "<stddef.h>", "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", "<stdint.h>", "public"] },
{ symbol: ["uint32_t", "private", "<stdint.h>", "public"] },
{ symbol: ["intptr_t", "private", "<stdint.h>", "public"] },
{ symbol: ["tparm", "private", "<ncurses.h>", "public"] },
{ symbol: ["tigetflag", "private", "<ncurses.h>", "public"] },
{ symbol: ["ERR", "private", "<ncurses.h>", "public"] },
{ symbol: ["OK", "private", "<ncurses.h>", "public"] },
{ symbol: ["select", "private", "<sys/select.h>", "public"] },
{ symbol: ["_LIBCPP_VERSION", "private", "<stddef.h>", "public"] },
{ symbol: ["_LIBCPP_VERSION", "private", "<unistd.h>", "public"] },
{ symbol: ["MB_CUR_MAX", "private", "<xlocale.h>", "public"] },
{ symbol: ["MB_CUR_MAX", "private", "<stdlib.h>", "public"] },
]

View File

@@ -1,132 +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 cppcheck_args $cppcheck_args $arg
else if string match -q -- '-I*' $arg
set cppcheck_args $cppcheck_args $arg
else if string match -q -- '-iquote*' $arg
set cppcheck_args $cppcheck_args $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 ========================================
# The stderr to stdout redirection is because cppcheck, incorrectly IMHO, writes its
# diagnostic messages to stderr. Anyone running this who wants to capture its output will
# expect those messages to be written to stdout.
set -l cn (set_color normal)
set -l cb (set_color --bold)
set -l cu (set_color --underline)
set -l cm (set_color magenta)
set -l cbrm (set_color brmagenta)
set -l template "[$cb$cu{file}$cn$cb:{line}$cn] $cbrm{severity}$cm ({id}):$cn\n {message}"
set cppcheck_args -q --verbose --std=c++11 --std=posix --language=c++ --template $template \
--suppress=missingIncludeSystem --inline-suppr --enable=$cppchecks \
--rule-file=.cppcheck.rules --suppressions-list=.cppcheck.suppressions $cppcheck_args
cppcheck $cppcheck_args $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,34 +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)
set -l committers_from_tag (mktemp)
# 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,560 +0,0 @@
#!/usr/bin/env python
""" Command line test driver. """
from __future__ import unicode_literals
from __future__ import print_function
import argparse
from collections import deque
import datetime
import io
import re
import shlex
import subprocess
import sys
# A regex showing how to run the file.
RUN_RE = re.compile(r"\s*#\s*RUN:\s+(.*)\n")
# A regex capturing lines that should be checked against stdout.
CHECK_STDOUT_RE = re.compile(r"\s*#\s*CHECK:\s+(.*)\n")
# A regex capturing lines that should be checked against stderr.
CHECK_STDERR_RE = re.compile(r"\s*#\s*CHECKERR:\s+(.*)\n")
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
# How many after lines to print
self.after = 5
# How many before lines to print
self.before = 5
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 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()
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, before=None, after=None):
self.line = line
self.check = check
self.testrun = testrun
self.error_annotation_line = None
# The output that comes *after* the failure.
self.after = after
self.before = before
def message(self):
afterlines = self.testrun.config.after
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.text.rstrip("\n"),
}
)
if self.check:
fields.update(
{
"input_file": self.check.line.file,
"input_lineno": self.check.line.number,
"input_line": self.check.line.text,
"check_type": self.check.type,
}
)
filemsg = "" if self.testrun.config.progress else " in {name}"
fmtstrs = ["{RED}Failure{RESET}" + filemsg + ":", ""]
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_line:
fields["error_annotation"] = self.error_annotation_line.text
fields["error_annotation_lineno"] = self.error_annotation_line.number
fmtstrs += [
" additional output on stderr:{error_annotation_lineno}:",
" {BOLD}{error_annotation}{RESET}",
]
if self.before:
fields["before_output"] = " ".join(self.before)
fields["additional_output"] = " ".join(self.after[:afterlines])
fmtstrs += [
" Context:",
" {BOLD}{before_output} {RED}{output_line}{RESET} <= does not match '{LIGHTBLUE}{input_line}{RESET}'",
" {BOLD}{additional_output}{RESET}",
]
elif self.after:
fields["additional_output"] = " ".join(self.after[:afterlines])
fmtstrs += [" additional output:", " {BOLD}{additional_output}{RESET}"]
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)
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]
# We keep the last couple of lines in a deque so we can show context.
before = deque(maxlen=self.config.before)
while lineq and checkq:
line = lineq[-1]
check = checkq[-1]
if check.regex.match(line.text):
# This line matched this checker, continue on.
lineq.pop()
checkq.pop()
before.append(line)
elif line.is_empty_space():
# Skip all whitespace input lines.
lineq.pop()
else:
# Failed to match.
lineq.pop()
line.text = escape_string(line.text.strip()) + "\n"
# Add context, ignoring empty lines.
return TestFailure(
line,
check,
self,
before=[escape_string(line.text.strip()) + "\n" for line in before],
after=[
escape_string(line.text.strip()) + "\n"
for line in lineq[::-1]
if not line.is_empty_space()
],
)
# Drain empties.
while lineq and lineq[-1].is_empty_space():
lineq.pop()
# If there's still lines or checkers, we have a failure.
# Otherwise it's success.
if lineq:
return TestFailure(lineq[-1], None, self)
elif checkq:
return TestFailure(None, checkq[-1], self)
else:
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")]
PIPE = subprocess.PIPE
if self.config.verbose:
print(self.subbed_command)
proc = subprocess.Popen(
self.subbed_command,
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
shell=True,
close_fds=True, # For Python 2.6 as shipped on RHEL 6
)
stdout, stderr = proc.communicate()
# HACK: This is quite cheesy: POSIX specifies that sh should return 127 for a missing command.
# Technically it's also possible to return it in other conditions.
# Practically, that's *probably* not going to happen.
status = proc.returncode
if status == 127:
raise CheckerError("Command could not be found: " + self.subbed_command)
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_line = errfail.line
return outfail if outfail else errfail
class CheckCmd(object):
def __init__(self, line, checktype, regex):
self.line = line
self.type = checktype
self.regex = regex
@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)]
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.
self.runcmds = [RunCmd(lines[0].text[2:-1] + " %s", lines[0])]
else:
raise CheckerError("No runlines ('# RUN') found")
# 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)
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("file", nargs="+", help="File to check")
parser.add_argument(
"-A",
"--after",
type=int,
help="How many non-empty lines of output after a failure to print (default: 5)",
action="store",
default=5,
)
parser.add_argument(
"-B",
"--before",
type=int,
help="How many non-empty lines of output before a failure to print (default: 5)",
action="store",
default=5,
)
return parser
def main():
args = get_argparse().parse_args()
# Default substitution is %% -> %
def_subs = {"%": "%"}
def_subs.update(parse_subs(args.substitute))
failure_count = 0
config = Config()
config.colorize = sys.stdout.isatty()
config.progress = args.progress
fields = config.colors()
config.after = args.after
config.before = args.before
if config.before < 0:
raise ValueError("Before must be at least 0")
if config.after < 0:
raise ValueError("After must be at least 0")
for path in args.file:
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()
if not check_path(path, subs, config, TestFailure.print_message):
failure_count += 1
elif config.progress:
endtime = datetime.datetime.now()
duration_ms = round((endtime - starttime).total_seconds() * 1000)
print(
"{GREEN}ok{RESET} ({duration} ms)".format(
duration=duration_ms, **fields
)
)
sys.exit(failure_count)
if __name__ == "__main__":
main()

View File

@@ -1,99 +0,0 @@
#!/usr/bin/env bash
# Helper to notarize an .app.zip or .pkg file.
# Based on https://www.logcg.com/en/archives/3222.html
set -e
die() { echo "$*" 1>&2 ; exit 1; }
check_status() {
echo "STATUS" $1
}
get_req_uuid() {
RESPONSE=$(</dev/stdin)
if echo "$RESPONSE" | egrep -q "RequestUUID"; then
echo "$RESPONSE" | egrep RequestUUID | awk '{print $3'}
elif echo "$RESPONSE" | egrep -q "The upload ID is "; then
echo "$RESPONSE" | egrep -p "The upload ID is [-a-z0-9]+" | awk '{print $5}'
else
die "Could not get Request UUID"
fi
}
INPUT=$1
AC_USER=$2
test -z "$AC_USER" && die "AC_USER not specified as second param"
test -z "$INPUT" && die "No path specified"
test -f "$INPUT" || die "Not a file: $INPUT"
ext="${INPUT##*.}"
(test "$ext" = "zip" || test "$ext" = "pkg") || die "Unrecognized extension: $ext"
LOGFILE=$(mktemp -t mac_notarize_log)
AC_PASS="@keychain:AC_PASSWORD"
echo "Logs at $LOGFILE"
NOTARIZE_UUID=$(xcrun altool --notarize-app \
--primary-bundle-id "com.ridiculousfish.fish-shell" \
--username "$AC_USER" \
--password "$AC_PASS" \
--file "$INPUT" 2>&1 |
tee -a "$LOGFILE" |
get_req_uuid)
test -z "$NOTARIZE_UUID" && cat "$LOGFILE" && die "Could not get RequestUUID"
echo "RequestUUID: $NOTARIZE_UUID"
success=0
for i in $(seq 20); do
echo "Checking progress..."
PROGRESS=$(xcrun altool --notarization-info "${NOTARIZE_UUID}" \
-u "$AC_USER" \
-p "$AC_PASS" 2>&1 |
tee -a "$LOGFILE")
echo "${PROGRESS}" | tail -n 1
if [ $? -ne 0 ] || [[ "${PROGRESS}" =~ "Invalid" ]] ; then
echo "Error with notarization. Exiting"
break
fi
if ! [[ "${PROGRESS}" =~ "in progress" ]]; then
success=1
break
else
echo "Not completed yet. Sleeping for 30 seconds."
fi
sleep 30
done
if [ $success -eq 1 ] ; then
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
fi
echo "Processed $INPUT"
if test "$ext" = "zip"; then
spctl -a -v "$STAPLE_TARGET"
fi

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,39 +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 and MAC_PRODUCTSIGN_ID, or default for ad-hoc.
# Note the :- means "or default" and the following - is the value.
MAC_CODESIGN_ID=${MAC_CODESIGN_ID:--}
MAC_PRODUCTSIGN_ID=${MAC_PRODUCTSIGN_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"
{ cd "$PKGDIR/build" && cmake -DMAC_INJECT_GET_TASK_ALLOW=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMAC_CODESIGN_ID="${MAC_CODESIGN_ID}" "$SRC_DIR" && make -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"
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 signed_fish_macapp && zip -r "$OUTPUT_PATH/fish-$VERSION.app.zip" fish.app; }
rm -r "$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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -1,12 +1,11 @@
{\rtf1\ansi\ansicpg1252\cocoartf1485\cocoasubrtf410
{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;\f1\fnil\fcharset0 Menlo-Regular;}
{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf370
{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 Monaco;}
{\colortbl;\red255\green255\blue255;}
{\*\expandedcolortbl;\csgenericrgb\c100000\c100000\c100000;}
{\info
{\author dlkfjslfjsfdlkfk}}\margl1440\margr1440\vieww10800\viewh8400\viewkind0
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural
\f0\fs30 \cf0 Fish is a smart and user friendly command line shell. For more information, visit {\field{\*\fldinst{HYPERLINK "https://fishshell.com"}}{\fldrslt https://fishshell.com}}\
\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/
@@ -14,7 +13,7 @@ fish will be installed into
\f1\fs26 /etc/shells
\f0\fs30 if necessary.\
\
Your default shell will
Your default shell will
\i not
\i0 be changed. To make fish your default, run:\
\

View File

@@ -1,319 +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
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
(?:\[.\]\ )? # optional vi mode prompt
"""
+ (r"prompt\ %d>" % counter), # prompt with counter
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:
if inspect.getmodule(f.frame) is not Message.MODULE:
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"
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()):
""" 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("'name' variable not found in environment" % name)
exe_path = env.get(name)
self.colorize = sys.stdout.isatty()
self.messages = []
self.start_time = None
self.spawn = pexpect.spawn(exe_path, env=env, encoding="utf-8", timeout=timeout)
self.spawn.delaybeforesend = None
self.prompt_counter = 1
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, **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:
res = self.spawn.expect(pat, **kwargs)
when = self.time_since_first_message()
self.messages.append(
Message.received_output(self.spawn.match.group(), when)
)
return res
except pexpect.ExceptionPexpect as err:
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, **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, bumping the prompt counter.
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)
self.expect_re(
get_prompt_re(self.prompt_counter),
pat_desc="prompt %d" % self.prompt_counter,
)
self.prompt_counter += 1
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("")
if sys.stdout.isatty():
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 5 messages.
print("Last 5 messages:")
delta = None
for m in self.messages[-5:]:
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
dir = m.dir
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),
}

View File

@@ -1,110 +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. Cowardly refusing to restyle the entire code base.
echo
exit 1
end
set c_files src/*.h src/*.cpp src/*.c
set fish_files (printf '%s\n' share/***.fish)
set python_files **.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
cp $file $file.new # preserves mode bits
clang-format $file >$file.new
if cmp --quiet $file $file.new
rm $file.new
else
echo $file was NOT correctly formatted
mv $file.new $file
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

View File

@@ -1,3 +0,0 @@
# Ubuntu Xenial (used for Travis CI builds) ships libstdc++ 5.4.0 which contains undefined behaviour
# See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63345
object-size:*bits/stl_tree.h

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/${var}.c)
elseif(_lang STREQUAL "CXX")
set(src ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CheckIncludeFiles/${var}.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,209 +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)
if(APPLE)
include(CheckCXXCompilerFlag)
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()
# 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()
# Get threads.
set(THREADS_PREFER_PTHREAD_FLAG ON)
# FindThreads < 3.4.0 doesn't work for C++-only projects
if(CMAKE_VERSION VERSION_LESS 3.4.0)
enable_language(C)
endif()
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)
include(CMakePushCheckState)
check_cxx_symbol_exists(backtrace_symbols execinfo.h HAVE_BACKTRACE_SYMBOLS)
check_cxx_symbol_exists(clock_gettime time.h HAVE_CLOCK_GETTIME)
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)
# futimens is new in OS X 10.13 but is a weak symbol.
# Don't assume it exists just because we can link - it may be null.
check_cxx_symbol_exists(futimens sys/stat.h HAVE_FUTIMENS)
check_cxx_symbol_exists(futimes sys/time.h HAVE_FUTIMES)
check_cxx_symbol_exists(getifaddrs ifaddrs.h HAVE_GETIFADDRS)
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)
check_cxx_symbol_exists(lrand48_r stdlib.h HAVE_LRAND48_R)
# 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_cxx_symbol_exists(sys_errlist stdio.h HAVE_SYS_ERRLIST)
check_include_file_cxx(sys/ioctl.h HAVE_SYS_IOCTL_H)
check_include_file_cxx(sys/select.h HAVE_SYS_SELECT_H)
check_include_files("sys/types.h;sys/sysctl.h" HAVE_SYS_SYSCTL_H)
check_include_file_cxx(termios.h HAVE_TERMIOS_H) # Needed for TIOCGWINSZ
check_cxx_symbol_exists(wcscasecmp wchar.h HAVE_WCSCASECMP)
check_cxx_symbol_exists(wcsdup wchar.h HAVE_WCSDUP)
check_cxx_symbol_exists(wcslcpy wchar.h HAVE_WCSLCPY)
check_cxx_symbol_exists(wcsncasecmp wchar.h HAVE_WCSNCASECMP)
check_cxx_symbol_exists(wcsndup wchar.h HAVE_WCSNDUP)
# 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)
endif()
if(NOT HAVE_WCSDUP)
check_cxx_symbol_exists(std::wcsdup wchar.h HAVE_STD__WCSDUP)
endif()
if(NOT HAVE_WCSNCASECMP)
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(_sys_errs stdlib.h HAVE__SYS__ERRS)
cmake_push_check_state()
set(CMAKE_EXTRA_INCLUDE_FILES termios.h sys/ioctl.h)
check_type_size("struct winsize" STRUCT_WINSIZE LANGUAGE CXX)
check_cxx_symbol_exists("TIOCGWINSZ" "termios.h;sys/ioctl.h" HAVE_TIOCGWINSZ)
if(STRUCT_WINSIZE GREATER -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()
# Solaris and X/Open-conforming systems have a fixed-args tparm
cmake_push_check_state()
list(APPEND CMAKE_REQUIRED_LIBRARIES ${CURSES_LIBRARY})
check_cxx_source_compiles("
#define TPARM_VARARGS
${TPARM_INCLUDES}
int main () {
tparm( \"\" );
}
"
TPARM_TAKES_VARARGS
)
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<uint64_t> x;
int main() {
return x;
}"
LIBATOMIC_NOT_NEEDED)
IF (NOT LIBATOMIC_NOT_NEEDED)
set(ATOMIC_LIBRARY "atomic")
endif()

View File

@@ -1,87 +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_CACHE_DIR "${SPHINX_ROOT_DIR}/doctrees")
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 ${CMAKE_COMMAND} -E copy_if_different ${SPHINX_SRC_DIR}/_static/pygments.css ${SPHINX_HTML_DIR}/_static/
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SPHINX_SRC_DIR}/_static/custom.css ${SPHINX_HTML_DIR}/_static/
COMMAND env PATH="$<TARGET_FILE_DIR:fish_indent>:$$PATH"
${SPHINX_EXECUTABLE}
-q -b html
-c "${SPHINX_SRC_DIR}"
-d "${SPHINX_CACHE_DIR}"
"${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}
-q -b man
-c "${SPHINX_SRC_DIR}"
-d "${SPHINX_CACHE_DIR}"
"${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,183 +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(rel_datadir ${CMAKE_INSTALL_DATADIR})
set(datadir ${CMAKE_INSTALL_FULL_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)
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 ${CMAKE_CURRENT_BINARY_DIR}/${FBVF} ${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 "*.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})
install(FILES share/lynx.lss DESTINATION ${rel_datadir}/fish/)
# 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()
install(FILES fish.desktop DESTINATION ${rel_datadir}/applications)
install(FILES fish.png DESTINATION ${rel_datadir}/pixmaps)
# Group install targets into a InstallTargets folder
set_property(TARGET build_fish_pc CHECK-FISH-BUILD-VERSION-FILE
test_fishscript
test_prep 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,36 +0,0 @@
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum OS X deployment version")
# Code signing ID on Mac. A default '-' is ad-hoc codesign.
# If this is falsey, codesigning is disabled.
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")
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,75 +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
)

View File

@@ -1,43 +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 bundled with fish")
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()
message(STATUS "Using bundled PCRE2 library")
add_subdirectory(pcre2 EXCLUDE_FROM_ALL)
set(PCRE2_INCLUDE_DIR ${CMAKE_BINARY_DIR}/pcre2)
set(PCRE2_LIB pcre2-${PCRE2_WIDTH})
endif(FISH_USE_SYSTEM_PCRE2)
include_directories(${PCRE2_INCLUDE_DIR})

View File

@@ -1,125 +0,0 @@
# Define fish_tests.
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)
# 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 tests files.
file(GLOB TESTS_FILES tests/*)
add_custom_target(tests_dir DEPENDS tests)
if(NOT FISH_IN_TREE_BUILD)
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)
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)
# Make the directory in which to run tests.
# Also symlink fish to where the tests expect it to be.
# Lastly put fish_test_helper there too.
add_custom_target(tests_buildroot_target
COMMAND ${CMAKE_COMMAND} -E make_directory ${TEST_INSTALL_DIR}
COMMAND DESTDIR=${TEST_INSTALL_DIR} ${CMAKE_COMMAND}
--build ${CMAKE_CURRENT_BINARY_DIR} --target install
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/fish_test_helper
${TEST_INSTALL_DIR}/${CMAKE_INSTALL_PREFIX}/bin
COMMAND ${CMAKE_COMMAND} -E create_symlink
${TEST_INSTALL_DIR}/${CMAKE_INSTALL_PREFIX}
${TEST_ROOT_DIR}
DEPENDS fish fish_test_helper)
if(NOT FISH_IN_TREE_BUILD)
# We need to symlink share/functions for the tests.
# This should be simplified.
add_custom_target(symlink_functions
COMMAND ${CMAKE_COMMAND} -E create_symlink
${CMAKE_CURRENT_SOURCE_DIR}/share/functions
${CMAKE_CURRENT_BINARY_DIR}/share/functions)
add_dependencies(tests_buildroot_target symlink_functions)
else()
add_custom_target(symlink_functions)
endif()
# Prep the environment for running the unit tests.
add_custom_target(test_prep
COMMAND ${CMAKE_COMMAND} -E remove_directory ${TEST_DIR}/data
COMMAND ${CMAKE_COMMAND} -E remove_directory ${TEST_DIR}/home
COMMAND ${CMAKE_COMMAND} -E remove_directory ${TEST_DIR}/temp
COMMAND ${CMAKE_COMMAND} -E make_directory
${TEST_DIR}/data ${TEST_DIR}/home ${TEST_DIR}/temp
DEPENDS tests_buildroot_target tests_dir
USES_TERMINAL)
# Define our individual tests.
# Each test is conceptually independent.
# However when running all tests, we want to run them serially for sanity's sake.
# So define both a normal target, and a serial variant which enforces ordering.
foreach(TESTTYPE test serial_test)
add_custom_target(${TESTTYPE}_low_level
COMMAND env XDG_DATA_HOME=test/data XDG_CONFIG_HOME=test/home ./fish_tests
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS fish_tests
USES_TERMINAL)
add_custom_target(${TESTTYPE}_fishscript
COMMAND cd tests && ${TEST_ROOT_DIR}/bin/fish test.fish
DEPENDS test_prep
USES_TERMINAL)
add_custom_target(${TESTTYPE}_interactive
COMMAND cd tests && ${TEST_ROOT_DIR}/bin/fish interactive.fish
DEPENDS test_prep
USES_TERMINAL)
endforeach(TESTTYPE)
# Now add a dependency chain between the serial versions.
# This ensures they run in order.
add_dependencies(serial_test_fishscript serial_test_low_level)
add_dependencies(serial_test_interactive serial_test_fishscript)
add_custom_target(serial_test_high_level
DEPENDS serial_test_interactive serial_test_fishscript)
# Create the 'test' target.
# Set a policy so CMake stops complaining about the name 'test'.
cmake_policy(PUSH)
if(${CMAKE_VERSION} VERSION_LESS 3.11.0 AND POLICY CMP0037)
cmake_policy(SET CMP0037 OLD)
endif()
add_custom_target(test)
cmake_policy(POP)
add_dependencies(test serial_test_high_level)
# Group test targets into a TestTargets folder
set_property(TARGET test tests_dir
test_low_level
test_fishscript
test_interactive
test_fishscript test_prep
tests_buildroot_target
serial_test_high_level
serial_test_low_level
serial_test_fishscript
serial_test_interactive
symlink_functions
PROPERTY FOLDER cmake/TestTargets)

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,43 +0,0 @@
set(languages de en fr nb nn pl pt_BR sv zh_CN)
include(FeatureSummary)
option(WITH_GETTEXT "translate messages if gettext is available" ON)
if(WITH_GETTEXT)
find_package(Intl QUIET)
find_package(Gettext)
if(GETTEXT_FOUND)
set(HAVE_GETTEXT 1)
include_directories(${Intl_INCLUDE_DIR})
endif()
endif()
add_feature_info(gettext GETTEXT_FOUND "translate messages with gettext")
# Define translations
if(GETTEXT_FOUND)
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()
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

1498
config.guess vendored Executable file

File diff suppressed because it is too large Load Diff

1609
config.sub vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -1,179 +0,0 @@
/* Define to 1 if you have the `backtrace_symbols' function. */
#cmakedefine HAVE_BACKTRACE_SYMBOLS 1
/* Define to 1 if compiled on WSL */
#cmakedefine WSL 1
/* Define to 1 if you have the `clock_gettime' function. */
#cmakedefine HAVE_CLOCK_GETTIME 1
/* Define to 1 if you have the `ctermid_r' function. */
#cmakedefine HAVE_CTERMID_R 1
/* Define to 1 if C++11 thread_local is supported. */
#cmakedefine HAVE_CX11_THREAD_LOCAL 1
/* Define to 1 if you have the `dirfd' function. */
#cmakedefine HAVE_DIRFD 1
/* Define to 1 if you have the <execinfo.h> header file. */
#cmakedefine HAVE_EXECINFO_H 1
/* Define to 1 if you have the `flock' function. */
#cmakedefine HAVE_FLOCK 1
/* Define to 1 if you have the `futimens' function. */
#cmakedefine HAVE_FUTIMENS 1
/* Define to 1 if you have the `futimes' function. */
#cmakedefine HAVE_FUTIMES 1
/* Define to 1 if you have the `getifaddrs' function. */
#cmakedefine HAVE_GETIFADDRS 1
/* Define to 1 if you have the `getpwent' function. */
#cmakedefine HAVE_GETPWENT 1
/* Define to 1 if you have the 'getrusage' function. */
#cmakedefine HAVE_GETRUSAGE 1
/* Define to 1 if you have the `gettext' function. */
#cmakedefine HAVE_GETTEXT 1
/* Define to 1 if you have the `killpg' function. */
#cmakedefine HAVE_KILLPG 1
/* Define to 1 if you have the `lrand48_r' function. */
#cmakedefine HAVE_LRAND48_R 1
/* Define to 1 if you have the `mkostemp' function. */
#cmakedefine HAVE_MKOSTEMP 1
/* Define to 1 if you have the <curses.h> header file. */
#cmakedefine HAVE_CURSES_H 1
/* Define to 1 if you have the <ncurses/curses.h> header file. */
#cmakedefine HAVE_NCURSES_CURSES_H 1
/* Define to 1 if you have the <ncurses.h> header file. */
#cmakedefine HAVE_NCURSES_H 1
/* Define to 1 if you have the <ncurses/term.h> header file. */
#cmakedefine HAVE_NCURSES_TERM_H 1
/* Define to 1 if you have the <siginfo.h> header file. */
#cmakedefine HAVE_SIGINFO_H 1
/* Define to 1 if you have the <spawn.h> header file. */
#cmakedefine HAVE_SPAWN_H 1
/* Define to 1 if you have the `std::wcscasecmp' function. */
#cmakedefine HAVE_STD__WCSCASECMP 1
/* Define to 1 if you have the `std::wcsdup' function. */
#cmakedefine HAVE_STD__WCSDUP 1
/* Define to 1 if you have the `std::wcsncasecmp' function. */
#cmakedefine HAVE_STD__WCSNCASECMP 1
/* Define to 1 if `d_type' is a member of `struct dirent'. */
#cmakedefine HAVE_STRUCT_DIRENT_D_TYPE 1
/* Define to 1 if `st_ctime_nsec' is a member of `struct stat'. */
#cmakedefine HAVE_STRUCT_STAT_ST_CTIME_NSEC 1
/* Define to 1 if `st_mtimespec.tv_nsec' is a member of `struct stat'. */
#cmakedefine HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC 1
/* Define to 1 if `st_mtim.tv_nsec' is a member of `struct stat'. */
#cmakedefine HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC 1
/* Define to 1 if the sys_errlist array is available. */
#cmakedefine HAVE_SYS_ERRLIST 1
/* Define to 1 if you have the <sys/ioctl.h> header file. */
#cmakedefine HAVE_SYS_IOCTL_H 1
/* Define to 1 if you have the <sys/select.h> header file. */
#cmakedefine HAVE_SYS_SELECT_H 1
/* Define to 1 if you have the <sys/sysctl.h> header file. */
#cmakedefine HAVE_SYS_SYSCTL_H 1
/* Define to 1 if you have the <termios.h> header file. */
#cmakedefine HAVE_TERMIOS_H 1
/* Define to 1 if you have the <term.h> header file. */
#cmakedefine HAVE_TERM_H 1
/* Define to 1 if you have the `wcscasecmp' function. */
#cmakedefine HAVE_WCSCASECMP 1
/* Define to 1 if you have the `wcsdup' function. */
#cmakedefine HAVE_WCSDUP 1
/* Define to 1 if you have the `wcslcpy' function. */
#cmakedefine HAVE_WCSLCPY 1
/* Define to 1 if you have the `wcsncasecmp' function. */
#cmakedefine HAVE_WCSNCASECMP 1
/* Define to 1 if you have the `wcsndup' function. */
#cmakedefine HAVE_WCSNDUP 1
/* Define to 1 if you have the `wcstod_l' function. */
#cmakedefine HAVE_WCSTOD_L 1
/* Define to 1 if the winsize struct and TIOCGWINSZ macro exist */
#cmakedefine HAVE_WINSIZE 1
/* Define to 1 if the _nl_msg_cat_cntr symbol is exported. */
#cmakedefine HAVE__NL_MSG_CAT_CNTR 1
/* Define to 1 if std::make_unique is available. */
#cmakedefine HAVE_STD__MAKE_UNIQUE 1
/* Define to 1 if the _sys_errs array is available. */
#cmakedefine HAVE__SYS__ERRS 1
/* Define to 1 to disable ncurses macros that conflict with the STL */
#define NCURSES_NOMACROS 1
/* Define to 1 to disable curses macros that conflict with the STL */
#define NOMACROS 1
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "https://github.com/fish-shell/fish-shell/issues"
/* Define to the full name of this package. */
#define PACKAGE_NAME "fish"
/* Use a variadic tparm on NetBSD curses. */
#cmakedefine TPARM_VARARGS 1
/* Define to 1 if tparm accepts a fixed amount of parameters. */
#cmakedefine TPARM_SOLARIS_KLUDGE 1
/* Enable GNU extensions on systems that have them. */
#ifndef _GNU_SOURCE
# define _GNU_SOURCE 1
#endif
/* The size of wchar_t in bits. */
#define WCHAR_T_BITS ${WCHAR_T_BITS}
/* Define if xlocale.h is required for locale_t or wide character support */
#cmakedefine HAVE_XLOCALE_H 1
/* Enable large inode numbers on Mac OS X 10.5. */
#ifndef _DARWIN_USE_64_BIT_INODE
# define _DARWIN_USE_64_BIT_INODE 1
#endif
#if __GNUC__ >= 3
#ifndef __warn_unused
#define __warn_unused __attribute__ ((warn_unused_result))
#endif
#else
#define __warn_unused
#endif

896
configure.ac Normal file
View File

@@ -0,0 +1,896 @@
#
# This file is the main build configuration file for fish. It is used
# to determine your systems capabilities, and tries to adapt fish to
# take maximum advantage of the services your system offers.
#
# Process this file using the 'autoconf' command to produce a working
# configure script, which should in turn be executed in order to
# configure the build process.
#
AC_INIT(fish,2.0.0,fish-users@lists.sf.net)
conf_arg=$@
#
# List of output variables produced by this configure script
#
AC_SUBST(docdir)
AC_SUBST(HAVE_GETTEXT)
AC_SUBST(LDFLAGS_FISH)
AC_SUBST(LIBS_FISH)
AC_SUBST(LIBS_FISH_INDENT)
AC_SUBST(LIBS_FISH_PAGER)
AC_SUBST(LIBS_FISHD)
AC_SUBST(LIBS_MIMEDB)
AC_SUBST(localedir)
AC_SUBST(optbindirs)
AC_SUBST(prefix)
#
# If needed, run autoconf to regenerate the configure file
#
# This makes sure that after running autoconf once to create the first
# version of configure, we never again need to worry about manually
# running autoconf to handle an updates configure.ac.
#
AC_MSG_CHECKING([if autoconf needs to be run])
if test configure -ot configure.ac; then
AC_MSG_RESULT([yes])
if which autoconf >/dev/null; then
# No need to provide any error messages if autoconf fails, the
# shell and autconf should take care of that themselves
AC_MSG_NOTICE([running autoconf])
if autoconf; then
./configure "$@"
exit
fi
exit 1
else
AC_MSG_ERROR(
[cannot find the autoconf program in your path.
This program needs to be run whenever the configure.ac file is modified.
Please install it and try again.]
)
fi
else
AC_MSG_RESULT([no])
fi
#
# If needed, run autoheader to regenerate config.h.in
#
# This makes sure we never ever have to run autoheader manually. It
# will be run whenever needed automatically.
#
AC_MSG_CHECKING([if autoheader needs to be run])
if test ! -f ./config.h.in -o config.h.in -ot configure.ac; then
AC_MSG_RESULT([yes])
if which autoheader >/dev/null; then
AC_MSG_NOTICE([running autoheader])
autoheader || exit 1
else
AC_MSG_ERROR(
[cannot find the autoheader program in your path.
This program needs to be run whenever the configure.ac file is modified.
Please install it and try again.]
)
fi
else
AC_MSG_RESULT([no])
fi
#
# Set up various programs needed for install
# Note AC_PROG_CXX sets CXXFLAGS if not set, which we want
# So ensure this happens before we modify CXXFLAGS below
#
AC_PROG_CXX([g++ c++])
AC_PROG_CPP
AC_PROG_INSTALL
echo "CXXFLAGS: $CXXFLAGS"
#
# Detect directories which may contain additional headers, libraries
# and commands. This needs to be done early - before Autoconf starts
# to mess with CFLAGS and all the other environemnt variables.
#
# This mostly helps OS X users, since fink usually installs out of
# tree and doesn't update CFLAGS.
#
# It also helps FreeBSD which puts libiconv in /usr/local/lib
for i in /usr/pkg /sw /opt /opt/local /usr/local; do
AC_MSG_CHECKING([for $i/include include directory])
if test -d $i/include; then
AC_MSG_RESULT(yes)
CXXFLAGS="$CXXFLAGS -I$i/include/"
CFLAGS="$CFLAGS -I$i/include/"
else
AC_MSG_RESULT(no)
fi
AC_MSG_CHECKING([for $i/lib library directory])
if test -d $i/lib; then
AC_MSG_RESULT(yes)
LDFLAGS="$LDFLAGS -L$i/lib/ -Wl,-rpath,$i/lib/"
else
AC_MSG_RESULT(no)
fi
AC_MSG_CHECKING([for $i/bin command directory])
if test -d $i/bin; then
AC_MSG_RESULT(yes)
optbindirs="$optbindirs $i/bin"
else
AC_MSG_RESULT(no)
fi
done
#
# Tell autoconf to create config.h header
#
AC_CONFIG_HEADERS(config.h)
#
# This adds markup to the code that results in a few extra compile
# time checks on recent GCC versions. It helps stop a few common bugs.
#
AH_BOTTOM([#if __GNUC__ >= 3
#define __warn_unused __attribute__ ((warn_unused_result))
#define __sentinel __attribute__ ((sentinel))
#else
#define __warn_unused
#define __sentinel
#endif])
#
# Optionally drop gettext support
#
AC_ARG_WITH(
gettext,
AS_HELP_STRING(
[--without-gettext],
[do not translate messages, even if gettext is available]
),
[local_gettext=$withval],
[local_gettext=check]
)
AS_IF([test x$local_gettext != xno],
[ AC_CHECK_PROGS( [found_msgfmt], [msgfmt], [no] )
if test x$found_msgfmt != xno; then
AC_DEFINE([USE_GETTEXT],[1],[Perform string translations with gettext])
elif test "x$local_gettext" != "xcheck" ; then
AC_MSG_FAILURE([--with-gettext was given, but the msgfmt program could not be found])
else
local_gettext=no
fi
],
)
#
# Try to enable large file support. This will make sure that on systems
# where off_t can be either 32 or 64 bit, the latter size is used. On
# other systems, this should do nothing. (Hopefully)
#
CXXFLAGS="$CXXFLAGS -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64"
#
# If we are using gcc, set some flags that increase the odds of the
# compiler producing a working binary...
#
if test "$GCC" = yes; then
#
# -fno-optimize-sibling-calls seems to work around a bug where
# sending a SIGWINCH to fish on NetBSD 3.0 causes fish to exit when
# compiled with GCC 3.3.3. This is probably either a compiler bug
# or a libc bug, but adding this flag seems to fix things for
# now. Long term, the real problem should be tracked down and
# truly fixed, at which point we can remove this silly flag. This
# bug has been verified to not exist on Linux using GCC 3.3.3.
#
GCC_VERSION=$($CC -dumpversion)
GCC_VERSION_MAJOR=$(echo $GCC_VERSION | cut -d'.' -f1)
GCC_VERSION_MINOR=$(echo $GCC_VERSION | cut -d'.' -f2)
GCC_VERSION_PATCH=$(echo $GCC_VERSION | cut -d'.' -f3)
if test "$GCC_VERSION_MAJOR" -le 3; then
if test 0"$GCC_VERSION_MINOR" -le 3; then
if test 0"$GCC_VERSION_PATCH" -le 3; then
CXXFLAGS="$CXXFLAGS -fno-optimize-sibling-calls"
fi
fi
fi
# fish does not use exceptions
# Disabling exceptions saves about 20% (!) of the compiled code size
CXXFLAGS="$CXXFLAGS -fno-exceptions"
#
# -Wall is there to keep me on my toes
#
# Some day...
CXXFLAGS="$CXXFLAGS -Wall"
#
# This is needed in order to get the really cool backtraces on Linux
#
if test `uname` != "Darwin"; then
LDFLAGS_FISH="$LDFLAGS_FISH -rdynamic"
fi
fi
#
# If we are compiling against glibc, set some flags to work around
# some rather stupid attempts to hide prototypes for *wprintf
# functions, as well as prototypes of various gnu extensions.
#
AC_MSG_CHECKING([if we are compiling against glibc])
AC_RUN_IFELSE(
[
AC_LANG_PROGRAM(
[
#include <stdlib.h>
#ifdef __GLIBC__
#define STATUS 0
#else
#define STATUS 1
#endif
],
[
return STATUS;
]
)
],
[glibc=yes],
[glibc=no]
)
if test "$glibc" = yes; then
AC_MSG_RESULT(yes)
#
# This gives us access to prototypes for gnu extensions and C99
# functions if we are compiling agains glibc. All GNU extensions
# that are used must have a fallback implementation available in
# fallback.h, in order to keep fish working on non-gnu platforms.
#
CFLAGS="$CFLAGS -D_GNU_SOURCE=1 -D_ISO99_SOURCE=1"
else
AC_MSG_RESULT(no)
fi
#
# Test cpu for special handling of ppc
#
# This is used to skip use of tputs on ppc systems, since it seemed to
# be broken, at least on older debin-based systems. This is obviously
# not the right way to to detect whether this workaround should be
# used, since it catches far to many systems, but I do not have the
# hardware available to narrow this problem down, and in practice, it
# seems that tputs is never really needed.
#
AC_CANONICAL_TARGET
if test $target_cpu = powerpc; then
AC_DEFINE([TPUTS_KLUDGE],[1],[Evil kludge to get Power based machines to work])
fi
#
# Solaris-specific flags go here
#
AC_MSG_CHECKING([if we are under Solaris])
case $target_os in
solaris*)
AC_DEFINE( __EXTENSIONS__, 1, [Macro to enable additional prototypes under Solaris])
AC_MSG_RESULT(yes)
;;
*)
AC_MSG_RESULT(no)
;;
esac
# Check for Solaris curses tputs having fixed length parameter list.
AC_MSG_CHECKING([if we are using non varargs tparm.])
AC_COMPILE_IFELSE(
[
AC_LANG_PROGRAM(
[
#include <curses.h>
#include <term.h>
],
[
tparm( "" );
]
)
],
[tparm_solaris_kludge=no],
[tparm_solaris_kludge=yes]
)
if test "x$tparm_solaris_kludge" = "xyes"; then
AC_MSG_RESULT(yes)
AC_DEFINE(
[TPARM_SOLARIS_KLUDGE],
[1],
[Define to 1 if tparm accepts a fixed amount of paramters.]
)
else
AC_MSG_RESULT(no)
fi
#
# BSD-specific flags go here
#
AC_MSG_CHECKING([if we are under BSD])
case $target_os in
*bsd*)
AC_DEFINE( __BSD_VISIBLE, 1, [Macro to enable additional prototypes under BSD])
AC_DEFINE( _NETBSD_SOURCE, 1, [Macro to enable additional prototypes under BSD])
AC_MSG_RESULT(yes)
;;
*)
AC_MSG_RESULT(no)
;;
esac
#
# Set up PREFIX and related preprocessor symbols. Fish needs to know
# where it will be installed. One of the reasons for this is so that
# it can make sure the fish installation directory is in the path
# during startup.
#
if [[ "$prefix" = NONE ]]; then
prefix=/usr/local
fi
#
# Set up the directory where the documentation files should be
# installed
#
AC_ARG_VAR( [docdir], [Documentation direcotry] )
if test -z $docdir; then
docdir=$datadir/doc/fish
else
docdir=$docdir
fi
#
# Set up locale directory. This is where the .po files will be
# installed.
#
localedir=$datadir/locale
#
# See if Linux procfs is present. This is used to get extra
# information about running processes.
#
AC_CHECK_FILES([/proc/self/stat])
#
# This is ued to tell the wgetopt library to translate strings. This
# way wgetopt can be dropped into any project without requiring i18n.
#
AC_DEFINE(
[HAVE_TRANSLATE_H],
[1],
[Define to 1 if the wgettext function should be used for translating strings.]
)
#
# Check presense of various libraries. This is done on a per-binary
# level, since including various extra libraries in all binaries only
# because thay are used by some of them can cause extra bloat and
# slower compiles when developing fish.
#
# Check for os dependant libraries for all binaries.
LIBS_COMMON=$LIBS
LIBS=""
AC_SEARCH_LIBS( connect, socket, , [AC_MSG_ERROR([Cannot find the socket library, needed to build this package.] )] )
AC_SEARCH_LIBS( nanosleep, rt, , [AC_MSG_ERROR([Cannot find the rt library, needed to build this package.] )] )
AC_SEARCH_LIBS( pthread_create, pthread, , [AC_MSG_ERROR([Cannot find the pthread library, needed to build this package.] )] )
AC_SEARCH_LIBS( setupterm, [ncurses curses], , [AC_MSG_ERROR([Could not find a curses implementation, needed to build fish])] )
AC_SEARCH_LIBS( [nan], [m], [AC_DEFINE( [HAVE_NAN], [1], [Define to 1 if you have the nan function])] )
LIBS_SHARED=$LIBS
LIBS=$LIBS_COMMON
#
# Check for libraries needed by fish.
#
LIBS_COMMON=$LIBS
LIBS="$LIBS_SHARED"
if test x$local_gettext != xno; then
AC_SEARCH_LIBS( gettext, intl,,)
fi
# Check for libiconv_open if we can't find iconv_open. Silly OS X does
# weird macro magic for the sole purpose of amusing me.
AC_SEARCH_LIBS( iconv_open, iconv, , [AC_SEARCH_LIBS( libiconv_open, iconv, , [AC_MSG_ERROR([Could not find an iconv implementation, needed to build fish])] )] )
LIBS_FISH=$LIBS
LIBS=$LIBS_COMMON
#
# Check for libraries needed by fish_indent.
#
LIBS_COMMON=$LIBS
LIBS="$LIBS_SHARED"
if test x$local_gettext != xno; then
AC_SEARCH_LIBS( gettext, intl,,)
fi
LIBS_FISH_INDENT=$LIBS
LIBS=$LIBS_COMMON
#
# Check for libraries needed by fish_pager.
#
LIBS_COMMON=$LIBS
LIBS="$LIBS_SHARED"
if test x$local_gettext != xno; then
AC_SEARCH_LIBS( gettext, intl,,)
fi
AC_SEARCH_LIBS( iconv_open, iconv, , [AC_SEARCH_LIBS( libiconv_open, iconv, , [AC_MSG_ERROR([Could not find an iconv implementation, needed to build fish])] )] )
LIBS_FISH_PAGER=$LIBS
LIBS=$LIBS_COMMON
#
# Check for libraries needed by fishd.
#
LIBS_COMMON=$LIBS
LIBS="$LIBS_SHARED"
if test x$local_gettext != xno; then
AC_SEARCH_LIBS( gettext, intl,,)
fi
AC_SEARCH_LIBS( iconv_open, iconv, , [AC_SEARCH_LIBS( libiconv_open, iconv, , [AC_MSG_ERROR([Could not find an iconv implementation, needed to build fish])] )] )
LIBS_FISHD=$LIBS
LIBS=$LIBS_COMMON
#
# Check for libraries needed by mimedb.
#
LIBS_COMMON=$LIBS
LIBS="$LIBS_SHARED"
if test x$local_gettext != xno; then
AC_SEARCH_LIBS( gettext, intl,,)
fi
LIBS_MIMEDB=$LIBS
LIBS=$LIBS_COMMON
#
# Check presense of various header files
#
AC_CHECK_HEADERS([getopt.h termios.h sys/resource.h term.h ncurses/term.h ncurses.h curses.h stropts.h siginfo.h sys/select.h sys/ioctl.h execinfo.h spawn.h])
if test x$local_gettext != xno; then
AC_CHECK_HEADERS([libintl.h])
fi
AC_CHECK_HEADER(
[regex.h],
[
AC_DEFINE(
[HAVE_REGEX_H],
[1],
[Define to 1 if you have the <regex.h> header file.]
)
],
[AC_MSG_ERROR([Could not find the header regex.h, needed to build fish])]
)
#
# On some platforms (Solaris 10) adding -std=c99 in turn requires that
# _POSIX_C_SOURCE be defined to 200112L otherwise several
# POSIX-specific, non-ISO-C99 types/prototypes are made unavailable
# e.g. siginfo_t. Defining _XOPEN_SOURCE to 600 is compatible with
# the _POSIX_C_SOURCE value and provides a little assurance that
# extension functions' prototypes are available, e.g. killpg().
#
# Some other platforms (OS X), will remove types/prototypes/macros
# e.g. SIGWINCH if either _POSIX_C_SOURCE or _XOPEN_SOURCE is defined.
#
# This test adds these macros only if they enable a program that uses
# both Posix and non-standard features to compile, and that program
# does not compile without these macros.
#
# We try to make everyone happy.
#
# The ordering of the various autoconf tests is very critical as well:
#
# * This test needs to be run _after_ header detection tests, so that
# the proper headers are included.
#
# * This test needs to be run _before_ testing for the presense of any
# prototypes or other language functinality.
#
# * This test should be (but does not need to be) run after the
# conditional definition of __EXTENSIONS__, to avoid redundant tests.
#
XCFLAGS="$CXXFLAGS"
echo checking how to use -D_XOPEN_SOURCE=600 and -D_POSIX_C_SOURCE=200112L...
local_found_posix_switch=no
for i in "" "-D_POSIX_C_SOURCE=200112L" "-D_XOPEN_SOURCE=600 -D_POSIX_C_SOURCE=200112L"; do
AC_MSG_CHECKING( if switches \"$i\" works)
CFLAGS="$XCFLAGS $i"
#
# Try to run this program, which should test various extensions
# and Posix functionality. If this program works, then everything
# should work. Hopefully.
#
AC_TRY_LINK(
[
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
/* POSIX, C89 and C99: POSIX extends this header.
* For: kill(), killpg(), siginfo_t, sigset_t,
* struct sigaction, sigemptyset(), sigaction(),
* SIGIO and SIGWINCH. */
#include <signal.h>
#ifdef HAVE_SIGINFO_H
/* Neither POSIX, C89 nor C99: Solaris-specific (others?).
* For: siginfo_t (also defined by signal.h when in
* POSIX/extensions mode). */
#include <siginfo.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
/* As above (under at least Linux and FreeBSD). */
#include <sys/ioctl.h>
#endif
],
[
/* Avert high-level optimisation, by making the program's
* return value depend on all tested identifiers. */
long ret = 0;
/* POSIX only: might be unhidden by _POSIX_C_SOURCE. */
struct sigaction sa;
sigset_t ss;
siginfo_t info;
ret += (long)(void *)&info + kill( 0, 0 ) +
sigaction( 0, &sa, 0 ) + sigemptyset( &ss );
/* Extended-POSIX: might be unhidden by _XOPEN_SOURCE. */
ret += killpg( 0, 0 );
/* Non-standard: might be hidden by the macros. */
{
struct winsize termsize;
ret += (long)(void *)&termsize;
ret += SIGWINCH + TIOCGWINSZ + SIGIO;
}
return ret;
],
local_cv_use__posix_c_source=yes,
local_cv_use__posix_c_source=no,
)
if test x$local_cv_use__posix_c_source = xyes; then
AC_MSG_RESULT( yes )
local_found_posix_switch=yes
break;
else
AC_MSG_RESULT( no )
fi
done
#
# We didn't find any combination of switches that worked - revert to
# no switches and hope that the fallbacks work. A warning will be
# printed at the end of the configure script.
#
if test ! x$local_found_posix_switch = xyes; then
CFLAGS="$XCFLAGS"
fi
#
# Check for presense of various functions used by fish
#
AC_CHECK_FUNCS( wcsdup wcsndup wcslen wcscasecmp wcsncasecmp fwprintf )
AC_CHECK_FUNCS( futimes wcwidth wcswidth wcstok fputwc fgetwc )
AC_CHECK_FUNCS( wcstol wcslcat wcslcpy lrand48_r killpg )
AC_CHECK_FUNCS( backtrace backtrace_symbols sysconf getifaddrs )
if test x$local_gettext != xno; then
AC_CHECK_FUNCS( gettext dcgettext )
#
# The Makefile also needs to know if we have gettext, so it knows if
# the translations should be installed.
#
AC_CHECK_FUNC( gettext, HAVE_GETTEXT=1, HAVE_GETTEXT=0 )
fi
#
# Here follows a list of small programs used to test for various
# features that Autoconf doesn't tell us about
#
#
# Check if realpath accepts null for its second argument
#
AC_MSG_CHECKING([if realpath accepts null for its second argument])
AC_RUN_IFELSE(
[
AC_LANG_PROGRAM(
[
#include <limits.h>
#include <errno.h>
#include <stdlib.h>
],
[
int status;
char *res;
res = realpath( "somefile", 0 );
status = !(res != 0 || errno == ENOENT);
exit( status );
]
)
],
[have_realpath_null=yes],
[have_realpath_null=no]
)
if test "$have_realpath_null" = yes; then
AC_MSG_RESULT(yes)
AC_DEFINE(
[HAVE_REALPATH_NULL],
[1],
[Define to 1 if realpath accepts null for its second argument.]
)
else
AC_MSG_RESULT(no)
fi
#
# Check if struct winsize and TIOCGWINSZ exist
#
AC_MSG_CHECKING([if struct winsize and TIOCGWINSZ exist])
AC_LINK_IFELSE(
[
AC_LANG_PROGRAM(
[
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
],
[
struct winsize termsize = {0};
TIOCGWINSZ;
]
)
],
[
AC_MSG_RESULT(yes);
AC_DEFINE([HAVE_WINSIZE], [1], [Define to 1 if the winsize struct and TIOCGWINSZ macro exist])
],
[
AC_MSG_RESULT(no)
]
)
#
# If we have a fwprintf in libc, test that it actually works. As of
# March 2006, it is broken under Dragonfly BSD.
#
if test "$ac_cv_func_fwprintf" = yes; then
AC_MSG_CHECKING([if fwprintf is broken])
AC_RUN_IFELSE(
[
AC_LANG_PROGRAM(
[
#include <stdlib.h>
#include <stdio.h>
#include <locale.h>
#include <wchar.h>
],
[
setlocale( LC_ALL, "" );
fwprintf( stderr, L"%ls%ls", L"", L"fish:" );
]
)
],
[
AC_MSG_RESULT(no)
],
[
AC_MSG_RESULT([yes])
AC_DEFINE([HAVE_BROKEN_FWPRINTF], [1], [Define to 1 one if the implemented fwprintf is broken])
]
)
fi
# Check for _nl_msg_cat_cntr symbol
AC_MSG_CHECKING([for _nl_msg_cat_cntr symbol])
AC_TRY_LINK(
[
#if HAVE_LIBINTL_H
#include <libintl.h>
#endif
],
[
extern int _nl_msg_cat_cntr;
int tmp = _nl_msg_cat_cntr;
exit(tmp);
],
have__nl_msg_cat_cntr=yes,
have__nl_msg_cat_cntr=no
)
if test "$have__nl_msg_cat_cntr" = yes; then
AC_MSG_RESULT(yes)
AC_DEFINE(
[HAVE__NL_MSG_CAT_CNTR],
[1],
[Define to 1 if the _nl_msg_cat_cntr symbol is exported.]
)
else
AC_MSG_RESULT(no)
fi
# Check for __environ symbol
AC_MSG_CHECKING([for __environ symbol])
AC_TRY_LINK(
[
#include <unistd.h>
],
[
extern char **__environ;
char **tmp = __environ;
exit(tmp!=0);
],
have___environ=yes,
have___environ=no
)
if test "$have___environ" = yes; then
AC_MSG_RESULT(yes)
AC_DEFINE(
[HAVE___ENVIRON],
[1],
[Define to 1 if the __environ symbol is exported.]
)
else
AC_MSG_RESULT(no)
fi
# Check if getopt_long exists and works
AC_MSG_CHECKING([if getopt_long exists and works])
AC_TRY_LINK(
[
#if HAVE_GETOPT_H
#include <getopt.h>
#endif
],
[
static struct option
long_options[] =
{
0, 0, 0, 0
}
;
int opt = getopt_long( 0,
0,
0,
long_options,
0 );
],
have_working_getopt_long=yes,
have_working_getopt_long=no
)
if test "$have_working_getopt_long" = yes; then
AC_MSG_RESULT(yes)
AC_DEFINE(
[HAVE_WORKING_GETOPT_LONG],
[1],
[Define to 1 if getopt_long exists and works.]
)
else
AC_MSG_RESULT(no)
fi
# Check if del_curterm is broken - in that case we redefine
# del_curterm as a no-op, to avoid a double-free
AC_MSG_CHECKING([If del_curterm is broken])
case $target_os in
*bsd*)
AC_MSG_RESULT(yes)
AC_DEFINE(
[HAVE_BROKEN_DEL_CURTERM],
[1],
[del_curterm is broken, redefine it to a no-op to avoid a double-free bug]
)
;;
*)
AC_MSG_RESULT(no)
;;
esac
# Tell the world what we know
AC_CONFIG_FILES([Makefile fish.spec])
AC_OUTPUT
if test ! x$local_found_posix_switch = xyes; then
echo "Can't find a combination of switches to enable common extensions like detecting window size."
echo "Some fish features may be disabled."
fi
echo "fish is now configured."
echo "Use 'make' and 'make install' to build and install fish."

121
create_wajig_completions.py Executable file
View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python
# -*- python -*-
# Program to generate fish completion function for wajig.
# It runs 'wajig command' and analyzes the output to build a
# completion file which it writes to stdout.
# To use the result, direct stdout to
# ~/.fish.d/completions/wajig.fish.
# Author Reuben Thomas, from Don Rozenberg's bash_completion.py and
# fish's apt-get.fish.
import os
import re
import pprint
pp = pprint.PrettyPrinter()
def escape_quotes(s):
return re.sub('\'', '\\\'', s)
# Run wajig command
f = os.popen('wajig commands', 'r')
lines = f.readlines()
option_patt = r'^-([a-z]*)\|--([a-z]*) +([^ ].*)'
option_patt_r = re.compile(option_patt)
command_patt = r'^([a-z-]*) +([^ ].*)'
command_patt_r = re.compile(command_patt)
os_str = []
os_str.append('')
ol_str = []
ol_str.append('')
oh_str = []
oh_str.append('')
o_i = 0
c_str = []
c_str.append('')
ch_str = []
ch_str.append('')
c_i = 0
for l in lines:
l = l.strip()
if l == '' or l.find(':') > -1 or l.find('Run') == 0:
continue
if l.find('-') == 0:
mo = option_patt_r.search(l)
if mo == None:
continue
os_str[o_i] = mo.group(1)
os_str.append('')
ol_str[o_i] = mo.group(2)
ol_str.append('')
oh_str[o_i] = escape_quotes(mo.group(3))
oh_str.append('')
o_i += 1
else:
mo = command_patt_r.search(l)
if mo == None:
continue
c_str[c_i] = mo.group(1)
c_str.append('')
ch_str[c_i] = escape_quotes(mo.group(2))
ch_str.append('')
c_i += 1
# For debugging, print the commands and options.
#print
#pp.pprint(c_str)
#print
#pp.pprint(os_str)
#print
#pp.pprint(ol_str)
part1 = '''function __fish_wajig_no_subcommand -d (N_ 'Test if wajig has yet to be given the subcommand')
for i in (commandline -opc)
if contains -- $i'''
part2 = '''
return 1
end
end
return 0
end
function __fish_wajig_use_package -d (N_ 'Test if wajig command should have packages as potential completion')
for i in (commandline -opc)
if contains -- $i contains bug build build-depend changelog dependents describe detail hold install installr installrs installs list list-files news package purge purge-depend readme recursive recommended reconfigure reinstall remove remove-depend repackage show showinstall showremove showupgrade size sizes source suggested unhold upgrade versions whatis
return 0
end
end
return 1
end
complete -c wajig -n '__fish_wajig_use_package' -a '(__fish_print_packages)' -d (N_ 'Package')'''
wajig = part1
#add the commands.
for i in range(0, len(c_str) - 1):
wajig = "%s %s" % (wajig, c_str[i])
#add part2
wajig = "%s%s" % (wajig, part2)
#add the options.
wajig = "%s%s" % (wajig, os_str[0].lstrip())
for i in range(1, len(os_str) - 1):
wajig = "%s\ncomplete -c apt-get -s %s -l %s -d (N_ '%s')" % (wajig, os_str[i], ol_str[i], oh_str[i])
#add the commands.
for i in range(0, len(c_str) - 1):
wajig = "%s\ncomplete -f -n '__fish_wajig_no_subcommand' -c wajig -a '%s' -d(N_ '%s')" % (wajig, c_str[i], ch_str[i])
#print it all
print wajig

1
debian/compat vendored
View File

@@ -1 +0,0 @@
9

47
debian/control vendored
View File

@@ -1,47 +0,0 @@
Source: fish
Section: shells
Priority: optional
Maintainer: ridiculous_fish <corydoras@ridiculousfish.com>
Uploaders: David Adam <zanchey@ucc.gu.uwa.edu.au>
Build-Depends: debhelper (>= 9.20151004), libncurses5-dev, cmake (>= 3.2.0), gettext,
# Test dependencies
locales-all, python3
# When libpcre2-dev is available on all supported Debian versions, add a dependency on that.
Standards-Version: 4.1.5
Homepage: https://fishshell.com/
Vcs-Git: https://github.com/fish-shell/fish-shell.git
Vcs-Browser: https://github.com/fish-shell/fish-shell
Package: fish
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, fish-common (= ${source:Version}), passwd (>= 4.0.3-10), gettext-base, man-db
Recommends: xsel (>=1.2.0)
Description: friendly interactive shell
Fish is a command-line shell for modern systems, focusing on user-friendliness,
sensibility and discoverability in interactive use. The syntax is simple, but
not POSIX compliant.
Package: fish-common
Architecture: all
Multi-Arch: foreign
Depends: ${misc:Depends}
Recommends: fish, python3 (>= 3.5), python3-distutils
Suggests: xdg-utils
Replaces: fish (<= 2.1.1.dfsg-2)
Description: friendly interactive shell (architecture-independent files)
Fish is a command-line shell for modern systems, focusing on user-friendliness,
sensibility and discoverability in interactive use. The syntax is simple, but
not POSIX compliant.
.
This package contains the common fish files shared by all architectures.
Package: fish-dbg
Architecture: any
Section: debug
Depends: fish (= ${binary:Version}), ${misc:Depends}
Description: debugging symbols for friendly interactive shell
Fish is a command-line shell for modern systems, focusing on user-friendliness,
sensibility and discoverability in interactive use. The syntax is simple, but
not POSIX compliant.
.
This package contains the debugging symbols for fish.

101
debian/copyright vendored
View File

@@ -1,101 +0,0 @@
This work was packaged for Debian by David Adam <zanchey@ucc.gu.uwa.edu.au>
on Thu, 14 Jun 2012 20:33:34 +0800, based on work by James Vega
<jamessan@jamessan.com>. Modifications from the downstream Debian maintainer,
Tristan Seligmann <mithrandi@debian.org>, have also been included.
It was downloaded from:
https://github.com/fish-shell/fish-shell
Upstream Authors:
Axel Liljencrantz
ridiculous_fish
Copyright:
Copyright (C) 2005-2008 Axel Liljencrantz
Copyright (C) 2011-2012 ridiculous_fish
License:
Copyright (C) 2005-2008 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., 51 Franklin St, Fifth Floor, Boston,
MA 02110-1301, USA.
On Debian systems, the complete text of the GNU General
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
Fish contains code from the PCRE2 library to support regular expressions. This
code, created by Philip Hazel, is distributed under the terms of the BSD
license. Copyright © 1997-2015 University of Cambridge.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of the University of Cambridge nor the names of any
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Fish also contains small amounts of code under the OpenBSD license, namely a
version of the function strlcpy, modified for use with wide character strings.
This code is copyrighted by Todd C. Miller (1998). It also contains code from
tmux, copyrighted by Nicholas Marriott <nicm@users.sourceforge.net> (2007), and
made available under an identical license.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Fish contains code from the glibc library, namely the wcstok function
in fallback.c. This code is licensed under the LGPL.
On Debian systems, the complete text of the GNU Lesser General
Public License can be found in `/usr/share/common-licenses/LGPL'.
The Debian packaging is:
Copyright (C) 2005 James Vega <jamessan@jamessan.com>
Copyright (C) 2012 David Adam <zanchey@ucc.gu.uwa.edu.au>
Copyright (C) 2015 Tristan Seligmann <mithrandi@debian.org>
and is licensed under the GPL version 2, see above.

View File

@@ -1,11 +0,0 @@
Document: fish
Title: Debian fish Manual
Author: Axel Liljencrantz <axel@liljencrantz.se>
Abstract: This guide documents fish, a shell
geared towards interactive use.
Section: Shells
Format: HTML
Index: /usr/share/doc/fish/index.html
Files: /usr/share/doc/fish/*.html

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