diff --git a/.gitignore b/.gitignore index de40c0b..fe7357f 100644 --- a/.gitignore +++ b/.gitignore @@ -95,4 +95,5 @@ i686-pc-mingw32/ /lib /build /bin -/doc \ No newline at end of file +/doc +/target \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..404ab65 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] + +name = "rustfmt" +version = "0.0.1" +readme = "README.md" +authors = ["Patrick Walton ", "Jeff Olson "] +tags = ["util"] + +[[bin]] + +name = "rustfmt" +path = "src/main.rs" + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e00cd6d --- /dev/null +++ b/Makefile @@ -0,0 +1,679 @@ +# Rust-Empty: An Makefile to get started with Rust +# https://github.com/bvssvni/rust-empty +# +# The MIT License (MIT) +# +# Copyright (c) 2014 Sven Nilsen +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +SHELL := /bin/bash + +# The default make command. +# Change this to 'lib' if you are building a library. +DEFAULT = exe +# The entry file of library source. +# Change this to support multi-crate source structure. +# For advanced usage, you can rename the file 'rust-empty.mk' +# and call it with 'make -f rust-empty.mk ' from your Makefile. +LIB_ENTRY_FILE = src/main.rs +# The entry file of executable source. +EXE_ENTRY_FILE = src/main.rs + +EXAMPLE_FILES = examples/*.rs +SOURCE_FILES = $(shell test -e src/ && find src -type f) + +COMPILER = rustc + +# For release: +# COMPILER_FLAGS = -O +# For debugging: + COMPILER_FLAGS = -g -D warnings + +RUSTDOC = rustdoc + +# Extracts target from rustc. +TARGET = $(shell rustc --version verbose 2> /dev/null | awk "/host:/ { print \$$2 }") +# TARGET = x86_64-unknown-linux-gnu +# TARGET = x86_64-apple-darwin + +TARGET_LIB_DIR = target/deps/ + +# Ask 'rustc' the file name of the library and use a dummy name if the source has not been created yet. +# The dummy file name is used to trigger the creation of the source first time. +# Next time 'rustc' will return the right file name. +RLIB_FILE = $(shell (rustc --crate-type=rlib --crate-file-name "$(LIB_ENTRY_FILE)" 2> /dev/null) || (echo "dummy.rlib")) +# You can't have quotes around paths because 'make' doesn't see it exists. +RLIB = target/$(RLIB_FILE) +DYLIB_FILE = $(shell (rustc --crate-type=dylib --crate-file-name "$(LIB_ENTRY_FILE)" 2> /dev/null) || (echo "dummy.dylib")) +DYLIB = target/$(DYLIB_FILE) + +# Use 'VERBOSE=1' to echo all commands, for example 'make help VERBOSE=1'. +ifdef VERBOSE + Q := +else + Q := @ +endif + +all: $(DEFAULT) + +help: + $(Q)echo "--- rust-empty (0.6 003)" + $(Q)echo "make run - Runs executable" + $(Q)echo "make exe - Builds main executable" + $(Q)echo "make lib - Both static and dynamic library" + $(Q)echo "make rlib - Static library" + $(Q)echo "make dylib - Dynamic library" + $(Q)echo "make test - Tests library internally and externally" + $(Q)echo "make test-internal - Tests library internally" + $(Q)echo "make test-external - Tests library externally" + $(Q)echo "make bench - Benchmarks library internally and externally" + $(Q)echo "make bench-internal - Benchmarks library internally" + $(Q)echo "make bench-external - Benchmarks library externally" + $(Q)echo "make doc - Builds documentation for library" + $(Q)echo "make git-ignore - Setup files to be ignored by Git" + $(Q)echo "make examples - Builds examples" + $(Q)echo "make cargo-lite-exe - Setup executable package" + $(Q)echo "make cargo-lite-lib - Setup library package" + $(Q)echo "make cargo-exe - Setup executable package" + $(Q)echo "make cargo-lib - Setup library package" + $(Q)echo "make rust-ci-lib - Setup Travis CI Rust library" + $(Q)echo "make rust-ci-exe - Setup Travis CI Rust executable" + $(Q)echo "make rusti - Setup 'rusti.sh' for interactive Rust" + $(Q)echo "make watch - Setup 'watch.sh' for compilation on save" + $(Q)echo "make loc - Count lines of code in src folder" + $(Q)echo "make nightly-install - Installs Rust nightly build" + $(Q)echo "make nightly-uninstall - Uninstalls Rust nightly build" + $(Q)echo "make clean - Deletes binaries and documentation." + $(Q)echo "make clear-project - WARNING: Deletes project files except 'Makefile'" + $(Q)echo "make clear-git - WARNING: Deletes Git setup" + $(Q)echo "make symlink-build - Creates a script for building dependencies" + $(Q)echo "make symlink-info - Symlinked libraries dependency info" + $(Q)echo "make target-dir - Creates directory for current target" + +.PHONY: \ + bench \ + bench-internal \ + bench-external \ + cargo-lib \ + cargo-exe \ + cargo-lite-lib \ + cargo-lite-exe \ + clean \ + clear-git \ + clear-project \ + loc \ + nightly-install \ + nightly-uninstall \ + run \ + rusti \ + rust-ci-lib \ + rust-ci-exe \ + symlink-build \ + symlink-info \ + target-dir \ + test \ + test-internal \ + test-external \ + watch + +nightly-install: + $(Q)cd ~ \ + && curl -s http://www.rust-lang.org/rustup.sh > rustup.sh \ + && ( \ + echo "Rust install-script stored as '~/rustup.sh'" ; \ + read -p "Do you want to install? [y/n]:" -n 1 -r ; \ + echo "" ; \ + if [[ $$REPLY =~ ^[Yy]$$ ]] ; \ + then \ + cat rustup.sh | sudo sh ; \ + fi \ + ) + +nightly-uninstall: + $(Q)cd ~ \ + && curl -s http://www.rust-lang.org/rustup.sh > rustup.sh \ + && ( \ + echo "Rust install-script stored as '~/rustup.sh'" ; \ + read -p "Do you want to uninstall? [y/n]:" -n 1 -r ; \ + echo "" ; \ + if [[ $$REPLY =~ ^[Yy]$$ ]] ; \ + then \ + cat rustup.sh | sudo sh -s -- --uninstall ; \ + fi \ + ) + +cargo-lite-exe: $(EXE_ENTRY_FILE) + $(Q)( \ + test -e cargo-lite.conf \ + && echo "--- The file 'cargo-lite.conf' already exists" \ + ) \ + || \ + ( \ + echo -e "deps = [\n]\n\n[build]\ncrate_root = \"$(EXE_ENTRY_FILE)\"\nrustc_args = []\n" > cargo-lite.conf \ + && echo "--- Created 'cargo-lite.conf' for executable" \ + && cat cargo-lite.conf \ + ) + +cargo-lite-lib: $(LIB_ENTRY_FILE) + $(Q)( \ + test -e cargo-lite.conf \ + && echo "--- The file 'cargo-lite.conf' already exists" \ + ) \ + || \ + ( \ + echo -e "deps = [\n]\n\n[build]\ncrate_root = \"$(LIB_ENTRY_FILE)\"\ncrate_type = \"library\"\nrustc_args = []\n" > cargo-lite.conf \ + && echo "--- Created 'cargo-lite.conf' for library" \ + && cat cargo-lite.conf \ + ) + +cargo-exe: $(EXE_ENTRY_FILE) + $(Q)( \ + test -e Cargo.toml \ + && echo "--- The file 'Cargo.toml' already exists" \ + ) \ + || \ + ( \ + name=$${PWD##/*/} ; \ + readme=$$((test -e README.md && echo -e "readme = \"README.md\"") || ("")) ; \ + echo -e "[package]\n\nname = \"$$name\"\nversion = \"0.0.0\"\n$$readme\nauthors = [\"Your Name \"]\ntags = []\n\n[[bin]]\n\nname = \"$$name\"\npath = \"$(EXE_ENTRY_FILE)\"\n" > Cargo.toml \ + && echo "--- Created 'Cargo.toml' for executable" \ + && cat Cargo.toml \ + ) + +cargo-lib: $(LIB_ENTRY_FILE) + $(Q)( \ + test -e Cargo.toml \ + && echo "--- The file 'Cargo.toml' already exists" \ + ) \ + || \ + ( \ + name=$${PWD##/*/} ; \ + readme=$$((test -e README.md && echo -e "readme = \"README.md\"") || ("")) ; \ + echo -e "[package]\n\nname = \"$$name\"\nversion = \"0.0.0\"\n$$readme\nauthors = [\"Your Name \"]\ntags = []\n\n[[lib]]\n\nname = \"$$name\"\npath = \"$(LIB_ENTRY_FILE)\"\n" > Cargo.toml \ + && echo "--- Created 'Cargo.toml' for executable" \ + && cat Cargo.toml \ + ) + +rust-ci-lib: $(LIB_ENTRY_FILE) + $(Q)( \ + test -e .travis.yml \ + && echo "--- The file '.travis.yml' already exists" \ + ) \ + || \ + ( \ + echo -e "install:\n - wget http://static.rust-lang.org/dist/rust-nightly-x86_64-unknown-linux-gnu.tar.gz -O - | sudo tar zxf - --strip-components 1 -C /usr/local\nscript:\n - make lib\n" > .travis.yml \ + && echo "--- Created '.travis.yml' for library" \ + && cat .travis.yml \ + ) + +rust-ci-exe: $(EXE_ENTRY_FILE) + $(Q)( \ + test -e .travis.yml \ + && echo "--- The file '.travis.yml' already exists" \ + ) \ + || \ + ( \ + echo -e "install:\n - wget http://static.rust-lang.org/dist/rust-nightly-x86_64-unknown-linux-gnu.tar.gz -O - | sudo tar zxf - --strip-components 1 -C /usr/local\nscript:\n - make exe\n" > .travis.yml \ + && echo "--- Created '.travis.yml' for executable" \ + && cat .travis.yml \ + ) + +doc: $(SOURCE_FILES) | src/ + $(Q)$(RUSTDOC) $(LIB_ENTRY_FILE) -L "$(TARGET_LIB_DIR)" \ + && echo "--- Built documentation" + +run: exe + $(Q)cd bin/ \ + && ./main + +target-dir: $(TARGET_LIB_DIR) + +exe: bin/main | $(TARGET_LIB_DIR) + +bin/main: $(SOURCE_FILES) | bin/ $(EXE_ENTRY_FILE) + $(Q)$(COMPILER) --target "$(TARGET)" $(COMPILER_FLAGS) $(EXE_ENTRY_FILE) -o bin/main -L "$(TARGET_LIB_DIR)" -L "target" \ + && echo "--- Built executable" \ + && echo "--- Type 'make run' to run executable" + +test: test-internal + $(Q)echo "--- Internal tests succeeded" + +test-external: bin/test-external + $(Q)cd "bin/" \ + && ./test-external + +bin/test-external: $(SOURCE_FILES) | rlib bin/ src/test.rs + $(Q)$(COMPILER) --target "$(TARGET)" $(COMPILER_FLAGS) --test src/test.rs -o bin/test-external -L "$(TARGET_LIB_DIR)" -L "target" \ + && echo "--- Built external test runner" + +test-internal: bin/test-internal + $(Q)cd "bin/" \ + && ./test-internal + +bin/test-internal: $(SOURCE_FILES) | rlib src/ bin/ + $(Q)$(COMPILER) --target "$(TARGET)" $(COMPILER_FLAGS) --test $(LIB_ENTRY_FILE) -o bin/test-internal -L "$(TARGET_LIB_DIR)" -L "target" \ + && echo "--- Built internal test runner" + +bench: bench-internal bench-external + +bench-external: test-external + $(Q)bin/test-external --bench + +bench-internal: test-internal + $(Q)bin/test-internal --bench + +lib: rlib dylib + $(Q)echo "--- Type 'make test' to test library" + +rlib: $(RLIB) + +$(RLIB): $(SOURCE_FILES) | $(LIB_ENTRY_FILE) $(TARGET_LIB_DIR) + $(Q)$(COMPILER) --target "$(TARGET)" $(COMPILER_FLAGS) --crate-type=rlib $(LIB_ENTRY_FILE) -L "$(TARGET_LIB_DIR)" --out-dir "target" \ + && echo "--- Built rlib" + +dylib: $(DYLIB) + +$(DYLIB): $(SOURCE_FILES) | $(LIB_ENTRY_FILE) $(TARGET_LIB_DIR) + $(Q)$(COMPILER) --target "$(TARGET)" $(COMPILER_FLAGS) --crate-type=dylib $(LIB_ENTRY_FILE) -L "$(TARGET_LIB_DIR)" --out-dir "target/" \ + && echo "--- Built dylib" + +bin/: + $(Q)mkdir -p bin + +$(TARGET_LIB_DIR): + $(Q)mkdir -p $(TARGET_LIB_DIR) + +src/: + $(Q)mkdir -p src + +examples-dir: + $(Q)test -e examples \ + || \ + ( \ + mkdir examples \ + && echo -e "fn main() {\n\tprintln!(\"Hello!\");\n}\n" > examples/hello.rs \ + && echo "--- Created examples folder" \ + ) + +rust-dir: + $(Q)mkdir -p .rust + +git-ignore: + $(Q)( \ + test -e .gitignore \ + && echo "--- The file '.gitignore' already exists" \ + ) \ + || \ + ( \ + echo -e ".DS_Store\n*~\n*#\n*.o\n*.so\n*.swp\n*.dylib\n*.dSYM\n*.dll\n*.rlib\n*.dummy\n*.exe\n*-test\n/bin/main\n/bin/test-internal\n/bin/test-external\n/doc/\n/target/\n/build/\n/.rust/\nrusti.sh\nwatch.sh\n/examples/**\n!/examples/*.rs\n!/examples/assets/" > .gitignore \ + && echo "--- Created '.gitignore' for git" \ + && cat .gitignore \ + ) + +examples: $(EXAMPLE_FILES) + +$(EXAMPLE_FILES): lib examples-dir + $(Q)$(COMPILER) --target "$(TARGET)" $(COMPILER_FLAGS) $@ -L "$(TARGET_LIB_DIR)" -L "target" --out-dir examples/ \ + && echo "--- Built '$@' (make $@)" + +$(EXE_ENTRY_FILE): | src/ + $(Q)test -e $(EXE_ENTRY_FILE) \ + || \ + ( \ + echo -e "fn main() {\n\tprintln!(\"Hello world!\");\n}" > $(EXE_ENTRY_FILE) \ + ) + +src/test.rs: | src/ + $(Q)test -e src/test.rs \ + || \ + ( \ + touch src/test.rs \ + ) + +$(LIB_ENTRY_FILE): | src/ + $(Q)test -e $(LIB_ENTRY_FILE) \ + || \ + ( \ + echo -e "#![crate_id = \"\"]\n#![deny(missing_doc)]\n\n//! Documentation goes here.\n" > $(LIB_ENTRY_FILE) \ + ) + +clean: + $(Q)rm -f "$(RLIB)" + $(Q)rm -f "$(DYLIB)" + $(Q)rm -rf "doc/" + $(Q)rm -f "bin/main" + $(Q)rm -f "bin/test-internal" + $(Q)rm -f "bin/test-external" + $(Q)echo "--- Deleted binaries and documentation" + +clear-project: + $(Q)rm -f ".symlink-info" + $(Q)rm -f "cargo-lite.conf" + $(Q)rm -f "Cargo.toml" + $(Q)rm -f ".travis.yml" + $(Q)rm -f "rusti.sh" + $(Q)rm -f "watch.sh" + $(Q)rm -rf "target/" + $(Q)rm -rf "src/" + $(Q)rm -rf "bin/" + $(Q)rm -rf "examples/" + $(Q)rm -rf "doc/" + $(Q)echo "--- Removed all source files, binaries and documentation" \ + && echo "--- Content in project folder" \ + && ls -a + +clear-git: + $(Q)rm -f ".gitignore" + $(Q)rm -rf ".git" + $(Q)echo "--- Removed Git" \ + && echo "--- Content in project folder" \ + && ls -a + +# borrowed from http://stackoverflow.com/q/649246/1256624 +define RUSTI_SCRIPT +#!/bin/bash + +#written by mcpherrin + +while true; do + echo -n "> " + read line + TMP="`mktemp r.XXXXXX`" + $(COMPILER) - -o $$TMP -L "$(TARGET_LIB_DIR)" < rusti.sh \ + && chmod +x rusti.sh \ + && echo "--- Created 'rusti.sh'" \ + && echo "--- Type './rusti.sh' to start interactive Rust" \ + ) + +# borrowed from http://stackoverflow.com/q/649246/1256624 +define WATCH_SCRIPT +#!/bin/bash + +#written by zzmp + +# This script will recompile a rust project using `make` +# every time something in the specified directory changes. + +# Watch files in infinite loop +watch () { + UNAME=$$(uname) + if [ -e "$$2" ]; then + echo "Watching files in $$2.." + CTIME=$$(date "+%s") + while :; do + sleep 1 + for f in `find $$2 -type f -name "*.rs"`; do + if [[ $$UNAME == "Darwin" ]]; then + st_mtime=$$(stat -f "%m" "$$f") + elif [[ $$UNAME == "FreeBSD" ]]; then + st_mtime=$$(stat -f "%m" "$$f") + else + st_mtime=$$(stat -c "%Y" "$$f") + fi + if [ $$st_mtime -gt $$CTIME ]; then + CTIME=$$(date "+%s") + echo "~~~ Rebuilding" + $$1 + if [ ! $$? -eq 0 ]; then + echo "" + fi + fi + done + done + else + echo "$$2 is not a valid directory" + fi +} + +# Capture user input with defaults +CMD=$${1:-make} +DIR=$${2:-src} + +if [ $${CMD:0:2} = '-h' ]; then +echo ' +This script will recompile a rust project using `make` +every time something in the specified directory changes. + +Use: ./watch.sh [CMD] [DIR] +Example: ./watch.sh "make run" src + +CMD: Command to execute + Complex commands may be passed as strings + `make` by default +DIR: Directory to watch + src by default + +If DIR is supplied, CMD must be as well.\n' +else + watch "$$CMD" "$$DIR" +fi + +endef +export WATCH_SCRIPT + +watch: $(TARGET_LIB_DIR) + $(Q)( \ + test -e watch.sh \ + && echo "--- The file 'watch.sh' already exists" \ + ) \ + || \ + ( \ + echo -e "$$WATCH_SCRIPT" > watch.sh \ + && chmod +x watch.sh \ + && echo "--- Created 'watch.sh'" \ + && echo "--- Type './watch.sh' to start compilation on save" \ + && echo "--- Type './watch.sh -h' for more options" \ + ) + +# borrowed from http://stackoverflow.com/q/649246/1256624 +define SYMLINK_BUILD_SCRIPT +#!/bin/bash +# written by bvssvni +# Modify the setting to do conditional compilation. +# For example "--cfg my_feature" +SETTINGS="" +# ================================================ + +MAKE=make +if [ "$$OS" == "Windows_NT" ]; then + MAKE=mingw32-make +fi + +# Checks if an item exists in an array. +# Copied from http://stackoverflow.com/questions/3685970/check-if-an-array-contains-a-value +function contains() { + local n=$$# + local value=$${!n} + for ((i=1;i < $$#;i++)) { + if [ "$${!i}" == "$${value}" ]; then + echo "y" + return 0 + fi + } + echo "n" + return 1 +} + +# This is a counter used to insert dependencies. +# It is global because we need an array of all the +# visited dependencies. +i=0 +function build_deps { + local current=$$(pwd) + for symlib in $$(find $(TARGET_LIB_DIR) -type l) ; do + cd $$current + echo $$symlib + local original_file=$$(readlink $$symlib) + local original_dir=$$(dirname $$original_file) + cd $$original_dir + + # Go to the git root directory. + local current_git_dir=$$(git rev-parse --show-toplevel) + echo "--- Parent $$current" + echo "--- Child $$current_git_dir" + cd $$current_git_dir + + # Skip building if it is already built. + if [ $$(contains "$${git_dir[@]}" $$current_git_dir) == "y" ]; then + echo "--- Visited $$current_git_dir" + continue + fi + + # Remember git directory to not build it twice + git_dir[i]=$$current_git_dir + let i+=1 + + # Visit the symlinks and build the dependencies + build_deps + + # First check for a 'build.sh' script with default settings. + # Check for additional 'rust-empty.mk' file. \ + # Compile with the settings flags. \ + # If no other options, build with make. + ( \ + test -e build.sh \ + && ./build.sh \ + ) \ + || \ + ( \ + test -e rust-empty.mk \ + && $$MAKE -f rust-empty.mk clean \ + && $$MAKE -f rust-empty.mk \ + ) \ + || \ + ( \ + echo "--- Building $$current_git_dir" \ + && $$MAKE clean \ + && $$MAKE \ + ) + done + cd $$current +} + +# Mark main project as visited to avoid infinite loop. +git_dir[i]=$$(pwd) +let i+=1 +if [ "$$1" == "deps" ]; then + build_deps +fi + +echo "--- Building $$(pwd)" +( \ + test -e rust-empty.mk \ + && $$MAKE -f rust-empty.mk clean \ + && $$MAKE -f rust-empty.mk COMPILER_FLAGS+="$$SETTINGS" \ +) \ +|| \ +( \ + $$MAKE clean + $$MAKE COMPILER_FLAGS+="$$SETTINGS" +) + +endef +export SYMLINK_BUILD_SCRIPT + +symlink-build: + $(Q)( \ + test -e build.sh \ + && echo "--- The file 'build.sh' already exists" \ + ) \ + || \ + ( \ + echo -e "$$SYMLINK_BUILD_SCRIPT" > build.sh \ + && chmod +x build.sh \ + && echo "--- Created 'build.sh'" \ + && echo "--- Type './build.sh deps' to build everything" \ + ) + +loc: + $(Q)echo "--- Counting lines of .rs files in 'src' (LOC):" \ + && find src/ -type f -name "*.rs" -exec cat {} \; | wc -l + +# Finds the original locations of symlinked libraries and +# prints the commit hash with remote branches containing that commit. +symlink-info: + $(Q) current=$$(pwd) ; \ + for symlib in $$(find $(TARGET_LIB_DIR) -type l) ; do \ + cd $$current ; \ + echo $$symlib ; \ + original_file=$$(readlink $$symlib) ; \ + original_dir=$$(dirname $$original_file) ; \ + cd $$original_dir ; \ + commit=$$(git rev-parse HEAD) ; \ + echo $$commit ; \ + echo "origin:" ; \ + git config --get remote.origin.url ; \ + echo "upstream:" ; \ + git config --get remote.upstream.url ; \ + echo "available in remote branches:" ; \ + git branch -r --contains $$commit ; \ + echo "" ; \ + done \ + > .symlink-info \ + && cd $$current \ + && echo "--- Created '.symlink-info'" \ + && cat .symlink-info diff --git a/README.md b/README.md index 1df5fe4..37d27ae 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,25 @@ ### Building `rustfmt` -With a recent build of `rustc`: +With a recent build of `rustc` and `cargo` installed: ~~~~ -rustc rustfmt.rs +cargo build ~~~~ -This will give you a `rustfmt` binary that behaves as detailed below. +Failing a cargo install, the project can be built with: + +~~~~ +make +~~~~ + +or + +~~~~ +rustc src/main.rs +~~~~ + +This will give you a `rustfmt` or `main` binary that behaves as detailed below. ### Functionality diff --git a/rustfmt.rs b/rustfmt.rs deleted file mode 100644 index ac9344c..0000000 --- a/rustfmt.rs +++ /dev/null @@ -1,6 +0,0 @@ -// rustfmt/rustfmt.rs - -extern crate syntax; - -mod main; - diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..184e3d7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,43 @@ +// rustfmt/main.rs +#![crate_id="http://github.com/pcwalton/rustfmt#rustfmt:0.0.1"] +#![desc = "Rust code formatter"] +#![license = "MIT"] +#![feature(macro_rules)] + +extern crate syntax; + +use std::io; +use std::str; +use syntax::parse::lexer; +use syntax::parse; + +mod rustfmt; +#[cfg(test)] +mod test; + +/// The Main Function +pub fn main() { + let source = io::stdin().read_to_end().unwrap(); + let source = str::from_utf8(source.as_slice()).unwrap(); + + // nothing special + let session = parse::new_parse_sess(); + let filemap = parse::string_to_filemap(&session, source.to_string(), "".to_string()); + let lexer = lexer::StringReader::new(&session.span_diagnostic, filemap); + let mut stdout = io::stdio::stdout(); + let mut formatter = rustfmt::Formatter::new(lexer, &mut stdout); + + loop { + match formatter.next_token() { + Ok(true) => { + match formatter.parse_production() { + Err(e) => fail!(e), + _ => {} + } + }, + Ok(false) => break, + Err(e) => fail!(e) + } + } +} + diff --git a/main.rs b/src/rustfmt.rs similarity index 63% rename from main.rs rename to src/rustfmt.rs index 24b9d87..e9955d4 100644 --- a/main.rs +++ b/src/rustfmt.rs @@ -1,21 +1,30 @@ -// rustfmt/main.rs +// rustfmt/rustfmt.rs + +use std::io::Writer; -use std::io; -use std::str; use syntax::parse::lexer::{StringReader, TokenAndSpan}; -use syntax::parse::lexer; use syntax::parse::token::Token; use syntax::parse::token::keywords; use syntax::parse::token; -use syntax::parse; + +macro_rules! try_io( + ($e:expr) => (match $e { + Ok(_) => {}, + Err(err) => return Err( + format!("Err in Formatter: {}: '{}' details: {}", err.to_str(), err.desc, err.detail)) + }) +) static TAB_WIDTH: i32 = 4; +pub type FormatterResult = Result; + enum ProductionToParse { MatchProduction, UseProduction, BracesProduction, ParenthesesProduction, + AttributeProduction } struct LineToken { @@ -142,126 +151,159 @@ impl LogicalLine { } } -struct Formatter<'a> { +pub struct Formatter<'a> { lexer: StringReader<'a>, indent: i32, logical_line: LogicalLine, last_token: Token, + second_previous_token: Token, newline_after_comma: bool, newline_after_brace: bool, + output: &'a mut Writer } impl<'a> Formatter<'a> { - fn new<'a>(lexer: StringReader<'a>) -> Formatter<'a> { + pub fn new<'a>(lexer: StringReader<'a>, output: &'a mut Writer) -> Formatter<'a> { Formatter { lexer: lexer, indent: 0, logical_line: LogicalLine::new(), last_token: token::SEMI, + second_previous_token: token::SEMI, newline_after_comma: false, newline_after_brace: true, + output: output } } fn token_ends_logical_line(&self, line_token: &LineToken) -> bool { + use syntax::parse::lexer::Reader; + match line_token.token_and_span.tok { - token::SEMI | token::RBRACE => true, + token::SEMI => true, + token::RBRACE => { + match self.lexer.peek() { + TokenAndSpan { tok: token::COMMA, sp: _ } => { + false + } + _ => true + } + }, token::COMMA => self.newline_after_comma, - token::LBRACE => self.newline_after_brace, + token::LBRACE => { + match self.lexer.peek() { + TokenAndSpan { tok: token::RBRACE, sp: _ } => { + false + } + _ => self.newline_after_brace + } + }, _ => false, } } fn token_starts_logical_line(&self, line_token: &LineToken) -> bool { match line_token.token_and_span.tok { - token::RBRACE => self.newline_after_brace, + token::RBRACE => { + match (&self.second_previous_token, &self.last_token) { + // suppress newline separating braces in empty match arms + (&token::FAT_ARROW, &token::LBRACE) => false, + _ => self.newline_after_brace + } + }, _ => false, } } - fn parse_tokens_up_to(&mut self, pred: |&token::Token| -> bool) -> bool { - while self.next_token() { + fn parse_tokens_up_to(&mut self, pred: |&token::Token| -> bool) -> FormatterResult { + while try!(self.next_token()) { if pred(&self.last_token) { - return true; + return Ok(true); } } - return false; + return Ok(false); } - fn parse_productions_up_to(&mut self, pred: |&token::Token| -> bool) -> bool { - while self.next_token() { + fn parse_productions_up_to(&mut self, pred: |&token::Token| -> bool) -> FormatterResult { + while try!(self.next_token()) { if pred(&self.last_token) { - return true; + return Ok(true); } - self.parse_production(); + try!(self.parse_production()); } - return false; + return Ok(false); } - fn parse_match(&mut self) -> bool { + fn parse_match(&mut self) -> FormatterResult { // We've already parsed the keyword. Parse until we find a `{`. - if !self.parse_tokens_up_to(|token| *token == token::LBRACE) { - return false; + if !try!(self.parse_tokens_up_to(|token| *token == token::LBRACE)) { + return Ok(false); } let old_newline_after_comma_setting = self.newline_after_comma; self.newline_after_comma = true; - if !self.parse_productions_up_to(|token| *token == token::RBRACE) { - return false; + if !try!(self.parse_productions_up_to(|token| *token == token::RBRACE)) { + return Ok(false); } self.newline_after_comma = old_newline_after_comma_setting; - return true; + return Ok(true); } - fn parse_use(&mut self) -> bool { + fn parse_use(&mut self) -> FormatterResult { let old_newline_after_brace_setting = self.newline_after_brace; self.newline_after_brace = false; // We've already parsed the keyword. Parse until we find a `{`. - if !self.parse_tokens_up_to(|token| *token == token::LBRACE || *token == token::SEMI) { - return false; + if !try!(self.parse_tokens_up_to(|token| *token == token::LBRACE || *token == token::SEMI)) { + return Ok(false); } if self.last_token == token::LBRACE { let old_newline_after_comma_setting = self.newline_after_comma; self.newline_after_comma = false; - if !self.parse_productions_up_to(|token| *token == token::RBRACE) { - return false; + if !try!(self.parse_productions_up_to(|token| *token == token::RBRACE)) { + return Ok(false); } self.newline_after_comma = old_newline_after_comma_setting; } self.newline_after_brace = old_newline_after_brace_setting; - return true; + return Ok(true); } - fn parse_braces(&mut self) -> bool { + fn parse_braces(&mut self) -> FormatterResult { let old_newline_after_comma_setting = self.newline_after_comma; self.newline_after_comma = true; - // We've already parsed the '{'. Parse until we find a '}'. - let result = self.parse_productions_up_to(|token| *token == token::RBRACE); + let result = try!(self.parse_productions_up_to(|token| *token == token::RBRACE)); self.newline_after_comma = old_newline_after_comma_setting; - return result; + return Ok(result); } - fn parse_parentheses(&mut self) -> bool { + fn parse_parentheses(&mut self) -> FormatterResult { let old_newline_after_comma_setting = self.newline_after_comma; self.newline_after_comma = false; // We've already parsed the '('. Parse until we find a ')'. - let result = self.parse_productions_up_to(|token| *token == token::RPAREN); + let result = try!(self.parse_productions_up_to(|token| *token == token::RPAREN)); self.newline_after_comma = old_newline_after_comma_setting; - return result; + return Ok(result); + } + + fn parse_attribute(&mut self) -> FormatterResult { + // Parse until we find a ']'. + let result = try!(self.parse_productions_up_to(|token| *token == token::RBRACKET)); + try!(self.flush_line()); + return Ok(result); } - fn parse_production(&mut self) -> bool { + pub fn parse_production(&mut self) -> FormatterResult { let production_to_parse; match self.last_token { token::IDENT(..) if token::is_keyword(keywords::Match, &self.last_token) => { @@ -272,7 +314,12 @@ impl<'a> Formatter<'a> { } token::LBRACE => production_to_parse = BracesProduction, token::LPAREN => production_to_parse = ParenthesesProduction, - _ => return true, + token::POUND => production_to_parse = AttributeProduction, + token::DOC_COMMENT(_) => { + try!(self.flush_line()); + return Ok(true); + }, + _ => return Ok(true), } match production_to_parse { @@ -280,24 +327,27 @@ impl<'a> Formatter<'a> { UseProduction => return self.parse_use(), BracesProduction => return self.parse_braces(), ParenthesesProduction => return self.parse_parentheses(), + AttributeProduction => return self.parse_attribute() } } - fn next_token(&mut self) -> bool { + pub fn next_token(&mut self) -> FormatterResult { use syntax::parse::lexer::Reader; loop { if self.lexer.is_eof() { - return false; + return Ok(false); } - let last_token = self.lexer.peek(); - self.last_token = last_token.tok.clone(); - let line_token = LineToken::new(last_token); + let next_token = self.lexer.peek(); + let next_tok_copy = next_token.tok.clone(); + let line_token = LineToken::new(next_token); if self.token_starts_logical_line(&line_token) && self.logical_line.tokens.len() > 0 { - self.flush_line(); + try!(self.flush_line()); continue; } + self.second_previous_token = self.last_token.clone(); + self.last_token = next_tok_copy; if self.logical_line.tokens.len() == 0 { self.indent += line_token.preindentation(); @@ -307,44 +357,43 @@ impl<'a> Formatter<'a> { let token_ends_logical_line = self.token_ends_logical_line(&line_token); self.logical_line.tokens.push(line_token); if token_ends_logical_line { - self.flush_line(); + try!(self.flush_line()); } - return true; + return Ok(true); } } - fn flush_line(&mut self) { + fn flush_line(&mut self) -> FormatterResult<()> { + self.logical_line.layout(self.indent); for _ in range(0, self.indent) { - print!(" "); + try_io!(self.output.write_str(" ")); } for i in range(0, self.logical_line.tokens.len()) { - print!("{}", token::to_str(&self.logical_line.tokens.get(i).token_and_span.tok)); + let curr_tok = &self.logical_line.tokens.get(i).token_and_span.tok; + try_io!(self.output.write_str(format!("{}", token::to_str(curr_tok)).as_slice())); + + // collapse empty blocks in match arms + if (curr_tok == &token::LBRACE && i != self.logical_line.tokens.len() -1) && + &self.logical_line.tokens.get(i+1).token_and_span.tok == &token::RBRACE { + continue; + } + // no whitespace after right-brackets, before comma in match arm + if (curr_tok == &token::RBRACE && i != self.logical_line.tokens.len() -1) && + &self.logical_line.tokens.get(i+1).token_and_span.tok == &token::COMMA { + continue; + } for _ in range(0, self.logical_line.whitespace_after(i)) { - print!(" "); + try_io!(self.output.write_str(" ")); } } - println!(""); + try_io!(self.output.write_line("")); self.indent += self.logical_line.postindentation(); self.logical_line = LogicalLine::new(); - } -} - -#[main] -pub fn main() { - let source = io::stdin().read_to_end().unwrap(); - let source = str::from_utf8(source.as_slice()).unwrap(); - - let session = parse::new_parse_sess(); - let filemap = parse::string_to_filemap(&session, source.to_string(), "".to_string()); - let lexer = lexer::StringReader::new(&session.span_diagnostic, filemap); - let mut formatter = Formatter::new(lexer); - - while formatter.next_token() { - formatter.parse_production(); + Ok(()) } } diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..fa6aa5c --- /dev/null +++ b/src/test.rs @@ -0,0 +1,150 @@ +// rustfmt/test.rs +use rustfmt; + +use std::io::MemWriter; +use std::str; +use syntax::parse::lexer; +use syntax::parse; + +fn test_rustfmt(source: &str) -> String { + + // nothing special + let session = parse::new_parse_sess(); + let filemap = parse::string_to_filemap(&session, source.to_string(), "".to_string()); + let lexer = lexer::StringReader::new(&session.span_diagnostic, filemap); + let mut output = MemWriter::new(); + { + let mut formatter = rustfmt::Formatter::new(lexer, &mut output); + loop { + match formatter.next_token() { + Ok(true) => { + match formatter.parse_production() { + Err(e) => fail!(e), + _ => {} + } + }, + Ok(false) => break, + Err(e) => fail!(e) + } + } + } + str::from_utf8(output.unwrap().as_slice()).unwrap().to_string() +} + +#[test] +fn can_format_a_basic_function() { + let result = test_rustfmt("fn main() {}"); + assert_eq!(result, +"fn main() { +} +".to_string()); +} + +#[test] +fn adds_newline_after_attributes() { + let result = test_rustfmt("#[foo]fn main() {}"); + assert_eq!(result, +"#[foo] +fn main() { +} +".to_string()); +} + +#[test] +fn adds_newline_after_doc_comments() { + let result = test_rustfmt("/// The Main function +fn main() {}"); + assert_eq!(result, +"/// The Main function +fn main() { +} +".to_string()); +} + +#[test] +fn adds_newline_after_multiline_doc_comment() { + let result = test_rustfmt( +"/*! The Main function +* some neat info goes here +* ```` +* bleh(); +* ```` +*/ +fn main() {}"); + assert_eq!(result, +"/*! The Main function +* some neat info goes here +* ```` +* bleh(); +* ```` +*/ +fn main() { +} +".to_string()); +} + +#[test] +fn indent_regression_from_port_to_result_api() { + let input = "#![feature(macro_rules)] +extern crate syntax; +use foo; +mod rustfmt; +#[cfg(test)] +mod test; +/// The Main Function +pub fn main() { + foo(); +} +"; + + assert_eq!(input.to_string(), test_rustfmt(input)); +} + +#[test] +fn should_preserve_empty_blocks() { + let input = "match foo { + _ => {} +} +"; + + assert_eq!(input.to_string(), test_rustfmt(input)); +} + +#[test] +fn full_regression() { + let input = "#![feature(macro_rules)] +extern crate syntax; +use foo; +mod rustfmt; +#[cfg(test)] +mod test; +/// The Main Function +pub fn main() { + let source = io::stdin().read_to_end().unwrap(); + let source = str::from_utf8(source.as_slice()).unwrap(); + let session = parse::new_parse_sess(); + let filemap = parse::string_to_filemap(&session, source.to_string(), foo.to_string()); + let lexer = lexer::StringReader::new(&session.span_diagnostic, filemap); + let mut output = io::MemWriter::new(); + { + let mut formatter = rustfmt::Formatter::new(lexer, &mut output); + loop { + match formatter.next_token() { + Ok(true) => { + match formatter.parse_production() { + Err(e) => fail!(e), + _ => {} + } + }, + Ok(false) => break, + Err(e) => fail!(e) + } + } + } + let output = str::from_utf8(output.unwrap().as_slice()).unwrap().to_string(); + print!(bar, output); +} +"; + + assert_eq!(input.to_string(), test_rustfmt(input)); +}