RustBook/ch12-05-working-with-environment-variables.html

496 lines
39 KiB
HTML
Raw Normal View History

2020-01-06 20:57:15 +00:00
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Working with Environment Variables - The Rust Programming Language</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link href="googleFonts/css.css" rel="stylesheet" type="text/css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
<link rel="stylesheet" href="ferris.css">
<link rel="stylesheet" href="theme/2018-edition.css">
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
<ol class="chapter"><li class="expanded affix "><a href="title-page.html">The Rust Programming Language</a></li><li class="expanded affix "><a href="foreword.html">Foreword</a></li><li class="expanded affix "><a href="ch00-00-introduction.html">Introduction</a></li><li class="expanded "><a href="ch01-00-getting-started.html"><strong aria-hidden="true">1.</strong> Getting Started</a></li><li><ol class="section"><li class="expanded "><a href="ch01-01-installation.html"><strong aria-hidden="true">1.1.</strong> Installation</a></li><li class="expanded "><a href="ch01-02-hello-world.html"><strong aria-hidden="true">1.2.</strong> Hello, World!</a></li><li class="expanded "><a href="ch01-03-hello-cargo.html"><strong aria-hidden="true">1.3.</strong> Hello, Cargo!</a></li></ol></li><li class="expanded "><a href="ch02-00-guessing-game-tutorial.html"><strong aria-hidden="true">2.</strong> Programming a Guessing Game</a></li><li class="expanded "><a href="ch03-00-common-programming-concepts.html"><strong aria-hidden="true">3.</strong> Common Programming Concepts</a></li><li><ol class="section"><li class="expanded "><a href="ch03-01-variables-and-mutability.html"><strong aria-hidden="true">3.1.</strong> Variables and Mutability</a></li><li class="expanded "><a href="ch03-02-data-types.html"><strong aria-hidden="true">3.2.</strong> Data Types</a></li><li class="expanded "><a href="ch03-03-how-functions-work.html"><strong aria-hidden="true">3.3.</strong> Functions</a></li><li class="expanded "><a href="ch03-04-comments.html"><strong aria-hidden="true">3.4.</strong> Comments</a></li><li class="expanded "><a href="ch03-05-control-flow.html"><strong aria-hidden="true">3.5.</strong> Control Flow</a></li></ol></li><li class="expanded "><a href="ch04-00-understanding-ownership.html"><strong aria-hidden="true">4.</strong> Understanding Ownership</a></li><li><ol class="section"><li class="expanded "><a href="ch04-01-what-is-ownership.html"><strong aria-hidden="true">4.1.</strong> What is Ownership?</a></li><li class="expanded "><a href="ch04-02-references-and-borrowing.html"><strong aria-hidden="true">4.2.</strong> References and Borrowing</a></li><li class="expanded "><a href="ch04-03-slices.html"><strong aria-hidden="true">4.3.</strong> The Slice Type</a></li></ol></li><li class="expanded "><a href="ch05-00-structs.html"><strong aria-hidden="true">5.</strong> Using Structs to Structure Related Data</a></li><li><ol class="section"><li class="expanded "><a href="ch05-01-defining-structs.html"><strong aria-hidden="true">5.1.</strong> Defining and Instantiating Structs</a></li><li class="expanded "><a href="ch05-02-example-structs.html"><strong aria-hidden="true">5.2.</strong> An Example Program Using Structs</a></li><li class="expanded "><a href="ch05-03-method-syntax.html"><strong aria-hidden="true">5.3.</strong> Method Syntax</a></li></ol></li><li class="expanded "><a href="ch06-00-enums.html"><strong aria-hidden="true">6.</strong> Enums and Pattern Matching</a></li><li><ol class="section"><li class="expanded "><a href="ch06-01-defining-an-enum.html"><strong aria-hidden="true">6.1.</strong> Defining an Enum</a></li><li class="expanded "><a href="ch06-02-match.html"><strong aria-hidden="true">6.2.</strong> The match Control Flow Operator</a></li><li class="expanded "><a href="ch06-03-if-let.html"><strong aria-hidden="true">6.3.</strong> Concise Control Flow with if let</a></li></ol></li><li class="expanded "><a href="ch07-00-managing-growing-projects-with-packages-crates-and-modules.html"><strong aria-hidden="true">7.</strong> Managing Growing Projects with Packages, Crates, and Modules</a></li><li><ol class="section"><li class="expanded "><a href="ch07-01-packages-and-crates.html"><strong aria-hidden="true">7.1.</strong> Packages and Crates</a></li><li class="expanded "><a href="ch07-02-defining-modules-to-control-scope-and-privacy.html"><strong aria-hidden="true">7.2.</strong> Defining Modules to Control Scope and Privacy</a></li><li class="expanded "><a href="ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html"><
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div id="menu-bar-sticky-container">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">The Rust Programming Language</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h2><a class="header" href="#working-with-environment-variables" id="working-with-environment-variables">Working with Environment Variables</a></h2>
<p>Well improve <code>minigrep</code> by adding an extra feature: an option for
case-insensitive searching that the user can turn on via an environment
variable. We could make this feature a command line option and require that
users enter it each time they want it to apply, but instead well use an
environment variable. Doing so allows our users to set the environment variable
once and have all their searches be case insensitive in that terminal session.</p>
<h3><a class="header" href="#writing-a-failing-test-for-the-case-insensitive-search-function" id="writing-a-failing-test-for-the-case-insensitive-search-function">Writing a Failing Test for the Case-Insensitive <code>search</code> Function</a></h3>
<p>We want to add a new <code>search_case_insensitive</code> function that well call when
the environment variable is on. Well continue to follow the TDD process, so
the first step is again to write a failing test. Well add a new test for the
new <code>search_case_insensitive</code> function and rename our old test from
<code>one_result</code> to <code>case_sensitive</code> to clarify the differences between the two
tests, as shown in Listing 12-20.</p>
<p><span class="filename">Filename: src/lib.rs</span></p>
<pre><pre class="playpen"><code class="language-rust">
<span class="boring">#![allow(unused_variables)]
</span><span class="boring">fn main() {
</span>#[cfg(test)]
mod tests {
use super::*;
#[test]
fn case_sensitive() {
let query = &quot;duct&quot;;
let contents = &quot;\
Rust:
safe, fast, productive.
Pick three.
Duct tape.&quot;;
assert_eq!(
vec![&quot;safe, fast, productive.&quot;],
search(query, contents)
);
}
#[test]
fn case_insensitive() {
let query = &quot;rUsT&quot;;
let contents = &quot;\
Rust:
safe, fast, productive.
Pick three.
Trust me.&quot;;
assert_eq!(
vec![&quot;Rust:&quot;, &quot;Trust me.&quot;],
search_case_insensitive(query, contents)
);
}
}
<span class="boring">}
</span></code></pre></pre>
<p><span class="caption">Listing 12-20: Adding a new failing test for the
case-insensitive function were about to add</span></p>
<p>Note that weve edited the old tests <code>contents</code> too. Weve added a new line
with the text <code>&quot;Duct tape.&quot;</code> using a capital D that shouldnt match the query
<code>&quot;duct&quot;</code> when were searching in a case-sensitive manner. Changing the old test
in this way helps ensure that we dont accidentally break the case-sensitive
search functionality that weve already implemented. This test should pass now
and should continue to pass as we work on the case-insensitive search.</p>
<p>The new test for the case-<em>insensitive</em> search uses <code>&quot;rUsT&quot;</code> as its query. In
the <code>search_case_insensitive</code> function were about to add, the query <code>&quot;rUsT&quot;</code>
should match the line containing <code>&quot;Rust:&quot;</code> with a capital R and match the line
<code>&quot;Trust me.&quot;</code> even though both have different casing from the query. This is
our failing test, and it will fail to compile because we havent yet defined
the <code>search_case_insensitive</code> function. Feel free to add a skeleton
implementation that always returns an empty vector, similar to the way we did
for the <code>search</code> function in Listing 12-16 to see the test compile and fail.</p>
<h3><a class="header" href="#implementing-the-search_case_insensitive-function" id="implementing-the-search_case_insensitive-function">Implementing the <code>search_case_insensitive</code> Function</a></h3>
<p>The <code>search_case_insensitive</code> function, shown in Listing 12-21, will be almost
the same as the <code>search</code> function. The only difference is that well lowercase
the <code>query</code> and each <code>line</code> so whatever the case of the input arguments,
theyll be the same case when we check whether the line contains the query.</p>
<p><span class="filename">Filename: src/lib.rs</span></p>
<pre><pre class="playpen"><code class="language-rust">
<span class="boring">#![allow(unused_variables)]
</span><span class="boring">fn main() {
</span>pub fn search_case_insensitive&lt;'a&gt;(query: &amp;str, contents: &amp;'a str) -&gt; Vec&lt;&amp;'a str&gt; {
let query = query.to_lowercase();
let mut results = Vec::new();
for line in contents.lines() {
if line.to_lowercase().contains(&amp;query) {
results.push(line);
}
}
results
}
<span class="boring">}
</span></code></pre></pre>
<p><span class="caption">Listing 12-21: Defining the <code>search_case_insensitive</code>
function to lowercase the query and the line before comparing them</span></p>
<p>First, we lowercase the <code>query</code> string and store it in a shadowed variable with
the same name. Calling <code>to_lowercase</code> on the query is necessary so no matter
whether the users query is <code>&quot;rust&quot;</code>, <code>&quot;RUST&quot;</code>, <code>&quot;Rust&quot;</code>, or <code>&quot;rUsT&quot;</code>, well
treat the query as if it were <code>&quot;rust&quot;</code> and be insensitive to the case.</p>
<p>Note that <code>query</code> is now a <code>String</code> rather than a string slice, because calling
<code>to_lowercase</code> creates new data rather than referencing existing data. Say the
query is <code>&quot;rUsT&quot;</code>, as an example: that string slice doesnt contain a lowercase
<code>u</code> or <code>t</code> for us to use, so we have to allocate a new <code>String</code> containing
<code>&quot;rust&quot;</code>. When we pass <code>query</code> as an argument to the <code>contains</code> method now, we
need to add an ampersand because the signature of <code>contains</code> is defined to take
a string slice.</p>
<p>Next, we add a call to <code>to_lowercase</code> on each <code>line</code> before we check whether it
contains <code>query</code> to lowercase all characters. Now that weve converted <code>line</code>
and <code>query</code> to lowercase, well find matches no matter what the case of the
query is.</p>
<p>Lets see if this implementation passes the tests:</p>
<pre><code class="language-text">running 2 tests
test tests::case_insensitive ... ok
test tests::case_sensitive ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
</code></pre>
<p>Great! They passed. Now, lets call the new <code>search_case_insensitive</code> function
from the <code>run</code> function. First, well add a configuration option to the
<code>Config</code> struct to switch between case-sensitive and case-insensitive search.
Adding this field will cause compiler errors because we arent initializing
this field anywhere yet:</p>
<p><span class="filename">Filename: src/lib.rs</span></p>
<pre><pre class="playpen"><code class="language-rust">
<span class="boring">#![allow(unused_variables)]
</span><span class="boring">fn main() {
</span>pub struct Config {
pub query: String,
pub filename: String,
pub case_sensitive: bool,
}
<span class="boring">}
</span></code></pre></pre>
<p>Note that we added the <code>case_sensitive</code> field that holds a Boolean. Next, we
need the <code>run</code> function to check the <code>case_sensitive</code> fields value and use
that to decide whether to call the <code>search</code> function or the
<code>search_case_insensitive</code> function, as shown in Listing 12-22. Note this still
wont compile yet.</p>
<p><span class="filename">Filename: src/lib.rs</span></p>
<pre><pre class="playpen"><code class="language-rust">
<span class="boring">#![allow(unused_variables)]
</span><span class="boring">fn main() {
</span><span class="boring">use std::error::Error;
</span><span class="boring">use std::fs::{self, File};
</span><span class="boring">use std::io::prelude::*;
</span><span class="boring">
</span><span class="boring">pub fn search&lt;'a&gt;(query: &amp;str, contents: &amp;'a str) -&gt; Vec&lt;&amp;'a str&gt; {
</span><span class="boring"> vec![]
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">pub fn search_case_insensitive&lt;'a&gt;(query: &amp;str, contents: &amp;'a str) -&gt; Vec&lt;&amp;'a str&gt; {
</span><span class="boring"> vec![]
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">pub struct Config {
</span><span class="boring"> query: String,
</span><span class="boring"> filename: String,
</span><span class="boring"> case_sensitive: bool,
</span><span class="boring">}
</span><span class="boring">
</span>pub fn run(config: Config) -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; {
let contents = fs::read_to_string(config.filename)?;
let results = if config.case_sensitive {
search(&amp;config.query, &amp;contents)
} else {
search_case_insensitive(&amp;config.query, &amp;contents)
};
for line in results {
println!(&quot;{}&quot;, line);
}
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<p><span class="caption">Listing 12-22: Calling either <code>search</code> or
<code>search_case_insensitive</code> based on the value in <code>config.case_sensitive</code></span></p>
<p>Finally, we need to check for the environment variable. The functions for
working with environment variables are in the <code>env</code> module in the standard
library, so we want to bring that module into scope with a <code>use std::env;</code> line
at the top of <em>src/lib.rs</em>. Then well use the <code>var</code> function from the <code>env</code>
module to check for an environment variable named <code>CASE_INSENSITIVE</code>, as shown
in Listing 12-23.</p>
<p><span class="filename">Filename: src/lib.rs</span></p>
<pre><pre class="playpen"><code class="language-rust">
<span class="boring">#![allow(unused_variables)]
</span><span class="boring">fn main() {
</span>use std::env;
<span class="boring">struct Config {
</span><span class="boring"> query: String,
</span><span class="boring"> filename: String,
</span><span class="boring"> case_sensitive: bool,
</span><span class="boring">}
</span>
// --snip--
impl Config {
pub fn new(args: &amp;[String]) -&gt; Result&lt;Config, &amp;'static str&gt; {
if args.len() &lt; 3 {
return Err(&quot;not enough arguments&quot;);
}
let query = args[1].clone();
let filename = args[2].clone();
let case_sensitive = env::var(&quot;CASE_INSENSITIVE&quot;).is_err();
Ok(Config { query, filename, case_sensitive })
}
}
<span class="boring">}
</span></code></pre></pre>
<p><span class="caption">Listing 12-23: Checking for an environment variable named
<code>CASE_INSENSITIVE</code></span></p>
<p>Here, we create a new variable <code>case_sensitive</code>. To set its value, we call the
<code>env::var</code> function and pass it the name of the <code>CASE_INSENSITIVE</code> environment
variable. The <code>env::var</code> function returns a <code>Result</code> that will be the successful
<code>Ok</code> variant that contains the value of the environment variable if the
environment variable is set. It will return the <code>Err</code> variant if the
environment variable is not set.</p>
<p>Were using the <code>is_err</code> method on the <code>Result</code> to check whether its an error
and therefore unset, which means it <em>should</em> do a case-sensitive search. If the
<code>CASE_INSENSITIVE</code> environment variable is set to anything, <code>is_err</code> will
return false and the program will perform a case-insensitive search. We dont
care about the <em>value</em> of the environment variable, just whether its set or
unset, so were checking <code>is_err</code> rather than using <code>unwrap</code>, <code>expect</code>, or any
of the other methods weve seen on <code>Result</code>.</p>
<p>We pass the value in the <code>case_sensitive</code> variable to the <code>Config</code> instance so
the <code>run</code> function can read that value and decide whether to call <code>search</code> or
<code>search_case_insensitive</code>, as we implemented in Listing 12-22.</p>
<p>Lets give it a try! First, well run our program without the environment
variable set and with the query <code>to</code>, which should match any line that contains
the word “to” in all lowercase:</p>
<pre><code class="language-text">$ cargo run to poem.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/minigrep to poem.txt`
Are you nobody, too?
How dreary to be somebody!
</code></pre>
<p>Looks like that still works! Now, lets run the program with <code>CASE_INSENSITIVE</code>
set to <code>1</code> but with the same query <code>to</code>.</p>
<p>If youre using PowerShell, you will need to set the environment variable and
run the program in two commands rather than one:</p>
<pre><code class="language-text">$ $env:CASE_INSENSITIVE=1
$ cargo run to poem.txt
</code></pre>
<p>We should get lines that contain “to” that might have uppercase letters:</p>
<pre><code class="language-text">$ CASE_INSENSITIVE=1 cargo run to poem.txt
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/minigrep to poem.txt`
Are you nobody, too?
How dreary to be somebody!
To tell your name the livelong day
To an admiring bog!
</code></pre>
<p>Excellent, we also got lines containing “To”! Our <code>minigrep</code> program can now do
case-insensitive searching controlled by an environment variable. Now you know
how to manage options set using either command line arguments or environment
variables.</p>
<p>Some programs allow arguments <em>and</em> environment variables for the same
configuration. In those cases, the programs decide that one or the other takes
precedence. For another exercise on your own, try controlling case
insensitivity through either a command line argument or an environment
variable. Decide whether the command line argument or the environment variable
should take precedence if the program is run with one set to case sensitive and
one set to case insensitive.</p>
<p>The <code>std::env</code> module contains many more useful features for dealing with
environment variables: check out its documentation to see what is available.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="ch12-04-testing-the-librarys-functionality.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="ch12-06-writing-to-stderr-instead-of-stdout.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a href="ch12-04-testing-the-librarys-functionality.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a href="ch12-06-writing-to-stderr-instead-of-stdout.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script type="text/javascript">
window.playpen_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
<script type="text/javascript" src="ferris.js"></script>
</body>
</html>