├── .eslintrc.json ├── .gitignore ├── README.md ├── config ├── path.config.js ├── webpack.config.js └── webpack.dll.config.js ├── docs ├── app.2b571855c9f3fc4e0f3a.js ├── app.ba0382357b4c97ae1ab2.css ├── book_of_ruby.pdf ├── html │ ├── 0-homepage.html │ ├── 1-author.html │ ├── 10-chapter8.html │ ├── 11-chapter9.html │ ├── 12-chapter10.html │ ├── 13-chapter11.html │ ├── 14-chapter12.html │ ├── 15-chapter13.html │ ├── 16-chapter14.html │ ├── 17-chapter15.html │ ├── 18-chapter16.html │ ├── 19-chapter17.html │ ├── 2-introduction.html │ ├── 20-chapter18.html │ ├── 21-chapter19.html │ ├── 22-chapter20.html │ ├── 23-appendix.html │ ├── 3-chapter1.html │ ├── 4-chapter2.html │ ├── 5-chapter3.html │ ├── 6-chapter4.html │ ├── 7-chapter5.html │ ├── 8-chapter6.html │ ├── 9-chapter7.html │ └── category.json ├── images │ ├── author.png │ ├── book-of-ruby.jpg │ ├── chapter13_debug_recursion.png │ ├── chapter18_debugger.png │ ├── chapter19_blog_test_create.png │ ├── chapter19_blog_test_index.png │ ├── chapter19_blog_test_list.png │ ├── chapter19_rails.png │ ├── chapter2_Class.png │ └── chapter2_Class_Variables.png ├── index.html ├── vendor-manifest.json ├── vendor.js └── vendors │ ├── fonts │ └── .gitkeep │ ├── libs │ └── bootstrap-4.1.3 │ │ └── css │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ └── plugins │ └── compatible-plugin │ ├── html5shiv.min.js │ ├── respond.min.js │ └── selectivizr.min.js ├── index.js ├── markdown ├── 0-homepage.md ├── 1-author.md ├── 10-chapter8.md ├── 11-chapter9.md ├── 12-chapter10.md ├── 13-chapter11.md ├── 14-chapter12.md ├── 15-chapter13.md ├── 16-chapter14.md ├── 17-chapter15.md ├── 18-chapter16.md ├── 19-chapter17.md ├── 2-introduction.md ├── 20-chapter18.md ├── 21-chapter19.md ├── 22-chapter20.md ├── 23-appendix.md ├── 3-chapter1.md ├── 4-chapter2.md ├── 5-chapter3.md ├── 6-chapter4.md ├── 7-chapter5.md ├── 8-chapter6.md └── 9-chapter7.md ├── md_2_html.js ├── package-lock.json ├── package.json └── src ├── app.html ├── app.js ├── app.scss ├── components ├── category.js ├── line-numbers.js └── navigation.js ├── utils ├── base.scss ├── controller.js └── github-gist.css └── vendors ├── fonts └── .gitkeep ├── libs └── bootstrap-4.1.3 │ └── css │ ├── bootstrap-grid.min.css │ ├── bootstrap-grid.min.css.map │ ├── bootstrap-reboot.min.css │ ├── bootstrap-reboot.min.css.map │ ├── bootstrap.min.css │ └── bootstrap.min.css.map └── plugins └── compatible-plugin ├── html5shiv.min.js ├── respond.min.js └── selectivizr.min.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node" : true, 4 | "browser": true, 5 | "es6" : true 6 | }, 7 | "extends" : [ 8 | "eslint:recommended" 9 | ], 10 | "parser" : "babel-eslint", 11 | "parserOptions": { 12 | "ecmaFeatures": { 13 | "experimentalObjectRestSpread": true 14 | }, 15 | "sourceType": "module" 16 | }, 17 | "plugins": [], 18 | "rules" : { 19 | "no-console": [ 20 | "error", 21 | { 22 | "allow": [ 23 | "warn", 24 | "error" 25 | ] 26 | } 27 | ], 28 | "indent": [ 29 | "error", 30 | "tab" 31 | ], 32 | "linebreak-style": [ 33 | "error", 34 | "windows" 35 | ], 36 | "quotes": [ 37 | "error", 38 | "single" 39 | ], 40 | "semi": [ 41 | "error", 42 | "always" 43 | ] 44 | }, 45 | "globals": {} 46 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 《The Book Of Ruby》 - Chinese Edition 2 | 3 | 本项目对开源书籍《The Book Of Ruby》进行了简体中文的翻译,目的: 4 | 5 | 1. 通过本书了解 Ruby 语言的细节和内部机制; 6 | 2. 作为留给自己和国内同样热爱 Ruby 的开发者的一份参考资料,中文版可以快速浏览和查阅; 7 | 3. 算是对开源社区做的一份小小贡献,正所谓 - 给予比接受更快乐! 8 | 9 | ## 在线浏览 10 | 11 | https://wang1212.github.io/the-book-of-ruby/ 12 | 13 | ## 关于本书 14 | 15 |
16 | 17 |
18 | 19 | 书名:《The Book Of Ruby》 20 | 21 | 作者:How Collingbourne 22 | 23 | 页数:425 页 24 | 25 | 出版时间:2011-7-13 26 | 27 | 翻译完成时间:2019.01.13 28 | 29 | 简介:《The Book Of Ruby》是一本免费的 Ruby 编程高级教程。以 PDF 文件格式提供,并且每一个章节的所有例子都伴有可运行的源代码。同时,也有一个介绍来阐述如何在 Steel 或其它任何你喜欢的编辑器/IDE 中运行这些 Ruby 代码。它主要集中于 Ruby 语言的 1.8.x 版本。 30 | 31 | 目录概述: 32 | 33 | - 第一章:字符串、数字、类和对象 - 获取输入和输出,字符串和内嵌表达式,数字和测试语句 if...then,局部变量和全局变量,类和对象,实例变量,消息、方法与多态性,构造方法与对象初始化,查看对象信息。 34 | - 第二章:类的层次结构、属性与变量 - 超类与子类,访问器方法,属性读写,调用超类方法,类变量。 35 | - 第三章:字符串和 Range - 字符串分隔符,字符串处理方法,Range,Range 迭代器。 36 | - 第四章:数组与 Hash - 常用处理方法。 37 | - 第五章:循环和迭代器 - for 循环,多参数迭代,代码块,while 循环,until 循环,loop 循环。 38 | - 第六章:条件语句 - if...then...else,and...or...not,if...elsif,unless,case 语句,=== 方法,catch 与 throw。 39 | - 第七章:方法 - 类方法,类变量,构造方法,单例方法,单例类,重写方法,public、private 和 protected 方法。 40 | - 第八章:参数传递与返回值 - 实例方法,类方法,单例方法,返回值,返回多个值,默认参数和多参数,整数,进出原则,并行赋值,引用传值。 41 | - 第九章:异常处理 - rescue,ensure,else,error 编号,retry,raise。 42 | - 第十章:Block、Proc 和 Lambda - 匿名函数,proc 与 lambda,闭包,yield,嵌套块,优先级规则,块中实例变量,块中局部变量。 43 | - 第十一章:符号 - 符号与字符串,符号和变量,为什么使用符号? 44 | - 第十二章:模块和 mixin - 模块与类,模块方法,命名空间,包含模块,alias 方法,作用域解析符。 45 | - 第十三章:文件与 IO - 打开和关闭文件,文件和目录,赋值文件,目录查询,递归,排序。 46 | - 第十四章:Yaml - 转换成 yaml,嵌套序列,保存 yaml 数据,一个文件包含多个 yaml 文档,yaml 数据库。 47 | - 第十五章:Marshal - 保存与加载数据,保存单例对象,yaml 与单例对象。 48 | - 第十六章:正则表达式 - 匹配组,前后匹配,贪婪匹配,字符串方法,文件操作。 49 | - 第十七章:线程 - 创建线程,运行线程,主线程,线程状态,线程优先级,主线程优先级,互斥。 50 | - 第十八章:调试与测试 - irb、调试、单元测试、断言。 51 | - 第十九章:Ruby On Rails - 安装 RoR、第一个 RoR 应用,创建控制器,创建视图,Rails 标记,MVC。 52 | - 第二十章:动态编程 - 自修改程序,eval,动态添加变量和方法,运行时创建类,绑定,send,移除方法。 53 | 54 | 本书由 SapphireSteel Software 发布,SapphireSteel Software 是用于 Visual Studio 的 Ruby In Steel 集成开发环境的开发者。读者可以复制和发布本书的文本和代码(免费版)。 55 | 56 | ## 引用 57 | 58 | 如要引用该项目,请注明出处,谢谢! 59 | -------------------------------------------------------------------------------- /config/path.config.js: -------------------------------------------------------------------------------- 1 | /*! Path config */ 2 | 3 | const NODE_ENV = process.env.NODE_ENV || 'production'; 4 | 5 | const SRC_DIR = './src/', 6 | DIST_DIR = require('path').resolve(__dirname, '../docs/'); 7 | 8 | module.exports = { 9 | dev: !(NODE_ENV === 'production'), 10 | map: !(NODE_ENV === 'production'), 11 | 12 | src : SRC_DIR, 13 | dist: DIST_DIR 14 | }; -------------------------------------------------------------------------------- /config/webpack.config.js: -------------------------------------------------------------------------------- 1 | const PATH = require('path'); 2 | 3 | const path_config = require('./path.config.js'); 4 | 5 | const webpack = require('webpack'); 6 | 7 | const CopyWebpackPlugin = require('copy-webpack-plugin'), 8 | HtmlWebpackPlugin = require('html-webpack-plugin'), 9 | UglifyJsPlugin = require('uglifyjs-webpack-plugin'), 10 | MiniCssExtractPlugin = require('mini-css-extract-plugin'), 11 | OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'), 12 | ImageminPlugin = require('imagemin-webpack-plugin').default, 13 | ImageminJpeg = require('imagemin-jpeg-recompress'); 14 | 15 | 16 | module.exports = { 17 | mode : path_config.dev ? 'development' : 'production', 18 | target : 'web', 19 | devtool : path_config.map ? 'cheap-module-eval-source-map': 'none', 20 | watch : true, 21 | watchOptions: { 22 | ignored: /node_modules/ 23 | }, 24 | context: PATH.resolve(__dirname, '../'), 25 | entry : { 26 | app: './src/app.js' 27 | }, 28 | output: { 29 | path : path_config.dist, 30 | filename : path_config.dev ? '[name].js': '[name].[chunkhash].js', 31 | chunkFilename: path_config.dev ? '[name].js': '[name].[chunkhash].js' 32 | }, 33 | module: { 34 | rules: [ 35 | { 36 | test : /\.(js|jsx)$/, 37 | exclude: /node_modules/, 38 | use : [ 39 | { 40 | loader : 'babel-loader', 41 | options: { 42 | presets: ['@babel/preset-env'], 43 | plugins: [ 44 | require('@babel/plugin-syntax-dynamic-import'), 45 | require('@babel/plugin-proposal-class-properties'), 46 | require('@babel/plugin-proposal-object-rest-spread') 47 | ], 48 | sourceMaps: true 49 | } 50 | } 51 | ] 52 | }, 53 | { 54 | test : /\.(sa|sc|c)ss$/, 55 | exclude: /node_modules/, 56 | use : [ 57 | path_config.dev ? 'style-loader': MiniCssExtractPlugin.loader, 58 | { 59 | loader : 'css-loader', 60 | options: { 61 | minimize : true, 62 | sourceMap : true, 63 | importLoader: 2 64 | } 65 | }, 66 | { 67 | loader : 'postcss-loader', 68 | options: { 69 | sourceMap: true, 70 | ident : 'postcss', 71 | plugins : () => [ 72 | require('autoprefixer')({ browsers: ['last 2 versions'] }) 73 | ] 74 | } 75 | }, 76 | { 77 | loader : 'sass-loader', 78 | options: { 79 | sourceMap: true 80 | } 81 | } 82 | ] 83 | }, 84 | { 85 | test: /\.(png|jpg|gif)$/, 86 | use : [ 87 | { 88 | loader : 'url-loader', 89 | options: { 90 | limit: 8192 91 | } 92 | } 93 | ] 94 | } 95 | ] 96 | }, 97 | plugins: [ 98 | new webpack.DllReferencePlugin({ 99 | context : '.', 100 | manifest: PATH.join(path_config.dist, './vendor-manifest.json') 101 | }), 102 | new CopyWebpackPlugin([{ 103 | from : './src/vendors', 104 | to : './vendors', 105 | cache: true 106 | }]), 107 | new HtmlWebpackPlugin({ 108 | filename: './index.html', 109 | template: './src/app.html', 110 | chunks : ['app', 'commons'] 111 | }), 112 | new MiniCssExtractPlugin({ 113 | filename : path_config.dev ? '[name].css': '[name].[hash].css', 114 | chunkFilename: path_config.dev ? '[id].css' : '[id].[hash].css', 115 | }), 116 | new ImageminPlugin({ 117 | disable: path_config.dev, 118 | optipng: { 119 | optimizationLevel: 7 120 | }, 121 | gifsicle: { 122 | optimizationLevel: 3, 123 | interlaced : true 124 | }, 125 | jpegtran: null, 126 | svgo : { 127 | plugins: [ 128 | { removeViewBox: true }, 129 | { cleanupIDs: false } 130 | ] 131 | }, 132 | pngquant: {}, 133 | plugins : [ 134 | ImageminJpeg() 135 | ] 136 | }) 137 | ], 138 | resolve: { 139 | alias: { 140 | components: PATH.resolve('./src/components/'), 141 | utils : PATH.resolve('./src/utils/'), 142 | vendors : PATH.resolve('./src/vendors/'), 143 | html : PATH.resolve('./docs/html/') 144 | } 145 | }, 146 | optimization: { 147 | minimizer: [ 148 | new UglifyJsPlugin({ 149 | cache : true, 150 | parallel : true, 151 | sourceMap: true 152 | }), 153 | new OptimizeCSSAssetsPlugin({}) 154 | ], 155 | splitChunks: { 156 | cacheGroups: { 157 | commons: { 158 | chunks : 'initial', 159 | minChunks : 2, 160 | maxInitialRequests: 5, 161 | minSize : 30000, 162 | reuseExistingChunk: true, 163 | }, 164 | /* vendor: { 165 | test : /node_modules/, 166 | chunks : 'initial', 167 | name : 'vendor', 168 | priority: 10, 169 | enforce : true 170 | } */ 171 | } 172 | }, 173 | //runtimeChunk: true 174 | }, 175 | performance: { 176 | hints : 'warning', 177 | assetFilter: assetFilename => { 178 | return path_config.dev ? false: !(/vendor/.test(assetFilename)); 179 | } 180 | } 181 | }; -------------------------------------------------------------------------------- /config/webpack.dll.config.js: -------------------------------------------------------------------------------- 1 | /*! External dependencies packaged individually */ 2 | 3 | const PATH = require('path'); 4 | 5 | const path_config = require('./path.config.js'); 6 | 7 | const webpack = require('webpack'); 8 | 9 | 10 | module.exports = { 11 | mode : path_config.dev ? 'development': 'production', 12 | context: PATH.resolve(__dirname, '../'), 13 | entry : { 14 | vendor: Object.keys(require('../package.json').dependencies) || '' 15 | }, 16 | output: { 17 | path : path_config.dist, 18 | filename: '[name].js', 19 | library : '[name]_lib_[hash]' 20 | }, 21 | plugins: [ 22 | new webpack.DllPlugin({ 23 | name: '[name]_lib_[hash]', 24 | path: PATH.join(path_config.dist, '[name]-manifest.json') 25 | }) 26 | ], 27 | performance: { 28 | hints : 'warning', 29 | assetFilter: assetFilename => { 30 | return !(/vendor/.test(assetFilename)); 31 | } 32 | } 33 | }; -------------------------------------------------------------------------------- /docs/app.2b571855c9f3fc4e0f3a.js: -------------------------------------------------------------------------------- 1 | !function(n){var r={};function o(t){if(r[t])return r[t].exports;var e=r[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,o),e.l=!0,e.exports}o.m=n,o.c=r,o.d=function(t,e,n){o.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},o.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)o.d(n,r,function(t){return e[t]}.bind(null,r));return n},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,"a",e),e},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o.p="",o(o.s=2)}([function(t){t.exports=[{order:0,path:"0-homepage.html",title:"The Book Of Ruby",ctime:"2016-11-03 18:30:00",mtime:"2019-01-16 00:30:00"},{order:1,path:"1-author.html",title:"关于作者",ctime:"2016-11-03 19:00:00",mtime:"2016-11-03 19:00:00"},{order:2,path:"2-introduction.html",title:"序言",ctime:"2016-11-03 20:30:00",mtime:"2016-11-03 20:30:00"},{order:3,path:"3-chapter1.html",title:"第一章",ctime:"2016-11-04 12:30:00",mtime:"2016-11-04 12:30:00"},{order:4,path:"4-chapter2.html",title:"第二章",ctime:"2016-11-06 21:30:00",mtime:"2016-11-06 21:30:00"},{order:5,path:"5-chapter3.html",title:"第三章",ctime:"2016-11-11 22:34:00",mtime:"2016-11-11 22:34:00"},{order:6,path:"6-chapter4.html",title:"第四章",ctime:"2016-11-13 19:37:00",mtime:"2016-11-13 19:37:00"},{order:7,path:"7-chapter5.html",title:"第五章",ctime:"2016-11-17 23:13:00",mtime:"2016-11-17 23:13:00"},{order:8,path:"8-chapter6.html",title:"第六章",ctime:"2016-11-23 21:20:00",mtime:"2016-11-23 21:20:00"},{order:9,path:"9-chapter7.html",title:"第七章",ctime:"2018-11-26 21:26:00",mtime:"2018-11-26 21:26:00"},{order:10,path:"10-chapter8.html",title:"第八章",ctime:"2018-12-01 10:30:00",mtime:"2018-12-01 10:30:00"},{order:11,path:"11-chapter9.html",title:"第九章",ctime:"2018-12-03 21:27:00",mtime:"2018-12-03 21:27:00"},{order:12,path:"12-chapter10.html",title:"第十章",ctime:"2018-12-06 21:50:00",mtime:"2018-12-06 21:50:00"},{order:13,path:"13-chapter11.html",title:"第十一章",ctime:"2018-12-11 01:10:00",mtime:"2018-12-11 01:10:00"},{order:14,path:"14-chapter12.html",title:"第十二章",ctime:"2018-12-13 15:57:00",mtime:"2018-12-13 15:57:00"},{order:15,path:"15-chapter13.html",title:"第十三章",ctime:"2018-12-23 00:49:00",mtime:"2018-12-23 00:49:00"},{order:16,path:"16-chapter14.html",title:"第十四章",ctime:"2018-12-25 00:02:00",mtime:"2018-12-25 00:02:00"},{order:17,path:"17-chapter15.html",title:"第十五章",ctime:"2018-12-27 23:29:00",mtime:"2018-12-27 23:29:00"},{order:18,path:"18-chapter16.html",title:"第十六章",ctime:"2018-12-29 01:50:00",mtime:"2018-12-29 01:50:00"},{order:19,path:"19-chapter17.html",title:"第十七章",ctime:"2018-12-29 19:14:00",mtime:"2018-12-29 19:14:00"},{order:20,path:"20-chapter18.html",title:"第十八章",ctime:"2018-12-31 13:30:00",mtime:"2018-12-31 13:30:00"},{order:21,path:"21-chapter19.html",title:"第十九章",ctime:"2019-01-03 23:00:00",mtime:"2019-01-03 23:00:00"},{order:22,path:"22-chapter20.html",title:"第二十章",ctime:"2019-01-08 00:03:00",mtime:"2019-01-08 00:03:00"},{order:23,path:"23-appendix.html",title:"附录",ctime:"2019-01-11 01:38:00",mtime:"2019-01-11 01:38:00"}]},function(t,e,n){},function(t,e,n){"use strict";n.r(e);n(1);var c=n(0);var r=function(){var n=0'+t.title+""}),document.getElementById("nav").innerHTML=e}function i(){document.querySelectorAll(".file-content pre > code").forEach(function(t){for(var e=getComputedStyle(t),n=+e.height.match(/[\d.]+/),r=+e.lineHeight.match(/[\d.]+/),o=Math.ceil(n/r),i=[];o--;)i.push(o+1+"\n");t.setAttribute("line-number",i.reverse().join(" "))})} 3 | /*! content navigation */ 4 | function l(t){if(!(+t.split("-")[0]<2)){var n=document.body.scrollTop||document.documentElement.scrollTop,r=[],e=document.createElement("div"),o="";["h1","h2","h3","h4","h5","h6"].forEach(function(e){document.querySelectorAll(e).forEach(function(t){r.push({type:e,top:t.getBoundingClientRect().top+n,text:t.textContent})})}),e.classList.value="navigation d-none d-gl-block",r.sort(function(t,e){return t.top-e.top}).forEach(function(t){o+='
'+t.text+"
"}),e.innerHTML=o,document.querySelector(".file-content").appendChild(e),window.onscroll=function(){e.style.top=(document.body.scrollTop||document.documentElement.scrollTop)+"px"},setTimeout(function(){Array.from(e.children).forEach(function(t){t.onclick=function(){document.body.scrollTop=+t.getAttribute("s_top"),document.documentElement.scrollTop=+t.getAttribute("s_top")}})},100)}}o(),r([function(){return(document.body.scrollTop=0)||(document.documentElement.scrollTop=0)},function(t){var e=!0,n=!1,r=void 0;try{for(var o,i=[].slice.apply(document.getElementById("nav").children)[Symbol.iterator]();!(e=(o=i.next()).done);e=!0){var c=o.value;c.getAttribute("href").slice(2)===t?c.classList.add("active"):c.classList.remove("active")}}catch(t){n=!0,r=t}finally{try{e||null==i.return||i.return()}finally{if(n)throw r}}},function(e){var t=c.find(function(t){return t.path===e}),n=document.getElementById("content"),r=document.createElement("header");r.classList.value="mb-5 pb-2 file-header",r.innerHTML=''+t.ctime+' 添加'+t.mtime+' 更新on GitHubPDF of book',n.insertBefore(r,n.firstChild)},function(e){var t=c.find(function(t){return t.path===e}),n=c.indexOf(t),r=document.createElement("footer"),o=c[n-1],i=c[n+1];r.innerHTML=""+(o&&'<< '+o.title+""||"")+(i&&''+i.title+" >>"||""),r.classList.value="file-footer clearfix mt-5 pt-2",document.getElementById("content").appendChild(r)},i,l])}]); -------------------------------------------------------------------------------- /docs/app.ba0382357b4c97ae1ab2.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;background:#fff;padding:.5em;color:#333;overflow-x:auto}.hljs-comment,.hljs-meta{color:#969896}.hljs-emphasis,.hljs-quote,.hljs-string,.hljs-strong,.hljs-template-variable,.hljs-variable{color:#df5000}.hljs-keyword,.hljs-selector-tag,.hljs-type{color:#a71d5d}.hljs-attribute,.hljs-bullet,.hljs-literal,.hljs-symbol{color:#0086b3}.hljs-name,.hljs-section{color:#63a35c}.hljs-tag{color:#333}.hljs-attr,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-title{color:#795da3}.hljs-addition{color:#55a532;background-color:#eaffea}.hljs-deletion{color:#bd2c00;background-color:#ffecec}.hljs-link{text-decoration:underline} 2 | /*! 3 | Sharing basic style sheets 4 | */body,html{font-size:12px;font-family:-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,Arial,Segoe UI,Microsoft Yahei,Roboto,Hiragino Sans GB,Heiti SC,WenQuanYi Micro Hei,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;line-height:1.5;width:100%;height:100%;position:relative;color:#000;background-color:#fff}*{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0;margin:0}:focus{outline:none}a{text-decoration:none}li,ul{list-style:none}::-webkit-scrollbar{width:8px;height:8px;background-color:#ccc;border-radius:4px}::-webkit-scrollbar-button{display:none;background-color:auto}::-webkit-scrollbar-track,::-webkit-scrollbar-track-piece{background-color:transparent}::-webkit-scrollbar-thumb{background-color:rgba(22,22,22,.75);border-radius:4px}::-webkit-resizer,::-webkit-scrollbar-corner{background-color:auto}.clear-float:after,.clear-float:before{content:"";display:table}.clear-float:after{clear:both}.clear-float{zoom:1}.responsive-abs{position:absolute;z-index:2}.responsive-abs,.responsive-container,.responsive-rel{-webkit-box-sizing:border-box;box-sizing:border-box;left:0;top:0}.responsive-container,.responsive-rel{width:100%;height:100%;position:relative;z-index:1}@media (min-width:1600px){.d-gl-block{display:block!important}}.category{overflow:auto;position:fixed;left:0;top:0;height:100%;padding:2rem 1rem 2rem 2rem;font-size:1.35rem}.category>.chapter{color:grey;text-decoration:none}.category>.chapter.active,.category>.chapter:hover{color:orange}.navigation{overflow:auto;position:absolute;top:0;right:0;-webkit-transform:translate(100%);transform:translate(100%);padding:100px 10px 20px;font-size:1rem}.navigation>div{border-left:2px solid transparent}.navigation>div.active,.navigation>div:hover{color:#2196f3;border-color:#2196f3;cursor:pointer}.navigation>.h1{font-size:2em}.navigation>.h2{padding-left:.5rem;font-size:1.75em}.navigation>.h3{padding-left:1rem;font-size:1.5em}.navigation>.h4{padding-left:1.5rem;font-size:1.25em}.file-content{position:relative;padding:4rem 1rem 6rem;font-size:1.35rem;line-height:1.8}.file-content>.file-header{padding:0 .5rem;border-bottom:1px solid #9e9e9e}.file-content>.file-footer{padding:0 .5rem;border-top:1px solid #9e9e9e}.file-content h3,.file-content h4,.file-content h5,.file-content h6{padding:.5rem;background-color:#e9ecef}.file-content h1,.file-content h2{text-align:center}.file-content p{text-indent:2em}.file-content p>*{text-indent:0}.file-content p>em{margin-right:.5rem}.file-content ul{padding-left:2em}.file-content ul>li{list-style-type:disc}.file-content ul>li>ul{padding-left:1em}.file-content ul>li>ul>li{list-style-type:circle}.file-content ul>li>p{text-indent:0}.file-content blockquote{padding:.25rem 0 .25rem .5rem;margin:1rem 0;background-color:#f8f9fa;border-left:4px solid #2196f3}.file-content blockquote>p:last-child{text-indent:0;margin-bottom:0}.file-content pre{overflow:auto;padding:.5rem 0;margin:1rem 0;background-color:#f8f9fa}.file-content pre>code{display:block;position:relative;padding:0 0 0 3rem}.file-content pre>code:before{content:attr(line-number);position:absolute;left:0;width:2.5rem;padding-right:.5rem;color:#bbb;text-align:right;border-right:1px solid #ced4da}.file-content table{width:100%;margin-bottom:1rem;border:1px solid #000;background-color:transparent}.file-content table th{padding:0 .75rem;border-bottom:1px solid #000;border-right:1px solid #000;vertical-align:bottom}.file-content table th:last-child{border-right:none}.file-content table td{padding:0 .75rem;border-top:1px solid #000;border-right:1px solid #000;vertical-align:top}.file-content table td:last-child{border-right:none}.file-content .note{overflow:auto;padding:.5rem 1rem;margin:1rem 0;border:1px dashed #000}.file-content .note>p:last-child{margin-bottom:0}.file-content .code-file{text-align:right;margin-bottom:.5rem}.file-content .code-file>span{display:inline-block;padding:0 .5rem;font-weight:700;border:1px solid #000} -------------------------------------------------------------------------------- /docs/book_of_ruby.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/docs/book_of_ruby.pdf -------------------------------------------------------------------------------- /docs/html/0-homepage.html: -------------------------------------------------------------------------------- 1 |

The Book Of Ruby Huw Collingbourne

2 |
3 |
4 | 5 |

The Book of Ruby

6 |
7 | 8 |

随着开源社区的兴盛,作为一名开发者,我们在日常工作和生活中接触的开源技术也越来越多,未来开源已成为趋势和潮流。其中,开源书籍也成为了提供给我们开发者学习的免费并且宝贵的资源。在此之前,很多开发者都将自己所感兴趣领域的开源书籍、文档翻译成了中文,分享给国内的开发者参考学习,这似乎也算是对开源社区的一部分贡献吧。

9 |

《The Book Of Ruby》是一本与 Ruby 语言 编程知识相关的开源书籍,市面上有这本书的纸质出版书(英文版),而且 Windows 平台下安装了 Ruby 语言之后,这本书的 pdf 文件(英文版)也会随 API 文档存放于 Ruby 根目录的 doc/ 目录下(目前发现,在安装 Ruby 2.3 还是 2.4 版本之后不再附带该 pdf 文档)。

10 |

我与 Ruby 结缘也算是一种巧合,源于一个网络游戏。事实上之前我非常喜欢玩《梦幻西游》这款网易公司出品的经典网游,但是随着自己慢慢长大,尤其是进入大学之后,学业上的事情比较繁重,就没有精力再继续玩下去了。然而,《梦幻西游》是一代人的回忆,也是我青春中的一段美好经历,10 年后它依然在,玩家已经换了一批又一批,它也早都不是当年的模样了。

11 |

在我读高中期间,偶尔闲暇的时候会上网搜索一下有关《梦幻西游》的信息,一次偶然间发现了民间单机版梦幻西游,下载下来后感觉还不错,而且民间制作单机版网游的人还不少。后来在大学期间,又接触了一批新的梦幻西游单机游戏,这次明显比高中时候接触的那些质量要好得多。这一次,激发我的探索欲,我接触到了 66rpg 论坛和 RPG Maker XP 游戏制作工具,后来也尝试着自己修改 RPG Maker XP 的脚本代码,而这些脚本代码正是由 Ruby 编写的。

12 |

Ruby 可以说是除过大学课程里面的 C 语言以外,我接触到并且用来写了大量代码的第一个高级语言,当然后来我也喜欢上了这门语言。Ruby 是一门纯粹的面向对象的脚本语言,可能它的运行效率会慢一点,但它的开发效率是极高的(Ruby 时代提倡敏捷开发)。更重要的是,它的面向对象的编程思想和设计哲学非常值得热爱编程的开发者去学习和借鉴。

13 |

Ruby 在前几年因为 ROR(Ruby on Rails)在 Web 开发方面大放异彩,事实上 GitHub 平台就是基于 ROR 构建的。遗憾的是,Ruby 在国内一直不温不火,据说在国外还是比较火的,虽然 ROR 框架现在用的少了,但 Ruby 语言在很多方面还是能看到它的踪影的,例如前端 Sass 工具就是用 Ruby 写的。Ruby 的创造者是一名日本人,也是首个亚洲人创造的编程语言,可能是由于偏见以及诸多原因,让 Ruby 没能成为一门主流语言。但是,Ruby 是一个集合了诸多早期其它编程语言(Perl、Smalltalk、Lisp 等)优点的高级语言,其中的设计哲学有很多地方值得我们去学习和体会。

14 |

国内的大多数开发者也许还不了解 Ruby,据说用过 Ruby 的人大多数认为它是对程序员友好的,同时我个人也觉得官方的 API 文档做的也是非常的好,而且从 Ruby 中领略到的面向对象的思维以及元编程的技巧是对开发者充满诱惑的,所以我推荐大家去学习 Ruby,它也许是一个很不错的工具。

15 |

在国内 Ruby 的学习资料是稀缺的,事实上 Ruby 相比其它语言(Java、PHP、C++ 等)的英文学习资料也是很少的,但这并不代表人们不认可它,近些年来 Ruby 在全球编程语言排名中一直位居 10 位左右,表现还不错。《The Book Of Ruby》这本书的最新版出版时间距今(2016年)已经约有六、七年了,但它的参考价值仍然是巨大的,全书共二十章,400 多页。该书不是简单的讨论了 Ruby 的语法,而是作者(Huw Collingbourne)带领你步步深入到 Ruby 之中,了解它的核心思想。

16 |

因此,我打算将这本书翻译成中文版,一方面是给自己找个事情做,能从中了解到更多编程思想和设计哲学,也方便以后自己去查阅,毕竟中文对我来说还是阅览速度更快的;另一方面呢,将该项目放到 GitHub 平台上,如果国内有 Ruby 的开发者感兴趣可以作为参考,这也算是为开源社区做一点点贡献吧。

17 |

目录概述

18 |

全书共 20 章节,下面是每一章节的内容概要:

19 | 41 |

学习资料

42 |

最后,我向大家推荐一些有关 Ruby 的学习资料。

43 | 58 | -------------------------------------------------------------------------------- /docs/html/1-author.html: -------------------------------------------------------------------------------- 1 |

关于作者

2 |
3 |
4 | 5 |
6 | 7 |

Huw Collingbourne 是 SapphireSteel 软件 (http://www.sapphiresteel.com/) 的技术总监,同时也是基于 Visual Studio 的 'Ruby In Steel' Ruby 和 Rails 集成开发环境(IDE)以及基于 Adobe Flex 的 'Amethyst' 集成开发环境的开发人员。Huw 还是英国著名的技术作家,在一些计算机杂志上,例如 Computer Shopper、PC Pro and PC Plus,撰写了许多有关 C#、Delphi、Java、Smalltalk 以及 Ruby 的评论和编程专栏。另外,他也是免费的电子书《The Little Book Of Ruby》的作者,以及在线计算杂志 Bitwise (www.bitwisemag.com) 的编辑。Huw 拥有剑桥大学的英语硕士学位。

8 | -------------------------------------------------------------------------------- /docs/html/2-introduction.html: -------------------------------------------------------------------------------- 1 |

序言

2 |
3 |

Ruby 入门

4 |

正因为你现在在阅读一本关于 Ruby 的书,我认为你不需要我去利用 Ruby 语言的优点说服你接受它这个假设是完全成立的。所以,我将采取特别的方式开始,需要注意的是:许多人被 Ruby 吸引是因为其简单的语法和易用性。然而,他们错了。Ruby 的语法一眼看去很简单,但是当你对它了解得越来越多时你会意识到,相反地,它是非常复杂的。事实就是,对于不熟悉 Ruby 的程序员来说却有很多陷阱。

5 |

在这本书中,我的目标是引导你摆脱这些陷阱,带领你穿越 Ruby 的语法和类库的浪潮。在探索的过程中会经历平坦的高速路以及有些曲折颠簸的小路。在旅途结束时,你应该能够安全、有效的使用 Ruby 了,而不会被沿途的任何意外所绊倒。

6 |

本书中使用的 Ruby 版本主要集中于 1.8.x 版本。虽然 Ruby 的 1.9 版本可能已经发布了,但 1.8 版本的 Ruby 仍然远远没有被广泛的使用。Ruby 的 1.9 版本可能被视为 2.0 版本的铺垫。大多数人同意 Ruby 1.9 版本的语法接近 1.8 版本,但你应该注意其中的一些差异,完全兼容是不可能的。

7 |
8 |

译注:目前(2016年11月)Ruby 的版本已经更新到 2.4(开发版),而稳定版为 2.3.x 。

9 |
10 |

如何阅读本书

11 |

这本书被分成了多个小部分。每一章都会介绍一个主题,而此又细分为多个子话题。并且,每个编程主题中都会伴随一个或多个彼此独立,直接就能运行的 Ruby 程序。

12 |

如果你想跟随一个结构良好的教程学习,那就按顺序阅读每个章节。如果你更喜欢学习到实用的方法,你也可以先运行程序,在你需要解释说明的时候再去参考文本。当然,如果你已经有了一些 Ruby 的实践经验,你可以按任意的顺序选择你觉得对你有用的章节进行阅读。在本书中没有大篇幅的相关联的应用程序代码或主题讨论,所以你不必担心由于没有按顺序阅读而导致你错过了一些重点。

13 |

深入探索

14 |

除了第一章,每一章都有一个部分叫做“深入探索”。在这部分我们将更深入地探索 Ruby 更具体的某些方面(包括一些我刚才提到的可能历经的曲折)。在大多数情况下,你可以直接跳过深入探索这部分,继续学习你今后必须要用到的知识。另一方面来说的话,在深入探索这一部分我们经常会接触到关于 Ruby 内部运作机制的知识,所以,如果你跳过这部分可能会错过一些非常有趣的东西。

15 |

文本格式约定

16 |

在这本书中,任何 Ruby 源代码都是像下面这样写的:

17 |
def saysomething
18 |     puts("Hello")
19 | end

当有一个示例程序伴随代码时,程序名将显示在页面右侧的一个框中,像这样:

20 |
helloname.rb
21 | 22 |

说明注释(通常是为在文本中提到的一些要点提供一些提示或给予一个更深入的解释)显示在这样一个方框中:

23 |
24 | 这是一个解释性说明。如果你想的话可以跳过它,不过你这么做的话,可能会错过一些有趣的东西。 25 |
26 | 27 |

什么是 Ruby ?

28 |

Ruby 是一种跨平台解释型语言,它具有许多与其他“脚本”语言(如 Perl 和 Python )相同的特性。第一眼看上去它具有类似 Pascal 风格的英语语法。它是完全面向对象(object oriented)的,并且与著名的纯 OO 语言的鼻祖 Smalltalk 语言有很多共同点。据说,对 Ruby 语言的开发具有影响力的语言有:Perl、Smalltalk、Eiffel、Ada 和 Lisp。Ruby 语言是由 Yukihiro Matsumoto (通常称为 “Matz”,译注:日籍,中文译名为松本行弘)开发创造的,并于 1995 年首次发布。

29 |

什么是 Rails ?

30 |

目前,围绕 Ruby 最高的热度可以归功于一个 Web 开发框架,Rails —— 通常称为 “Ruby On Rails” 。Rails 是一个令人印象深刻的框架,但它并不代表 Ruby 的全部。事实上,你在没有首先精通 Ruby 的情况下直接去接触 Rails 开发框架,你可能会发现你最终创建的应用程序(applications),连你自己都不能理解(实际上,这在 Ruby On Rails 新手中太常见了)。学习 Ruby 是理解 Rails 的必要前提条件。

31 |

下载 Ruby

32 |

你可以在 http://www.ruby-lang.org 下载最新版本的 Ruby 。确保你下载了二进制文件(不仅仅是源代码)。在 PC 上,你可以使用 Ruby Installer for Windows 安装 Ruby :http://rubyinstaller.rubyforge.org/wiki/wiki.pl

33 |

或者,如果你使用的是 Ruby In Steel IDE,你可以使用网站的下载页面上提供的 Ruby In Steel “一体化安装程序”安装 Ruby,Rails,Ruby In Steel 和所有其他需要使用的工具:http://www.sapphiresteel.com/

34 |

获取示例程序源码

35 |

本书每章中的所有程序都可以从 http://www.sapphiresteel.com/The-Book-Of-Ruby 下载 Zip 文件获取。当你解压缩这些程序包之后,你会发现它们是被按章节分到一组目录中的。使用 Ruby In Steel (由本书作者的公司开发的 Visual Studio IDE)的好处就是,你可以将在一个项目树分支中的每个章节的源程序作为一个 Visual Studio solutions 全部加载到 Ruby In Steel For Visual Studio 2008 中。如果你正在使用其它编辑器或者 IDE ,你需要一个一个的加载每个 Ruby 程序。使用 Ruby In Steel for Visual Studio 2005 的用户可以(通过 文件 新建/打开 菜单)导入或者转换该项目。

36 |

运行 Ruby 程序

37 |

经常在包含有你的 Ruby 程序的源目录中打开一个命令行窗口是非常有用的。假设 Ruby 解释器(interpreter)在你的系统中路径是正确的,你就可以通过(在命令行)输入 ruby <filename> 来运行你的程序:

38 |
ruby 1helloworld.rb

如果你使用的是 Ruby In Steel ,你可以在交互式控制台通过按 CTRL+F5 运行你的程序,或者按 F5 调试程序。

39 |

Ruby 的库文档

40 |

这本书涵盖了标准 Ruby 库(Standard Ruby Library)中的许多类和方法,但并不是全部。因此,在某些时候你需要参考 Ruby 所使用的所有类的文档。幸运的是,Ruby 类库包含有被提取编译为易于浏览的多种合适格式的嵌入式文档。例如,请参考被放在 Web 网站上的在线文档:http://www.ruby-doc.org/core/

41 |

或者,你也可以按字母顺序浏览:http://www.ruby-doc.org/stdlib/

42 |

上面这些页面都包含有如何下载离线文档的说明。还有一个页面,你可以从其中下载多种格式、版本和语言的文档:http://www.ruby-doc.org/downloads

43 |

OK,这些序言已经足够了,让我们开始吧。是时候直接去阅读第一章了。

44 |
45 |

46 | 《The Book Of Ruby》由 SapphireSteel 软件赞助,并提供开发了 Ruby In Steel IDE for Visual Studio 。
47 | http://www.sapphiresteel.com 48 |

-------------------------------------------------------------------------------- /docs/html/23-appendix.html: -------------------------------------------------------------------------------- 1 |

附录

2 |
3 |

附录 A:使用 RDOC 记录 Ruby

4 |

RDoc 是描述源代码文档格式的工具。RDoc 工具是 Ruby 的标准工具,可以处理 Ruby 代码文件和 C 代码 Ruby 类库,以便提取文档并对其进行格式化,以便它可以显示,例如在 Web 浏览器中。可以以源代码注释的形式显式添加 RDoc 文档。RDoc 工具还可以提取源代码本身的元素,以提供类,模块和方法的名称以及方法所需的任何参数的名称。

5 |

以 RDoc 处理器可访问的方式记录你自己的代码很容易。你可以在记录代码之前编写一组普通的单行注释(例如类或方法),也可以编写由 =begin rdoc=end 分隔的嵌入式多行注释。请注意,rdoc 必须跟随 =begin 后,否则 RDoc 处理器将忽略注释块。

6 |

如果要生成文档,只需从命令提示符运行 RDoc 处理器即可。要为单个文件生成文档,请输入 rdoc,后跟文件名:

7 |
rdoc rdoc1.rb

要为多个文件生成文档,请在 rdoc 命令后输入以空格分隔的文件名:

8 |
rdoc rdoc1.rb rdoc2.rb rdoc3.rb

Rdoc 工具将创建一个格式良好的 HTML 文件(index.html),顶部有三个窗格,底部有四个较大的窗格。三个顶部窗格显示文件,类和方法的名称,而底部窗格显示文档。

9 |

HTML 包含超链接,以便你可以单击类和方法名称以导航到关联的文档。文档放在它自己的子目录 \doc 中,还有许多必需的 HTML 文件和一个样式表来应用格式。

10 |

你可以通过在单个单词或多个单词周围的标签周围放置格式字符来为 RDoc 注释添加额外的格式。使用 ** 表示粗体,__ 表示斜体,++ 表示'打字机'字体。较长文本的等效标记为 <b></b> 表示粗体,<em></em> 表示斜体,<tt></tt> 表示打字机字体。

11 |

如果要从 RDoc 文档中排除注释或注释的一部分,可以将它放在 #--#++ 注释标记之间,如下所示:

12 |
#--
 13 | # This comment won‟t appear
 14 | # in the documentation
 15 | #++
 16 | # But this one will

还有各种特殊说明,包含在冒号对之间。例如,如果要添加要在浏览器栏中显示的标题,请使用 :title:,像这样:

17 |
#:title: My Fabulous RDoc Document

RDoc 提供了更多选项,使你能够以各种方式格式化文档,并以其它格式输出来替代 HTML 格式。如果你真的想要掌握 RDoc,请务必阅读完整的文档:

18 |

http://rdoc.sourceforge.net/doc/index.html

19 |

附录 B:为 Ruby On Rails 安装 MySQL

20 |

如果你正在使用 Rails,则需要安装数据库。虽然有很多可能的选择,但最广泛使用的是 MySQL。如果你之前从未使用过 MySQL,你可能会发现一些设置选项令人困惑。在这里,我将尝试引导你完成整个过程,以避免潜在的问题。

21 |
22 | 23 |

本指南基于 Windows 下的 MySQL 5.0 安装。在其它操作系统上安装其它版本时可能会有所不同。有关其它指南,请参阅 MySQL 站点。

24 |
25 | 26 |

MySQL 主站点位于 http://www.mysql.com/,你可以从此处导航到当前版本的下载页面。

27 |

下载 MySQL

28 |

我假设你将使用 MySQL 的免费版本。可以从 http://dev.mysql.com/downloads 下载。在撰写本文时,当前版本是 MySQL 5 Community Server。当然,名称和版本号会随着时间的推移而变化。下载当前(即将发布的,alpha 或 beta)版本。选择为你的操作系统推荐的特定版本(例如,Win32 和 Win64 可能有不同的版本)。

29 |

你需要在此页面的某处向下滚动以找到适用于你的操作系统的安装程序。对于 Windows,你可以下载完整的 MySQL 包或较小的 Windows Essentials 包。完整的包包含数据库开发人员的额外工具,但这些不是简单的 Rails 开发所必需的。因此,对于大多数人来说,可以获得较小的 Windows Essentials 下载文件。

30 |

你应该单击此选项旁边的“选择镜像”(Pick A Mirror)链接。然后,你将看到一份问卷,如果你愿意,可以填写。如果你不希望这样做,只需向下滚动页面并选择一个区域下载站点。单击一个链接并保存文件,其名称类似(数字可能不同):mysql-essential-5.0.41-win32.msi,磁盘上任意合适的目录。

31 |

安装 MySQL

32 |

下载完成后,通过在下载对话框中选择“打开”(Open)“运行”(Run)(如果仍然可见)或通过 Windows 资源管理器双击安装文件来运行程序。

33 |
34 | 35 |

注意:在安装 MySQL 期间,可能会在屏幕上出现一些广告。单击按钮以浏览这些。某些安全警告还可能会提示你验证是否有意安装该软件。出现提示时,应单击必要的选项以继续安装。

36 |
37 | 38 |

现在将出现安装向导的第一页。 单击“下一步”(Next)按钮。如果你愿意将软件安装到 C:\Program Files\ 下的默认 MySQL 目录中,则可以选择“典型”(Typical)设置选项。但是,如果要安装到其它目录,请选择“自定义”(Custom)。然后单击下一步(Next)。单击“更改”(Change)以更改目录。

39 |

准备好继续后,单击“下一步”(Next)

40 |

你将看到屏幕上显示“准备安装程序”。验证目标文件夹是否正确,然后单击“安装”(Install)按钮。

41 |

根据 MySQL 的版本,你现在可能会看到显示的一些营销信息,或者可能会提示你创建一个新的 MySQL 帐户,以便你收到更改和更新的消息。这些不是软件安装的重要部分,你可以单击“下一步”(Next)“跳过”(Skip)按钮继续安装。

42 |

现在出现向导已完成对话框。

43 |

点击“完成”(Finish)按钮。

44 |

配置 MySQL

45 |

事实上,安装并没有结束。对于某些安装程序,会弹出一个新屏幕,欢迎你使用 MySQL 服务器实例配置向导。如果没有发生这种情况,你需要自己加载。在 Windows 上,单击“开始”菜单,然后在程序组中导航到 MySQL-> MySQL Server 5.0(或你使用的任何版本号),然后再运行 MySQL Server Instance Config Wizard。点击下一步

46 |

假设这是你第一次在此计算机上安装 MySQL,你可以选择标准配置(如果你要从旧版本的 MySQL 升级,则需要选择详细配置 - 这超出了此简单设置指南的范围)。点击下一步。在下一个对话框中,保留选中的默认选项(即,安装为 Windows 服务;服务名称 = 'MySQL' 并自动启动 MySQL 服务器)。然后单击下一步

47 |

在下一个屏幕中,选中“修改安全设置”(Modify Security Settings),然后在前两个文本字段中输入相同的密码(你选择的密码)。你将在以后需要此密码,因此请记住它或将其记录在安全的位置。如果你可能需要从另一台计算机访问 MySQL,可以选中“从远程计算机启用 root 访问权限”(Enable root access from remote machines)。然后单击下一步

48 |
49 | 50 |

注意:默认的 MySQL 用户名是 "root"。密码是你刚输入的密码。以后在创建 Rails 应用程序时,你将需要这两项信息。

51 |
52 | 53 |

下一个屏幕只显示有关即将执行的任务的一些信息。单击“执行”(Execute)按钮。

54 |
55 | 56 |

如果你以前安装或配置了 MySQL,则可能会看到一条错误消息,告诉你跳过安装。你可以单击“重试”(Retry)以查看是否可以绕过此问题。如果没有,请按 Skip 键,然后重新启动 MySQL 配置过程,在出现提示时选择 Reconfigure Instance 和 Standard Instance。

57 |
58 | 59 |

安装完所有内容后,将出现此屏幕。单击完成

60 |

就是这样!

61 |

附录 C:进一步阅读

62 |

以下是 Ruby 和 Rails 上一些最有用的书籍的简短列表...

63 |

书籍

64 | 103 |

E-Books

104 | 118 |

附录 D:Web 站点

119 |

有无数的网站致力于 Ruby,Rails 和相关技术。以下是一些开始探索的内容...

120 |

Ruby 和 Rails 信息

121 | 141 |

附录 E:Ruby 和 Rails 的开发软件

142 |

集成开发环境 IDE/编辑器

143 | 169 |

Web 服务器

170 |

以下是一些与 Ruby On Rails 一起使用的流行 Web 服务器。

171 | 185 |

数据库

186 | 200 |

附录 F:Ruby 实现

201 |

在撰写本文时,Ruby 1.8.x 和 1.9.1 的版本都可用,并且将在未来某个日期发布版本 2.0。目前 Ruby 1.8.6 可能是 Ruby 使用最广泛的版本,本书的大部分内容适用于 Ruby 1.8。实际上,尽管 Ruby 1.9 已经发布,但为了支持使用该版本开发的项目,Ruby 1.8.x 仍将是一个重要的 Ruby 平台,包括使用 Ruby On Rails 和其它框架构建的应用程序。其它 Ruby 解释器,编译器和虚拟机也可用或正在开发中。以下是一个简短的网站列表,它将提供有关 Ruby 实现的更多信息(和下载)...

202 | 224 | -------------------------------------------------------------------------------- /docs/html/category.json: -------------------------------------------------------------------------------- 1 | [{"order":0,"path":"0-homepage.html","title":"The Book Of Ruby","ctime":"2016-11-03 18:30:00","mtime":"2019-01-16 00:30:00"},{"order":1,"path":"1-author.html","title":"关于作者","ctime":"2016-11-03 19:00:00","mtime":"2016-11-03 19:00:00"},{"order":2,"path":"2-introduction.html","title":"序言","ctime":"2016-11-03 20:30:00","mtime":"2016-11-03 20:30:00"},{"order":3,"path":"3-chapter1.html","title":"第一章","ctime":"2016-11-04 12:30:00","mtime":"2016-11-04 12:30:00"},{"order":4,"path":"4-chapter2.html","title":"第二章","ctime":"2016-11-06 21:30:00","mtime":"2016-11-06 21:30:00"},{"order":5,"path":"5-chapter3.html","title":"第三章","ctime":"2016-11-11 22:34:00","mtime":"2016-11-11 22:34:00"},{"order":6,"path":"6-chapter4.html","title":"第四章","ctime":"2016-11-13 19:37:00","mtime":"2016-11-13 19:37:00"},{"order":7,"path":"7-chapter5.html","title":"第五章","ctime":"2016-11-17 23:13:00","mtime":"2016-11-17 23:13:00"},{"order":8,"path":"8-chapter6.html","title":"第六章","ctime":"2016-11-23 21:20:00","mtime":"2016-11-23 21:20:00"},{"order":9,"path":"9-chapter7.html","title":"第七章","ctime":"2018-11-26 21:26:00","mtime":"2018-11-26 21:26:00"},{"order":10,"path":"10-chapter8.html","title":"第八章","ctime":"2018-12-01 10:30:00","mtime":"2018-12-01 10:30:00"},{"order":11,"path":"11-chapter9.html","title":"第九章","ctime":"2018-12-03 21:27:00","mtime":"2018-12-03 21:27:00"},{"order":12,"path":"12-chapter10.html","title":"第十章","ctime":"2018-12-06 21:50:00","mtime":"2018-12-06 21:50:00"},{"order":13,"path":"13-chapter11.html","title":"第十一章","ctime":"2018-12-11 01:10:00","mtime":"2018-12-11 01:10:00"},{"order":14,"path":"14-chapter12.html","title":"第十二章","ctime":"2018-12-13 15:57:00","mtime":"2018-12-13 15:57:00"},{"order":15,"path":"15-chapter13.html","title":"第十三章","ctime":"2018-12-23 00:49:00","mtime":"2018-12-23 00:49:00"},{"order":16,"path":"16-chapter14.html","title":"第十四章","ctime":"2018-12-25 00:02:00","mtime":"2018-12-25 00:02:00"},{"order":17,"path":"17-chapter15.html","title":"第十五章","ctime":"2018-12-27 23:29:00","mtime":"2018-12-27 23:29:00"},{"order":18,"path":"18-chapter16.html","title":"第十六章","ctime":"2018-12-29 01:50:00","mtime":"2018-12-29 01:50:00"},{"order":19,"path":"19-chapter17.html","title":"第十七章","ctime":"2018-12-29 19:14:00","mtime":"2018-12-29 19:14:00"},{"order":20,"path":"20-chapter18.html","title":"第十八章","ctime":"2018-12-31 13:30:00","mtime":"2018-12-31 13:30:00"},{"order":21,"path":"21-chapter19.html","title":"第十九章","ctime":"2019-01-03 23:00:00","mtime":"2019-01-03 23:00:00"},{"order":22,"path":"22-chapter20.html","title":"第二十章","ctime":"2019-01-08 00:03:00","mtime":"2019-01-08 00:03:00"},{"order":23,"path":"23-appendix.html","title":"附录","ctime":"2019-01-11 01:38:00","mtime":"2019-01-11 01:38:00"}] -------------------------------------------------------------------------------- /docs/images/author.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/docs/images/author.png -------------------------------------------------------------------------------- /docs/images/book-of-ruby.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/docs/images/book-of-ruby.jpg -------------------------------------------------------------------------------- /docs/images/chapter13_debug_recursion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/docs/images/chapter13_debug_recursion.png -------------------------------------------------------------------------------- /docs/images/chapter18_debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/docs/images/chapter18_debugger.png -------------------------------------------------------------------------------- /docs/images/chapter19_blog_test_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/docs/images/chapter19_blog_test_create.png -------------------------------------------------------------------------------- /docs/images/chapter19_blog_test_index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/docs/images/chapter19_blog_test_index.png -------------------------------------------------------------------------------- /docs/images/chapter19_blog_test_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/docs/images/chapter19_blog_test_list.png -------------------------------------------------------------------------------- /docs/images/chapter19_rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/docs/images/chapter19_rails.png -------------------------------------------------------------------------------- /docs/images/chapter2_Class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/docs/images/chapter2_Class.png -------------------------------------------------------------------------------- /docs/images/chapter2_Class_Variables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/docs/images/chapter2_Class_Variables.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | The Book Of Ruby - Chinese 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 28 | 32 | 33 | 34 | 35 |
36 | 37 | 42 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/vendor-manifest.json: -------------------------------------------------------------------------------- 1 | {"name":"vendor_lib_00ccb2ec0d3f53b1e34d","content":{"./node_modules/lodash/lodash.js":{"id":1,"buildMeta":{"providedExports":true}},"./node_modules/webpack/buildin/global.js":{"id":2,"buildMeta":{"providedExports":true}},"./node_modules/webpack/buildin/module.js":{"id":3,"buildMeta":{"providedExports":true}}}} -------------------------------------------------------------------------------- /docs/vendors/fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/docs/vendors/fonts/.gitkeep -------------------------------------------------------------------------------- /docs/vendors/libs/bootstrap-4.1.3/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.1.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2018 The Bootstrap Authors 4 | * Copyright 2011-2018 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /docs/vendors/plugins/compatible-plugin/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/vendors/plugins/compatible-plugin/respond.min.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl 2 | * Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT 3 | * */ 4 | 5 | !function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b8||!Z){return}var C={"NW":"*.Dom.select","MooTools":"$$","DOMAssistant":"*.$","Prototype":"$$","YAHOO":"*.util.Selector.query","Sizzle":"*","jQuery":"*","dojo":"*.query"};var x;var Y=[];var A=[];var D=0;var d=true;var M="slvzr";var b=/(\/\*[^*]*\*+([^\/][^*]*\*+)*\/)\s*?/g;var r=/@import\s*(?:(?:(?:url\(\s*(['"]?)(.*)\1)\s*\))|(?:(['"])(.*)\3))\s*([^;]*);/g;var F=/(behavior\s*?:\s*)?\burl\(\s*(["']?)(?!data:)([^"')]+)\2\s*\)/g;var G=/^:(empty|(first|last|only|nth(-last)?)-(child|of-type))$/;var W=/:(:first-(?:line|letter))/g;var H=/((?:^|(?:\s*})+)(?:\s*@media[^{]+{)?)\s*([^\{]*?[\[:][^{]+)/g;var E=/([ +~>])|(:[a-z-]+(?:\(.*?\)+)?)|(\[.*?\])/g;var L=/(:not\()?:(hover|enabled|disabled|focus|checked|target|active|visited|first-line|first-letter)\)?/g;var ab=/[^\w-]/g;var s=/^(INPUT|SELECT|TEXTAREA|BUTTON)$/;var q=/^(checkbox|radio)$/;var w=y>6?/[\$\^*]=(['"])\1/:null;var e=/([(\[+~])\s+/g;var P=/\s+([)\]+~])/g;var t=/\s+/g;var c=/^\s*((?:[\S\s]*\S)?)\s*$/;var v="";var p=" ";var aa="$1";function X(ac){return ac.replace(W,aa).replace(H,function(ae,aj,ai){var af=ai.split(",");for(var ak=0,ah=af.length;ak0){A.push({selector:ad.substring(0,al),patches:ag});ag=[]}return ao}else{var aq=(ap)?B(ap):V(an);if(aq){ag.push(aq);return"."+aq.className}return am}})}return aj+af.join(",")})}function V(ac){return(!w||w.test(ac))?{className:I(ac),applyClass:true}:null}function B(ah){var ag=true;var af=I(ah.slice(1));var ae=ah.substring(0,5)==":not(";var ad;var ai;if(ae){ah=ah.slice(5,-1)}var ac=ah.indexOf("(");if(ac>-1){ah=ah.substring(0,ac)}if(ah.charAt(0)==":"){switch(ah.slice(1)){case"root":ag=function(aj){return ae?aj!=z:aj==z};break;case"target":if(y==8){ag=function(ak){var aj=function(){var am=location.hash;var al=am.slice(1);return ae?(am==v||ak.id!=al):(am!=v&&ak.id==al)};o(N,"hashchange",function(){J(ak,af,aj())});return aj()};break}return false;case"checked":ag=function(aj){if(q.test(aj.type)){o(aj,"propertychange",function(){if(event.propertyName=="checked"){J(aj,af,aj.checked!==ae)}})}return aj.checked!==ae};break;case"disabled":ae=!ae;case"enabled":ag=function(aj){if(s.test(aj.tagName)){o(aj,"propertychange",function(){if(event.propertyName=="$disabled"){J(aj,af,aj.$disabled===ae)}});Y.push(aj);aj.$disabled=aj.disabled;return aj.disabled===ae}return ah==":enabled"?ae:!ae};break;case"focus":ad="focus";ai="blur";case"hover":if(!ad){ad="mouseenter";ai="mouseleave"}ag=function(aj){o(aj,ae?ai:ad,function(){J(aj,af,true)});o(aj,ae?ad:ai,function(){J(aj,af,false)});return ae};break;default:if(!G.test(ah)){return false}break}}return{className:af,applyClass:ag}}function S(){var af,ac,ad,ak;for(var an=0;an0){setInterval(function(){for(var ae=0,ac=Y.length;ae0)?f[0].href:m.location.href;h();U(N,function(){for(var ae in C){var ac,af,ad=N;if(N[ae]){ac=C[ae].replace("*",ae).split(".");while((af=ac.shift())&&(ad=ad[af])){}if(typeof ad=="function"){x=ad;T();return}}}}); 27 | /*! 28 | * ContentLoaded.js by Diego Perini, modified for IE<9 only (to save space) 29 | * 30 | * Author: Diego Perini (diego.perini at gmail.com) 31 | * Summary: cross-browser wrapper for DOMContentLoaded 32 | * Updated: 20101020 33 | * License: MIT 34 | * Version: 1.2 35 | * 36 | * URL: 37 | * http://javascript.nwbox.com/ContentLoaded/ 38 | * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE 39 | * 40 | */ 41 | function U(ag,ad){var ac=false,af=true,ai=function(aj){if(aj.type=="readystatechange"&&m.readyState!="complete"){return}(aj.type=="load"?ag:m).detachEvent("on"+aj.type,ai,false);if(!ac&&(ac=true)){ad.call(ag,aj.type||aj)}},ah=function(){try{z.doScroll("left")}catch(aj){setTimeout(ah,50);return}ai("poll")};if(m.readyState=="complete"){ad.call(ag,v)}else{if(m.createEventObject&&z.doScroll){try{af=!ag.frameElement}catch(ae){}if(af){ah()}}o(m,"readystatechange",ai);o(ag,"load",ai)}}})(this); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* starter */ 2 | 3 | // tools 4 | const del = require('del'), 5 | webpack = require('webpack'), 6 | browser_sync = require('browser-sync').create(); 7 | 8 | // path config 9 | const Path = require('./config/path.config.js'); 10 | 11 | 12 | // server 13 | browser_sync.init({ 14 | open : false, 15 | server: { 16 | baseDir: './docs/' 17 | } 18 | }); 19 | 20 | del([Path.dist + '/**/*', '!' + Path.dist + '/html/**', '!' + Path.dist + '/images/**', '!' + Path.dist + '/*.pdf']).then(() => { 21 | 22 | console.log('--------- clean dir is completed ! -----------'); 23 | 24 | // webpack 25 | return new Promise((resolve, reject) => { 26 | 27 | /* 1. External dependence */ 28 | webpack(require('./config/webpack.dll.config'), (err, stats) => { 29 | err && reject(err); 30 | 31 | console.log(stats.toString({ 32 | colors : true, 33 | modules : false, 34 | children : false, 35 | chunks : false, 36 | chunkModules: false 37 | })); 38 | 39 | resolve(200); 40 | }); 41 | 42 | }); 43 | 44 | }) 45 | .then(data => { 46 | if (data === 200) { 47 | 48 | /* 2. Business code */ 49 | webpack(require('./config/webpack.config'), (err, stats) => { 50 | if (err) { 51 | return err; 52 | } 53 | 54 | console.log(stats.toString({ 55 | colors : true, 56 | modules : false, 57 | children : false, 58 | chunks : false, 59 | chunkModules: false 60 | })); 61 | 62 | browser_sync.reload(); 63 | }); 64 | 65 | } 66 | }) 67 | .catch(err => { 68 | console.log(err.message); 69 | }); -------------------------------------------------------------------------------- /markdown/0-homepage.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | { 4 | "title": "The Book Of Ruby", 5 | "ctime": "2016-11-03 18:30:00", 6 | "mtime": "2019-01-16 00:30:00" 7 | } 8 | 9 | --- 10 | 11 | # The Book Of Ruby Huw Collingbourne 12 | 13 | *** 14 | 15 |
16 | 17 | ![The Book of Ruby](./images/book-of-ruby.jpg) 18 |
19 | 20 | 随着开源社区的兴盛,作为一名开发者,我们在日常工作和生活中接触的开源技术也越来越多,未来开源已成为趋势和潮流。其中,开源书籍也成为了提供给我们开发者学习的免费并且宝贵的资源。在此之前,很多开发者都将自己所感兴趣领域的开源书籍、文档翻译成了中文,分享给国内的开发者参考学习,这似乎也算是对开源社区的一部分贡献吧。 21 | 22 | 《The Book Of Ruby》是一本与 **Ruby 语言** 编程知识相关的开源书籍,市面上有这本书的纸质出版书(英文版),而且 Windows 平台下安装了 Ruby 语言之后,这本书的 pdf 文件(英文版)也会随 API 文档存放于 Ruby 根目录的 `doc/` 目录下(目前发现,在安装 Ruby 2.3 还是 2.4 版本之后不再附带该 pdf 文档)。 23 | 24 | 我与 Ruby 结缘也算是一种巧合,源于一个网络游戏。事实上之前我非常喜欢玩《梦幻西游》这款网易公司出品的经典网游,但是随着自己慢慢长大,尤其是进入大学之后,学业上的事情比较繁重,就没有精力再继续玩下去了。然而,《梦幻西游》是一代人的回忆,也是我青春中的一段美好经历,10 年后它依然在,玩家已经换了一批又一批,它也早都不是当年的模样了。 25 | 26 | 在我读高中期间,偶尔闲暇的时候会上网搜索一下有关《梦幻西游》的信息,一次偶然间发现了民间单机版梦幻西游,下载下来后感觉还不错,而且民间制作单机版网游的人还不少。后来在大学期间,又接触了一批新的梦幻西游单机游戏,这次明显比高中时候接触的那些质量要好得多。这一次,激发我的探索欲,我接触到了 66rpg 论坛和 RPG Maker XP 游戏制作工具,后来也尝试着自己修改 RPG Maker XP 的脚本代码,而这些脚本代码正是由 Ruby 编写的。 27 | 28 | Ruby 可以说是除过大学课程里面的 C 语言以外,我接触到并且用来写了大量代码的第一个高级语言,当然后来我也喜欢上了这门语言。Ruby 是一门纯粹的面向对象的脚本语言,可能它的运行效率会慢一点,但它的开发效率是极高的(Ruby 时代提倡敏捷开发)。更重要的是,它的面向对象的编程思想和设计哲学非常值得热爱编程的开发者去学习和借鉴。 29 | 30 | Ruby 在前几年因为 ROR(Ruby on Rails)在 Web 开发方面大放异彩,事实上 GitHub 平台就是基于 ROR 构建的。遗憾的是,Ruby 在国内一直不温不火,据说在国外还是比较火的,虽然 ROR 框架现在用的少了,但 Ruby 语言在很多方面还是能看到它的踪影的,例如前端 Sass 工具就是用 Ruby 写的。Ruby 的创造者是一名日本人,也是首个亚洲人创造的编程语言,可能是由于偏见以及诸多原因,让 Ruby 没能成为一门主流语言。但是,Ruby 是一个集合了诸多早期其它编程语言(Perl、Smalltalk、Lisp 等)优点的高级语言,其中的设计哲学有很多地方值得我们去学习和体会。 31 | 32 | 国内的大多数开发者也许还不了解 Ruby,据说用过 Ruby 的人大多数认为它是对程序员友好的,同时我个人也觉得官方的 API 文档做的也是非常的好,而且从 Ruby 中领略到的面向对象的思维以及元编程的技巧是对开发者充满诱惑的,所以我推荐大家去学习 Ruby,它也许是一个很不错的工具。 33 | 34 | 在国内 Ruby 的学习资料是稀缺的,事实上 Ruby 相比其它语言(Java、PHP、C++ 等)的英文学习资料也是很少的,但这并不代表人们不认可它,近些年来 Ruby 在全球编程语言排名中一直位居 10 位左右,表现还不错。《The Book Of Ruby》这本书的最新版出版时间距今(2016年)已经约有六、七年了,但它的参考价值仍然是巨大的,全书共二十章,400 多页。该书不是简单的讨论了 Ruby 的语法,而是作者(Huw Collingbourne)带领你步步深入到 Ruby 之中,了解它的核心思想。 35 | 36 | 因此,我打算将这本书翻译成中文版,一方面是给自己找个事情做,能从中了解到更多编程思想和设计哲学,也方便以后自己去查阅,毕竟中文对我来说还是阅览速度更快的;另一方面呢,将该项目放到 GitHub 平台上,如果国内有 Ruby 的开发者感兴趣可以作为参考,这也算是为开源社区做一点点贡献吧。 37 | 38 | ## 目录概述 39 | 40 | 全书共 20 章节,下面是每一章节的内容概要: 41 | 42 | - 第一章:字符串、数字、类和对象 - 获取输入和输出,字符串和内嵌表达式,数字和测试语句 if...then,局部变量和全局变量,类和对象,实例变量,消息、方法与多态性,构造方法与对象初始化,查看对象信息。 43 | - 第二章:类的层次结构、属性与变量 - 超类与子类,访问器方法,属性读写,调用超类方法,类变量。 44 | - 第三章:字符串和 Range - 字符串分隔符,字符串处理方法,Range,Range 迭代器。 45 | - 第四章:数组与 Hash - 常用处理方法。 46 | - 第五章:循环和迭代器 - for 循环,多参数迭代,代码块,while 循环,until 循环,loop 循环。 47 | - 第六章:条件语句 - if...then...else,and...or...not,if...elsif,unless,case 语句,=== 方法,catch 与 throw。 48 | - 第七章:方法 - 类方法,类变量,构造方法,单例方法,单例类,重写方法,public、private 和 protected 方法。 49 | - 第八章:参数传递与返回值 - 实例方法,类方法,单例方法,返回值,返回多个值,默认参数和多参数,整数,进出原则,并行赋值,引用传值。 50 | - 第九章:异常处理 - rescue,ensure,else,error 编号,retry,raise。 51 | - 第十章:Block、Proc 和 Lambda - 匿名函数,proc 与 lambda,闭包,yield,嵌套块,优先级规则,块中实例变量,块中局部变量。 52 | - 第十一章:符号 - 符号与字符串,符号和变量,为什么使用符号? 53 | - 第十二章:模块和 mixin - 模块与类,模块方法,命名空间,包含模块,alias 方法,作用域解析符。 54 | - 第十三章:文件与 IO - 打开和关闭文件,文件和目录,赋值文件,目录查询,递归,排序。 55 | - 第十四章:Yaml - 转换成 yaml,嵌套序列,保存 yaml 数据,一个文件包含多个 yaml 文档,yaml 数据库。 56 | - 第十五章:Marshal - 保存与加载数据,保存单例对象,yaml 与单例对象。 57 | - 第十六章:正则表达式 - 匹配组,前后匹配,贪婪匹配,字符串方法,文件操作。 58 | - 第十七章:线程 - 创建线程,运行线程,主线程,线程状态,线程优先级,主线程优先级,互斥。 59 | - 第十八章:调试与测试 - irb、调试、单元测试、断言。 60 | - 第十九章:Ruby On Rails - 安装 RoR、第一个 RoR 应用,创建控制器,创建视图,Rails 标记,MVC。 61 | - 第二十章:动态编程 - 自修改程序,eval,动态添加变量和方法,运行时创建类,绑定,send,移除方法。 62 | 63 | ## 学习资料 64 | 65 | 最后,我向大家推荐一些有关 Ruby 的学习资料。 66 | 67 | - 中文书籍 68 | - 《Ruby基础教程》,[日] 高桥征义 后藤裕藏,何文斯 译 69 | - 《Ruby元编程》,[意] Paolo Perrotta,廖志刚 译 70 | - 中文论坛 71 | - Ruby China 72 | - Ruby 官网 73 | - Ruby -------------------------------------------------------------------------------- /markdown/1-author.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | { 4 | "title": "关于作者", 5 | "ctime": "2016-11-03 19:00:00", 6 | "mtime": "2016-11-03 19:00:00" 7 | } 8 | 9 | --- 10 | 11 | # 关于作者 12 | 13 | *** 14 | 15 |
16 | 17 |
18 | 19 | **Huw Collingbourne** 是 SapphireSteel 软件 (http://www.sapphiresteel.com/) 的技术总监,同时也是基于 Visual Studio 的 **'Ruby In Steel'** Ruby 和 Rails 集成开发环境(IDE)以及基于 Adobe Flex 的 **'Amethyst'** 集成开发环境的开发人员。Huw 还是英国著名的技术作家,在一些计算机杂志上,例如 Computer Shopper、PC Pro and PC Plus,撰写了许多有关 C#、Delphi、Java、Smalltalk 以及 Ruby 的评论和编程专栏。另外,他也是免费的电子书《The Little Book Of Ruby》的作者,以及在线计算杂志 Bitwise (www.bitwisemag.com) 的编辑。Huw 拥有剑桥大学的英语硕士学位。 -------------------------------------------------------------------------------- /markdown/11-chapter9.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | { 4 | "title": "第九章", 5 | "ctime": "2018-12-03 21:27:00", 6 | "mtime": "2018-12-03 21:27:00" 7 | } 8 | 9 | --- 10 | 11 | # 第九章 12 | 13 | *** 14 | 15 | ## 异常处理 16 | 17 | 即使是精心编写的程序,有时也会遇到无法预料的错误。例如,如果编写需要从磁盘读取某些数据的程序,则可以假设指定的磁盘实际可用且数据有效。如果你的程序根据用户输入进行计算,则它假定输入适合用于计算。 18 | 19 | 虽然你可能会在一些潜在的问题出现之前尽可能的预料到 - 例如,通过编写代码来检查文件是否存在,然后再从中读取数据,或者在进行计算之前检查用户输入是否为数字 - 你永远无法提前预料到每个问题。 20 | 21 | 例如,用户可以在已经开始从 CD 中读取数据时移除 CD;或者,在你的代码尝试除以此值之前,某些模糊的计算可能会产生 0。当你知道在运行时(runtime)某些不可预见的情况可能导致你的代码被“中断”(break)时,你可以尝试使用“异常处理”(exception handling)来避免灾难。 22 | 23 | “异常”(exception)是打包到对象中的错误。该对象是 Exception 类(或其后代之一)的一个实例。你可以通过捕获异常对象(Exception Object)来处理异常,可选地使用它包含的信息(比如打印相应的错误消息)并采取从错误中恢复所需的任何操作 - 可能通过关闭任何仍然打开的文件,或者分配合理的值给那些因错误计算而被分配了一些无意义的值的变量。 24 | 25 | ### Rescue 26 | 27 | 异常处理的基本语法可归纳如下: 28 | 29 | begin 30 | # Some code which may cause an exception 31 | rescue 32 | # Code to recover from the exception 33 | end 34 | 35 | 下面是一个处理尝试除以零的异常的程序示例: 36 | 37 |
exception1.rb
38 | 39 | begin 40 | x = 1/0 41 | rescue Exception 42 | x = 0 43 | puts( $!.class ) 44 | puts( $! ) 45 | end 46 | 47 |
div_by_zero.rb
48 | 49 | 运行此代码时,除以零的尝试会导致异常。如果未处理(如示例程序 **div_by_zero.rb**),程序将崩溃。但是,通过将有问题的代码放在异常处理块(`begin` 和 `end` 之间)中,我已经能够在以 `rescue` 开头的部分中捕获异常。我做的第一件事是将变量 `x` 设置为有意义的值。接下来是这两个令人费解的语句: 50 | 51 | puts( $!.class ) 52 | puts( $! ) 53 | 54 | 在 Ruby 中,`$!` 是一个全局变量,为其分配了最后一个捕获的异常对象。打印 `$!.class` 会显示类名,这里是 "ZeroDivisionError";单独打印变量 `$!` 会显示异常对象中包含的错误信息,这里是 "divided by 0"。 55 | 56 | 我一般都不太热衷于依赖全局变量,特别是当它们的'名字'与 `$!` 一样不具有描述性时。幸运的是,还有另一种选择。你可以通过将'关联运算符'(assoc operator),`=>` 放在异常的类名之后和变量名之前,将变量名与异常对象相关联: 57 | 58 |
exception2.rb
59 | 60 | rescue Exception => exc 61 | 62 | 你现在可以使用变量名称(此处为 `exc`)来引用 Exception 对象: 63 | 64 | puts( exc.class ) 65 | puts( exc ) 66 | 67 |
exception_tree.rb
68 | 69 |
70 |

Exceptions 有一个家族树(家谱)...

71 | 72 | 要理解 `rescue` 子句如何捕获异常,只要记住,在 Ruby 中异常是对象,并且像所有其它对象一样,它们由一个类定义。此外,还有一个明确的“继承链”,就像所有 Ruby 对象都继承自 Object 类一样。 73 |
74 | 75 | 虽然看起来很明显,当你除以零时,你将得到一个 ZeroDivisionError 异常,在现实世界的代码中,有时候异常的类型不是那么可预测的。例如,假设你有一个基于用户提供的两个值进行除法计算的方法: 76 | 77 | def calc( val1, val2 ) 78 | return val1 / val2 79 | end 80 | 81 | 这可能会产生各种不同的异常。显然,如果用户输入的第二个值为 0,我们将得到 ZeroDivisionError。 82 | 83 | 但是,如果*第二个*值是字符串(string),则异常将是 TypeError,而*第一个*值是字符串时,它将是 NoMethodError(因为 String 类没有定义'除法运算符' `/`)。这里的 `rescue` 块处理所有可能发生的异常: 84 | 85 |
multi_except.rb
86 | 87 | def calc( val1, val2 ) 88 | begin 89 | result = val1 / val2 90 | rescue Exception => e 91 | puts( e.class ) 92 | puts( e ) 93 | result = nil 94 | end 95 | return result 96 | end 97 | 98 | 通常,针对不同的异常采取不同的行为会很有用。你可以通过添加多个 `rescue` 块来实现。每个 `rescue` 子句都可以处理多个异常类型,异常类名用逗号分隔。这里我的 `calc` 方法在一个子句中处理 TypeError 和 NoMethodError 异常,并使用 catch-all 异常处理程序来处理其它所有异常类型: 99 | 100 |
multi_except2.rb
101 | 102 | def calc( val1, val2 ) 103 | begin 104 | result = val1 / val2 105 | rescue TypeError, NoMethodError => e 106 | puts( e.class ) 107 | puts( e ) 108 | puts( "One of the values is not a number!" ) 109 | result = nil 110 | rescue Exception => e 111 | puts( e.class ) 112 | puts( e ) 113 | result = nil 114 | end 115 | return result 116 | end 117 | 118 |
exception_tree.rb
119 | 120 |
121 |

Object 类是所有异常类(exceptions)的最终祖先类。

122 | 123 | 从 Object 类开始,派生出子类 Exception,然后是 StandardError,最后是更具体的异常类型,例如 ZeroDivisionError。如果你愿意,你可以编写一个 `rescue` 子句来处理 Object 类,因为 Object 是所有对象的祖先,这样确实会成功匹配一个异常对象: 124 | 125 | # This is possible... 126 | rescue Object => exc 127 | 128 | 但是,尽可能匹配 Exception 类的相关后代类通常更有用。作为更好的措施,附加一个处理 StandardError 或 Exception 对象的 `rescue` 子句是很有用的,以防止你没考虑到的异常类型被漏掉。你可以运行 `exception_tree.rb` 程序来查看 ZeroDivisionError 异常的家族树(继承链)。 129 |
130 | 131 | 在处理多个异常类型时,应始终让 `rescue` 子句先处理特定类型的异常,然后使用 `rescue` 子句处理通用类型的异常。 132 | 133 | 当特定类型异常(例如 TypeError)处理完时,`begin..end` 异常块将会退出,因此执行流程不会“进入”通用类型的 `rescue` 子句。但是,如果 `rescue` 子句首先处理通用类型的异常,那么它将处理所有类型的异常,因此任何用来处理更具体的类型的异常子句都将永远不会执行。 134 | 135 | 例如,如果我在 `calc` 方法中颠倒了 `rescue` 子句的顺序,首先放置了通用的 Exception 处理程序,这将匹配所有的异常类型,因此特定的 TypeError 和 NoMethodError 异常处理子句永远都不会运行: 136 | 137 |
multi_except_err.rb
138 | 139 | # This is incorrect... 140 | rescue Exception => e 141 | puts( e.class ) 142 | puts( e ) 143 | result = nil 144 | rescue TypeError, NoMethodError => e 145 | puts( e.class ) 146 | puts( e ) 147 | puts( "Oops! This message will never be displayed!" ) 148 | result = nil 149 | end 150 | 151 | ### Ensure 152 | 153 | 无论是否发生异常(Exception),你可能会在某些情况下采取某些特定操作。例如,每当你处理某种不可预测的输入/输出时 - 例如,在使用磁盘上的文件和目录时 - 总是有可能位置(磁盘或目录)或数据源(文件)根本不存在或者可能发生其它类型的问题 - 例如当你尝试写入时磁盘已满,或者尝试读取时可能包含一个错误类型的数据。 154 | 155 | 无论你是否遇到任何问题,你可能需要执行一些最终的“清理”(cleanup)过程 - 例如登录到特定的工作目录或关闭先前打开的文件。你可以通过在 `begin..rescue` 代码块后跟随一个以 `ensure` 关键字开头的另一个块的来执行此操作。`ensure` 块中的代码将始终会执行 - 无论之前是否发生异常。 156 | 157 | 最后,我想确保我的工作目录(由 `Dir.getwd` 提供)始终恢复到其原始位置。我通过在 `startdir` 变量中保存原始目录并再次在 `ensure` 块中将其作为工作目录来完成此操作: 158 | 159 |
ensure.rb
160 | 161 | startdir = Dir.getwd 162 | 163 | begin 164 | Dir.chdir( "X:\\" ) 165 | puts( `dir` ) 166 | rescue Exception => e 167 | puts e.class 168 | puts e 169 | ensure 170 | Dir.chdir( startdir ) 171 | end 172 | 173 | 现在让我们看看如何处理从文件中读取错误数据的问题。如果数据损坏,或者你不小心打开了错误的文件,或者很简单 - 你的程序代码包含错误(bug)时,则可能会发生这种情况。 174 | 175 | 这里我有一个文件 **test.txt**,包含六行内容。前五行是数字(numbers);第六行不是。我的代码会打开此文件并读入所有六行内容: 176 | 177 |
ensure2.rb
178 | 179 | f = File.new( "test.txt" ) 180 | 181 | begin 182 | for i in (1..6) do 183 | puts("line number: #{f.lineno}") 184 | line = f.gets.chomp 185 | num = line.to_i 186 | puts( "Line '#{line}' is converted to #{num}" ) 187 | puts( 100 / num ) 188 | end 189 | rescue Exception => e 190 | puts( e.class ) 191 | puts( e ) 192 | ensure 193 | f.close 194 | puts( "File closed" ) 195 | end 196 | 197 | 这些行作为字符串读入(使用 `gets`),尝试将它们转换为整数(使用 `to_i`)。转换失败时不会产生错误;Ruby 会返回值 0。 198 | 199 | 问题出现在下一行代码中,它尝试按转换后的数字进行除法运算。输入文件的第六行包含字符串 "six",当尝试转换为整数时产生 0 - 并且当在除法运算中使用该值时不可避免地会导致错误发生。 200 | 201 | 在外部打开数据文件后,无论是否发生错误我都想确保文件会关闭。例如,如果我只通过将 `for` 循环中的范围编辑为 `(1..5)` 来读取前五行,那么就没有异常。我仍然想要关闭该文件。 202 | 203 | 但是将文件关闭代码(`f.close`)放在 `rescue` 子句中并不好,因为在这种情况下它不会被执行。然而,通过将它放在 `ensure` 子句中,无论是否发生异常,我都可以确定该文件将被关闭。 204 | 205 | ### Else 206 | 207 | 如果说 `rescue` 部分在发生错误时执行,而 `ensure` 无论是否发生错误都会执行,那么我们怎么才能只有在没有*发生*错误时指定执行某些代码? 208 | 209 | 这样做的方法是在 `rescue` 部分之后和 `ensure` 部分之前添加一个可选的 `else` 子句(如果有的话),如下所示: 210 | 211 | begin 212 | # code which may cause an exception 213 | rescue [Exception Type] 214 | else # optional section executes if no exception occurs 215 | ensure # optional exception always executes 216 | end 217 | 218 | 这是一个示例: 219 | 220 |
else.rb
221 | 222 | def doCalc( aNum ) 223 | begin 224 | result = 100 / aNum.to_i 225 | rescue Exception => e # executes when there is an error 226 | result = 0 227 | msg = "Error: " + e 228 | else # executes when there is no error 229 | msg = "Result = #{result}" 230 | ensure # always executes 231 | msg = "You entered '#{aNum}'. " + msg 232 | end 233 | return msg 234 | end 235 | 236 | ### Error 编号 237 | 238 | 如果你之前运行了 `ensure.rb` 程序并且你正密切关注,你可能已经发现了一些异常情况当你尝试登录不存在的驱动器(例如,在我的系统上可能是 "X:\" 驱动器)。通常,当一个异常发生时,异常类是特定命名类型的实例,如 ZeroDivisionError 或 NoMethodError。然而,在这种情况下,类异常显示为: 239 | 240 | Errno::ENOENT 241 | 242 | 事实证明,Ruby 中存在各种各样的 `Errno` 错误。试试 **disk_err.rb**。这里定义了一个方法 `chDisk`,它尝试登录由字符 `aChar` 标识的磁盘。因此,如果你传递 "A" 作为 `chDisk` 的参数,它将尝试登录 A:\ 驱动器。我调用了三次 `chDisk` 方法,每次都传递一个不同的字符串: 243 | 244 |
disk_err.rb
245 | 246 | chDisk( "D" ) 247 | chDisk( "X" ) 248 | chDisk( "ABC" ) 249 | 250 | 在我的电脑上,D:\ 是我的 DVD 驱动器。目前它是空的,当我的程序尝试登录它时,Ruby 返回此类型的异常: 251 | 252 | Errno::EACCES 253 | 254 | 我的 PC 上没有 X:\ 驱动器,当我尝试登录时,Ruby 会返回此类型的异常: 255 | 256 | Errno::ENOENT 257 | 258 | 在最后一个示例中,我传递一个字符串参数 "ABC" 作为无效的磁盘标识符,Ruby 返回此类型的异常: 259 | 260 | Errno::EINVAL 261 | 262 | 此类型的错误是 SystemCallError 类的后代。你可以通过取消注释代码行来轻松的验证这一点,以显示 **disk_err.rb** 源代码中指示的类的族。 263 | 264 | 实际上,这些类包含底层操作系统返回的整数错误值。这里 `Errno` 是包含匹配相应整数错误值的常量(例如 `EACCES` 和 `ENOENT`)的模块的名称。 265 | 266 | 要查看 `Errno` 常量的完整列表,请运行以下命令: 267 | 268 | puts( Errno.constants ) 269 | 270 | 要查看任何给定常量的相应数值,请将 `::Errno` 追加到常量名称后面,如下所示: 271 | 272 | Errno::EINVAL::Errno 273 | 274 |
errno.rb
275 | 276 | 以下代码可用于显示所有 `Errno` 常量的列表及其数值: 277 | 278 | for err in Errno.constants do 279 | errnum = eval( "Errno::#{err}::Errno" ) 280 | puts( "#{err}, #{errnum}" ) 281 | end 282 | 283 | ### Retry 284 | 285 | 如果你认为错误情况可能是暂时的或者可以被纠正(由用户),你可以使用关键字 `retry` 重新运行 `begin..end` 块中的所有代码,如此示例中如果发生 ZeroDivisionError 等错误则会提示用户重新输入一个值: 286 | 287 |
retry.rb
288 | 289 | def doCalc 290 | begin 291 | print( "Enter a number: " ) 292 | aNum = gets().chomp() 293 | result = 100 / aNum.to_i 294 | rescue Exception => e 295 | result = 0 296 | puts( "Error: " + e + "\nPlease try again." ) 297 | retry # retry on exception 298 | else 299 | msg = "Result = #{result}" 300 | ensure 301 | msg = "You entered '#{aNum}'. " + msg 302 | end 303 | return msg 304 | end 305 | 306 | 当然,存在这样的危险:错误可能不像你想象的那样是暂时的,如果你使用 `retry`,你必须要提供明确定义的退出(exit)条件,以确保代码在固定次数的尝试后停止执行。 307 | 308 | 例如,你可以在 `begin` 子句中递增一个局部变量(如果这样做,请确保它在任何可能产生异常的代码之前递增,因为一旦发生异常,那些剩下的预先为 `rescue` 子句关联的代码将被跳过!)。然后在 `rescue` 部分测试该变量的值,如下所示: 309 | 310 | rescue Exception => e 311 | if aValue < someValue then 312 | retry 313 | end 314 | 315 | 这是一个完整的示例,其中我测试名为 `tries` 的变量的值,以确保在异常处理块退出之前在不出错的情况下尝试重新运行代码不超过三次: 316 | 317 | def doCalc 318 | tries = 0 319 | begin 320 | print( "Enter a number: " ) 321 | tries += 1 322 | aNum = gets().chomp() 323 | result = 100 / aNum.to_i 324 | rescue Exception => e 325 | msg = "Error: " + e 326 | puts( msg ) 327 | puts( "tries = #{tries}" ) 328 | result = 0 329 | if tries < 3 then # set a fixed number of retries 330 | retry 331 | end 332 | else 333 | msg = "Result = #{result}" 334 | ensure 335 | msg = "You entered '#{aNum}'. " + msg 336 | end 337 | return msg 338 | end 339 | 340 | ### Raise 341 | 342 | 有时你可能希望将异常保持为“活动的”(alive),即使它已被异常处理块捕获。例如,这可用于推迟异常的处理 - 通过将其传递给其他方法。你可以使用 `raise` 方法执行此操作。但是,你需要注意,一旦异常被抛出(raised),就需要重新处理该异常,否则可能导致程序崩溃。这是一个简单的示例,它引发了一个 ZeroDivisionError 异常,并将异常传递给一个名为 `handleError` 的方法: 343 | 344 |
raise.rb
345 | 346 | begin 347 | divbyzero 348 | rescue Exception => e 349 | puts( "A problem just occurred. Please wait..." ) 350 | x = 0 351 | begin 352 | raise 353 | rescue 354 | handleError( e ) 355 | end 356 | end 357 | 358 | 这里 `divbyzero` 是一个方法的名称,在该方法中进行除零操作,`handleError` 是一个打印该异常的更详细的信息的方法: 359 | 360 | def handleError( e ) 361 | puts( "Error of type: #{e.class}" ) 362 | puts( e ) 363 | puts( "Here is a backtrace: " ) 364 | puts( e.backtrace ) 365 | end 366 | 367 | 请注意,这里使用了 `backtrace` 方法,该方法显示一个字符串数组 - 显示发生错误所在的文件名和行号,在本例中为调用生成错误的 `divbyzero` 方法所在的行。 368 | 369 |
raise2.rb
370 | 371 | 即使程序代码本身没有引起异常,你也可以专门抛出(raise)异常以强制执行错误条件。单独调用 `raise` 会抛出 RuntimeError 类型的异常(或全局变量 `$!` 中的任何异常): 372 | 373 | raise # raises RuntimeError 374 | 375 | 默认情况下,这将没有与之关联的描述性消息。你可以将消息添加为参数,如下所示: 376 | 377 | raise "An unknown exception just occurred!" 378 | 379 | 你可以抛出特定类型的错误... 380 | 381 | raise ZeroDivisionError 382 | 383 | 你还可以创建特定异常类型的对象,并使用自定义消息对其进行初始化... 384 | 385 | raise ZeroDivisionError.new( "I'm afraid you divided by Zero" ) 386 | 387 |
raise3.rb
388 | 389 | 当然,如果标准异常类型不符合你的要求,你可以通过继承现有异常类来创建新的异常类型。为你的类提供 `to_str` 方法,以便为它们提供默认信息。 390 | 391 | class NoNameError < Exception 392 | def to_str 393 | "No Name given!" 394 | end 395 | end 396 | 397 | 这是一个如何抛出自定义异常的示例: 398 | 399 | def sayHello( aName ) 400 | begin 401 | if (aName == "") or (aName == nil) then 402 | raise NoNameError 403 | end 404 | rescue Exception => e 405 | puts( e.class ) 406 | puts( "message: " + e ) 407 | puts( e.backtrace ) 408 | else 409 | puts( "Hello #{aName}" ) 410 | end 411 | end 412 | 413 | ## 深入探索 414 | 415 | ### 省略 begin 和 end 416 | 417 | 在方法,类或模块中捕获异常时,你可以选择省略 `begin` 和 `end`。例如,以下所有内容都是合法的: 418 | 419 |
omit_begin_end.rb
420 | 421 | def calc 422 | result = 1/0 423 | rescue Exception => e 424 | puts( e.class ) 425 | puts( e ) 426 | result = nil 427 | return result 428 | end 429 | 430 | class X 431 | @@x = 1/0 432 | rescue Exception => e 433 | puts( e.class ) 434 | puts( e ) 435 | end 436 | 437 | module Y 438 | @@x = 1/0 439 | rescue Exception => e 440 | puts( e.class ) 441 | puts( e ) 442 | end 443 | 444 | 在上面显示的所有情况中,如果以通常的方式将 `begin` 和 `end` 关键字放在异常处理代码块的开头和结尾,则异常处理也会起作用。 445 | 446 | ### Catch...Throw 447 | 448 | 在某些语言中,可以使用关键字 `catch` 捕获异常,使用关键字 `throw` 来抛出异常。虽然 Ruby 提供了 `catch` 和 `throw` 方法,但它们与异常处理没有直接关系。相反,`catch` 和 `throw` 用于在满足某些条件时跳出已定义的代码块。当然,在发生异常时,你也可以使用 `catch` 和 `throw` 来跳出代码块(尽管这可能不是处理错误的最优雅方式)。 449 | 450 |
catch_except.rb
451 | 452 | catch(:finished) { 453 | print( 'Enter a number: ' ) 454 | num = gets().chomp.to_i 455 | begin 456 | result = 100 / num 457 | rescue Exception => e 458 | throw :finished # jump to end of block 459 | end 460 | puts("The result of that calculation is #{result}" ) 461 | } # end of :finished catch block 462 | 463 | 有关 `catch` 和 `throw` 的更多信息,请参见第 6 章。 -------------------------------------------------------------------------------- /markdown/13-chapter11.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | { 4 | "title": "第十一章", 5 | "ctime": "2018-12-11 01:10:00", 6 | "mtime": "2018-12-11 01:10:00" 7 | } 8 | 9 | --- 10 | 11 | # 第十一章 12 | 13 | *** 14 | 15 | ## 符号(Symbols) 16 | 17 | Ruby 的许多新人都被符号(symbols)弄糊涂了。符号(symbol)是一个标识符,其首个字符为冒号(:),所以 `:this` 是一个符号,`:that` 也是。事实上,符号并不复杂 - 在某些情况下,它们可能非常有用,我们很快就会看到。 18 | 19 | 让我们首先明确一个符号*不是*什么:它不是一个字符串,它不是一个常量,它也不是一个变量。简单地说,符号是除了自己的名称之外没有内在含义的标识符。而你可能会这样为变量赋值... 20 | 21 | name = "Fred" 22 | 23 | 你不能为符号赋值。名为 `:name` 的符号的值也为 `:name`。 24 | 25 |
26 | 27 | 更多有关符号专门的说明,请参阅本章末尾的“深入探索”部分。 28 |
29 | 30 | 当然,我们之前使用过符号。例如,在第 2 章中,我们通过将符号传递给 `attr_reader` 和 `attr_writer` 方法来创建属性的读取器和修改器,如下所示: 31 | 32 | attr_reader( :description ) 33 | attr_writer( :description ) 34 | 35 | 你可能还记得上面的代码会使得 Ruby 创建一个 `@description` 实例变量以及一对名为 `description` 的 getter(reader)和 setter(writer)方法。Ruby 从字面量理解符号的值。它的值就是它的名字(`:description`)。`attr_reader` 和 `attr_writer` 方法会创建名称与该名称相匹配的变量和方法。 36 | 37 | ### 符号与字符串 38 | 39 | 一个常见的误解就是认为符号(symbol)是字符串的一种类型。毕竟,符号 `:hello` 与字符串 "hello" 非常相似不是吗? 40 | 41 | 事实上,符号与字符串完全不同。首先,每个字符串是不同的 — 因此,"hello"、"hello" 和 "hello" 是三个独立的对象,具有三个独立的 `object_id`s。 42 | 43 |
symbol_ids.rb
44 | 45 | puts( "hello".object_id ) # These 3 strings have 3 different object_ids 46 | puts( "hello".object_id ) 47 | puts( "hello".object_id ) 48 | 49 | 但是符号是唯一的,所以 `:hello`、`:hello` 和 `:hello` 都引用具有相同的 `object_id` 的对象。在这方面,符号与整数(integer)相比,要比字符串有更多的共同之处。你可能还记得,给定的整数值每次出现都引用相同的对象,因此 `10`、`10` 和 `10` 可以被认为是相同的对象,并且它们具有相同的 `object_id`: 50 | 51 |
ints_and_symbols.rb
52 | 53 | # These three symbols have the same object_id 54 | puts( :ten.object_id ) 55 | puts( :ten.object_id ) 56 | puts( :ten.object_id ) 57 | 58 | # These three integers have the same object_id 59 | puts( 10.object_id ) 60 | puts( 10.object_id ) 61 | puts( 10.object_id ) 62 | 63 | 或者你可以使用 `equal?` 方法测试其相等性: 64 | 65 |
symbols_strings.rb
66 | 67 | puts( :helloworld.equal?( :helloworld ) ) #=> true 68 | puts( "helloworld".equal?( "helloworld" ) ) #=> false 69 | puts( 1.equal?( 1 ) ) #=> true 70 | 71 | 由于是唯一的,所以符号提供了明确的标识符。你可以将符号作为参数传递给方法,如下所示: 72 | 73 | amethod( :deletefiles ) 74 | 75 | 方法可能包含测试传入参数的值的代码: 76 | 77 |
symbols_1.rb
78 | 79 | def amethod( doThis ) 80 | if (doThis == :deletefiles) then 81 | puts( 'Now deleting files...') 82 | elsif (doThis == :formatdisk) then 83 | puts( 'Now formatting disk...') 84 | else 85 | puts( "Sorry, command not understood." ) 86 | end 87 | end 88 | 89 | 符号还可用于提供字符串的可读性和整数的唯一性的 `case` 语句: 90 | 91 | case doThis 92 | when :deletefiles : puts( 'Now deleting files...') 93 | when :formatdisk : puts( 'Now formatting disk...') 94 | else puts( "Sorry, command not understood." ) 95 | end 96 | 97 | 声明符号的作用域不会影响其唯一性。思考以下... 98 | 99 |
symbol_ref.rb
100 | 101 | module One 102 | class Fred 103 | end 104 | $f1 = :Fred 105 | end 106 | 107 | module Two 108 | Fred = 1 109 | $f2 = :Fred 110 | end 111 | 112 | def Fred() 113 | end 114 | 115 | $f3 = :Fred 116 | 117 | 这里,变量 `$f1`,`$f2` 和 `$f3` 在三个不同的作用域内分配了符号 `:Fred`:模块 One,模块 Two 和 'main' 作用域。我将在第 12 章中对模块(modules)进行更多说明。现在,只需将它们视为定义不同作用域的“命名空间”(namespaces)即可。然而每个变量引用着相同的符号 `:Fred`,并且具有相同的 `object_id`: 118 | 119 | # All three display the same id! 120 | puts( $f1.object_id ) 121 | puts( $f2.object_id ) 122 | puts( $f3.object_id ) 123 | 124 | 即便如此,符号的“含义”(meaning)也会根据其作用域而变化。 125 | 126 | 换句话说,在模块 One 中,`:Fred` 引用类 `Fred`,在模块 Two 中,它引用常量 `Fred = 1`,在 main 作用域内引用 `Fred` 方法。 127 | 128 | 上一个程序的重写版本证实了这一点: 129 | 130 |
symbol_ref2.rb
131 | 132 | module One 133 | class Fred 134 | end 135 | $f1 = :Fred 136 | def self.evalFred( aSymbol ) 137 | puts( eval( aSymbol.id2name ) ) 138 | end 139 | end 140 | 141 | module Two 142 | Fred = 1 143 | $f2 = :Fred 144 | def self.evalFred( aSymbol ) 145 | puts( eval( aSymbol.id2name ) ) 146 | end 147 | end 148 | 149 | def Fred() 150 | puts( "hello from the Fred method" ) 151 | end 152 | 153 | $f3 = :Fred 154 | 155 | One::evalFred( $f1 ) #=> displays the module::class name: One::Fred 156 | Two::evalFred( $f2 ) #=> displays the Fred constant value: 1 157 | method($f3).call #=> calls Fred method: displays: "hello from the Fred method" 158 | 159 | 当然,由于变量 `$f1`,`$f2` 和 `$f3` 引用着相同的符号,因此你使用的变量是在任意地方指定的都是无关紧要的。以下产生完全相同的结果: 160 | 161 | One::evalFred( $f3 ) 162 | Two::evalFred( $f1 ) 163 | method($f2).call 164 | 165 | ### 符号和变量 166 | 167 |
symbols_2.rb
168 | 169 | 要了解符号(symbol)和标识符(例如变量名称)之间的关系,请查看我们的 **symbols_2.rb** 程序。首先将值 1 赋给局部变量 `x`。然后将符号 `:x` 赋给局部变量 `xsymbol`... 170 | 171 | x = 1 172 | xsymbol = :x 173 | 174 | 此时,变量 `x` 和符号 `:x` 之间没有明显的联系。我声明了一个方法,它只需要一些传入参数并使用 `p` 方法查看(inspects)和显示它。我可以使用变量和符号调用此方法: 175 | 176 | # Test 1 177 | amethod( x ) 178 | amethod( :x ) 179 | 180 | 这是该方法打印的数据结果: 181 | 182 | 1 183 | :x 184 | 185 | 换句话说,`x` 变量的值是 1,因为那是分配给它的值,而 `:x` 的值是 `:x`。但是出现了有趣的问题:如果 `:x` 的值是 `:x` 并且这也是变量 `x` 的符号名称,是否可以使用符号 `:x` 来查找变量 `x` 的值?困惑?希望下一行代码能这些更清楚: 186 | 187 | # Test 2 188 | amethod( eval(:x.id2name)) 189 | 190 | 这里,`id2name` 是 Symbol 类的一个方法。它返回与符号对应的名称或字符串(`to_s` 方法将执行相同的功能);最终结果是,当给出符号 `:x` 作为参数时,`id2name` 返回字符串 "x"。Ruby 的 `eval` 方法(在 Kernel 类中定义)能够计算字符串中的表达式。在本例中,这意味着它找到字符串 "x" 并尝试将其作为可执行代码进行计算。它发现 `x` 是变量的名称,并且 `x` 的值是 1。所以值 1 传递给 `amethod`。你可以通过运行 **symbols2.rb** 和比较代码的输出结果来验证这一点。 191 | 192 |
193 | 194 | 在第 20 章中更详细地解释了有关将数据作为代码来计算执行。 195 |
196 | 197 | 事情变得更加诡异。请记住,变量 `xsymbol` 已被赋予符号 `:x`... 198 | 199 | x = 1 200 | xsymbol = :x 201 | 202 | 这意味着如果我们 eval `:xsymbol`,我们可以获得分配给它的名称 - 即符号 `:x`。获得 `:x` 后我们可以继续计算它,给出 `x` 的值 - 即 1: 203 | 204 | # Test 3 205 | amethod( xsymbol ) #=> :x 206 | amethod( :xsymbol ) #=> :xsymbol 207 | amethod( eval(:xsymbol.id2name)) #=> :x 208 | amethod( eval( ( eval(:xsymbol.id2name)).id2name ) ) #=> 1 209 | 210 | 正如我们所见,当用于创建属性访问器(attribute accessors)时,符号可以引用方法名称。我们可以利用它将方法名称作为符号传递给 `method` 方法(是的,确实存在一个名为`'method'` 的方法),然后使用 `call` 方法调用指定的方法: 211 | 212 | #Test 4 213 | method(:amethod).call("") 214 | 215 | `call` 方法允许我们传递参数,为了方便,我们可以通过计算符号来传递一个参数: 216 | 217 | method(:amethod).call(eval(:x.id2name)) 218 | 219 | 如果这看起来很复杂,请看一下 **symbols_3.rb** 中的一个更简单的示例。这从以下赋值开始: 220 | 221 |
symbols_3.rb
222 | 223 | def mymethod( somearg ) 224 | print( "I say: " << somearg ) 225 | end 226 | 227 | this_is_a_method_name = method(:mymethod) 228 | 229 | 这里 `method(mymethod)` 查找一个方法,该方法的名称由作为参数传递的符号(`:mymethod`)指定,如果找到,则返回具有相应名称的 Method 对象。在我的代码中,我有一个名为 `mymethod` 的方法,现在将其分配给变量 `this_is_a_method_name`。 230 | 231 | 运行此程序时,你将看到第一行输出打印了变量的值: 232 | 233 | puts( this_is_a_method_name ) #=> This displays: # 234 | 235 | 这表明变量 `this_is_a_method_name` 已被赋予了方法 `mymethod`,该方法绑定到 Object 类(所有方法都作为'独立'(freestanding)函数输入)。要仔细检查变量是否真的是 Method 类的一个实例,下一行代码会打印出它的类: 236 | 237 | puts( "#{this_is_a_method_name.class}" ) #=> This displays: Method 238 | 239 | 好吧,如果它真的是一个真正的方法,那么我们应该可以调用它,不是吗?为此,我们需要使用 `call` 方法。这就是最后一行代码的作用: 240 | 241 | this_is_a_method_name.call( "hello world" ) #=> This displays: I say: hello world 242 | 243 | ### 为什么使用符号? 244 | 245 | Ruby 类库中的某些方法将符号(symbol)指定为参数。当然,如果你需要调用这些方法,则必须将符号传递给它们。但是,除了这些情况之外,没有绝对的要求你在自己的编程中使用符号。对于许多 Ruby 程序员来说,“常规”(conventional)数据类型(如字符串和整数)就足够了。 246 | 247 | 但是,符号确实在“动态”(dynamic)编程中占有特殊的地位。例如,Ruby 程序能够在运行时(runtime)通过在某个类的作用域内调用 `define_method` 来创建一个新方法,符号表示要定义的方法以及块表示该方法的代码: 248 | 249 |
add_method.rb
250 | 251 | class Array 252 | define_method( :aNewMethod, lambda{ |*args| puts( args.inspect) } ) 253 | end 254 | 255 | 执行上面的代码后,Array 类将获得一个名为 `aNewMethod` 的方法。你可以通过调用 `method_defined?` 并传入表示方法名称的符号来验证这一点: 256 | 257 | Array.method_defined?( :aNewMethod ) #=> returns: true 258 | 259 | 当然,你本身也可以调用该方法: 260 | 261 | [].aNewMethod( 1,2,3 ) #=> returns: [1,2,3] 262 | 263 | 你可以在运行时(runtime)以类似的方式删除现有的方法,在类中调用 `remove_method`,并传入提供被删除的方法的名称符号: 264 | 265 | class Array 266 | remove_method( :aNewMethod ) 267 | end 268 | 269 | 动态编程在需要程序仍在执行时修改 Ruby 程序本身的行为的应用程序中是非常有用的。例如,动态编程广泛用于 Rails 框架中。 270 | 271 | ## 深入探索 272 | 273 | ### 什么是符号? 274 | 275 | 之前,我说过符号(symbol)是一个标识符,其值就是它本身。从广义上讲,这描述了从 Ruby 程序员的角度来待看符号的行为方式。但它并没有告诉我们从 Ruby 解释器(interpreter)的角度来看,符号的字面意思是什么。实际上,符号是指向符号表(symbol table)的指针(pointer)。符号表是 Ruby 的已知标识符的内部列表 - 例如变量和方法名称。 276 | 277 | 如果你想深入了解 Ruby,你可以显示 Ruby 已知的所有符号,如下所示: 278 | 279 |
allsymbols.rb
280 | 281 | p( Symbol.all_symbols ) 282 | 283 | 这将显示数千个符号,包括方法名称,例如 `:to_s` 和 `:reverse`,全局变量,例如:`$/` 和 `:$DEBUG`,类名称,例如 `:Array` 和 `:Symbol`。你可以使用数组索引限制显示的符号数量,如下所示: 284 | 285 | p( Symbol.all_symbols[0,10] ) 286 | 287 | 但是你不能对符号进行排序,因为符号本身并不是连续的。显示符号排序列表的最简单方法是将它们转换为字符串并对其进行排序。在下面的代码中,我将 Ruby 已知的所有符号传递给一个块,该块将每个符号转换为一个字符串,并将字符串收集到一个新的数组中,该数组被分配给 `str_array` 变量。现在我可以对这个数组进行排序并显示结果: 288 | 289 | str_arr = Symbol.all_symbols.collect{ |s| s.to_s } 290 | puts( str_arr.sort ) -------------------------------------------------------------------------------- /markdown/15-chapter13.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | { 4 | "title": "第十三章", 5 | "ctime": "2018-12-23 00:49:00", 6 | "mtime": "2018-12-23 00:49:00" 7 | } 8 | 9 | --- 10 | 11 | # 第十三章 12 | 13 | *** 14 | 15 | ## Files 与 IO 16 | 17 | Ruby 提供了专门用于处理 IO – 输入和输出的类。其中最主要的是一个名为 IO 的类,这不足为奇。IO 类允许你打开和关闭 IO “流”(streams,字节序列),并向它们读写数据。 18 | 19 | 例如,假设你有一个名为 'textfile.txt' 的文件,它包含一些文本行,这就是你打开文件并在屏幕上显示每一行文本的方法: 20 | 21 |
io_test.rb
22 | 23 | IO.foreach("testfile.txt") {|line| print( line ) } 24 | 25 | 这里 `foreach` 是 IO 类的类方法,因此你不需要创建新的 IO 对象来使用它;相反,你只需将文件名指定为参数。`foreach` 方法接收一个块,从文件中读取的每一行都作为参数传递给它。你不必打开文件进行读操作,并在完成后关闭它(正如你根据其它语言的使用经验所预料的那样),因为 Ruby 的 `IO.foreach` 方法会为你完成这些操作。 26 | 27 | IO 有许多其它有用的方法。例如,你可以使用 `readlines` 方法将文件内容读入数组以进行进一步处理。这是一个简单的示例,它再次将文本行打印到屏幕: 28 | 29 | lines = IO.readlines("testfile.txt") 30 | lines.each{|line| print( line )} 31 | 32 | File 类是 IO 类的子类,上面的示例可以使用 File 类重写: 33 | 34 |
file_test.rb
35 | 36 | File.foreach("testfile.txt") {|line| print( line ) } 37 | 38 | lines = File.readlines("testfile.txt") 39 | lines.each{|line| print( line )} 40 | 41 | ### 打开和关闭文件 42 | 43 | 虽然一些标准方法会自动打开和关闭文件,但在处理文件内容时,你需要显式的打开和关闭文件。你可以使用 `new` 或 `open` 方法打开文件。你必须将两个参数传递给其中一个方法 - 文件名和文件 'mode' - 同时将返回一个新的 File 对象。文件模式(modes)可以是由操作系统指定的常量或字符串所定义的整数。该模式通常指示文件是打开以进行读取('r'),写入('w')还是读取和写入('rw')。这是可用字符串模式的列表: 44 | 45 | | Mode | Meaning | 46 | | ------ | ------ | 47 | | "r" | 只读,从文件开头开始(默认模式)。 | 48 | | "r+" | 读写,从文件开头开始。 | 49 | | "w" | 只写,将现有文件截断为零长度或创建用于写入的新文件。 | 50 | | "w+" | 读写,将现有文件截断为零长度或创建新文件以进行读写。 | 51 | | "a" | 只写,如果文件存在则从文件末尾开始,否则创建一个用于写入的新文件。 | 52 | | "a+" | 读写,如果文件存在则从文件末尾开始,否则创建一个用于读写的新文件。 | 53 | | "b" | (仅限 DOS/Windows)二进制文件模式(可能与上面列出的任何关键字母一起出现)。 | 54 | 55 |
open_close.rb
56 | 57 | 让我们看一下打开,处理和关闭文件的实际示例。在 **open_close.rb** 中,我首先打开一个文件 'myfile.txt',用于写入('w')。打开文件进行写入时,如果该文件尚不存在,则将创建该文件。我使用 `puts()` 在文件中写入六个字符串,在六行中分别写一个字符串。最后我关闭了文件。 58 | 59 | f = File.new("myfile.txt", "w") 60 | f.puts( "I", "wandered", "lonely", "as", "a", "cloud" ) 61 | f.close 62 | 63 | 关闭文件不仅会释放“文件句柄”(file handle,指向文件数据的指针),还会“刷新”(flushes)内存中的数据,以确保它全部保存到磁盘上的文件中。未能关闭文件可能会导致不可预测的副作用(尝试注释掉上面显示的 `f.close` 以便你自己查看!)。 64 | 65 | 现在,将文本写入文件后,让我们看看如何打开该文件并重新读取数据。这次我将一次读取一个数据中的字符。在我这样做的时候,我将保留已读过的字符数。我还会保留行数,每当我读入一个换行符时,行数都会递增(给定 ASCII 码 10)。为了清楚起见,我将在每行读取的末尾添加一个字符串,显示其行号。我将在屏幕上显示文件字符加上我的行结束字符串,当从文件中读取所有内容后,我将关闭它并显示我计算的统计数据。这是完整的代码: 66 | 67 | charcount = 0 68 | linecount = 0 69 | f = File.new("myfile.txt", "r") 70 | while !( f.eof ) do # while not at end of file... 71 | c = f.getc() # getc gets a single character 72 | if ( c == 10 ) then # ...whose ASCII code is tested 73 | linecount += 1 74 | puts( " " ) 75 | else 76 | putc( c ) # putc here puts the char to screen 77 | charcount += 1 78 | end 79 | end 80 | 81 | if f.eof then 82 | puts( "" ) 83 | end 84 | f.close 85 | puts("This file contains #{linecount} lines and #{charcount} characters." ) 86 | 87 | ### 文件和目录... 88 | 89 | 你还可以使用 File 类来操作磁盘上的文件(files)和目录(directories)。在尝试对文件执行某些操作之前,你必须自然地确保该文件存在。毕竟,它可能在程序启动后被重命名或删除 - 或者用户可能错误地输入了文件或目录名称。 90 | 91 | 你可以使用 `File.exist?` 方法验证文件是否。这是 FileTest 模块提供给 File 类的几种测试方法之一。就 `File.exist?` 方法而言,一个目录记为一个文件,所以你可以使用下面的代码来测试是否存在 C:\ 驱动器(注意你必须在字符串中使用双文件分隔符 '\\',单个 '\' 将被视为转义字符): 92 | 93 |
file_ops.rb
94 | 95 | if File.exist?( "C:\\" ) then 96 | puts( "Yup, you have a C:\\ directory" ) 97 | else 98 | puts( "Eeek! Can't find the C:\\ drive!" ) 99 | end 100 | 101 | 如果要区分目录和数据文件,请使用 `directory?` 方法: 102 | 103 | def dirOrFile( aName ) 104 | if File.directory?( aName ) then 105 | puts( "#{aName} is a directory" ) 106 | else 107 | puts( "#{aName} is a file" ) 108 | end 109 | end 110 | 111 | ### 复制文件 112 | 113 | 让我们通过编写一个简单的文件备份程序将 File 类用于实际用途。当你运行 **copy_files.rb** 时,将要求你选择要从中复制的目录(源目录)和要复制到的另一个目录(目标目录)。假设两个目录都存在,程序将把所有文件从源目录复制到目标目录。如果目标目录不存在,它将询问你是否要创建它(你应该输入,'Y' 接受)。我已经为你提供了一个源目录;只需在提示时输入名称 **srcdir**。当询问目标目录时,输入 **targetdir** 以在当前目录下创建该名称的子目录。 114 | 115 | 程序使用源目录的路径初始化变量 `sourcedir`,并使用目标目录的名称初始化 `targetdir`。这是执行文件复制的代码: 116 | 117 |
copy_files.rb
118 | 119 | Dir.foreach( sourcedir ){ 120 | |f| 121 | filepath = "#{sourcedir}\\#{f}" 122 | if !(File.directory?(filepath) ) then 123 | if File.exist?("#{targetdir}\\#{f}") then 124 | puts("#{f} already exists in target directory (not copied)" ) 125 | else 126 | FileUtils.cp( filepath, targetdir ) 127 | puts("Copying... #{filepath}" ) 128 | end 129 | end 130 | } 131 | 132 | 在这里,我使用了 Dir 类的 `foreach` 方法,该方法将指定目录中每个文件的文件名传递给块变量 `f`。我很快就会说到关于 Dir 类的东西。该代码通过将文件名附加到 `sourcedir` 变量给出的目录名来构造合适的文件路径 `filepath`。我只想复制数据文件而不是目录,所以我测试文件路径是文件而不是目录: 133 | 134 | if !(File.directory?(filepath) ) 135 | 136 | 此程序不会复制已存在的文件,因此它首先检查目标目录 `targetdir` 中是否已存在名称为 `f` 的文件: 137 | 138 | if File.exist?("#{targetdir}\\#{f}") 139 | 140 | 最后,假设满足所有指定条件,源文件 `filepath` 将复制到 `targetdir`: 141 | 142 | FileUtils.cp( filepath, targetdir ) 143 | 144 | 这里的 `cp` 是 FileUtils 模块中的文件复制方法。该模块还包含许多其它有用的文件处理例程,例如 `mv(source,target)` 用于将文件从 `source` 移动到 `target`;`rm(files)` 将删除 files 参数列出的一个或多个文件,`mkdir` 将创建一个目录,就像我在当前程序中创建 `targetdir` 时所做的那样: 145 | 146 | FileUtils.mkdir( targetdir ) 147 | 148 | ### 目录查询 149 | 150 | 我的备份程序一次只处理一个目录级别 - 这就是为什么它在尝试复制之前测试文件 `f` 不是目录的原因。但是,有很多次,你可能想要遍历子目录。举个例子,让我们编写一个程序来计算指定根目录下所有子目录的大小。例如,如果你想要找到最大的文件和目录,以便通过存档或删除它们来释放磁盘空间,这可能很有用。 151 | 152 | 浏览子目录为我们提供了一个有趣的编程问题。当我们开始搜索存在的子目录时,我们不知道我们是否会找到一个,没有或者多个。此外,我们找到的任何子目录可能包含另一级子目录,每个子目录可能包含其它子目录,依此类推,通过许多可能的级别。 153 | 154 | ### 关于递归的讨论 155 | 156 | 我们的程序需要能够将整个子目录树向下导航到任意数量的级别。为了能够做到这一点,我们必须使用递归。 157 | 158 |
159 |

什么是递归(Recursion)?

160 | 161 | 简单的说,递归方法就是调用它自己的。如果你不熟悉递归编程,请参阅本章末尾的“深入探索”部分中的“简单递归”。 162 |
163 | 164 |
file_info.rb
165 | 166 | 在程序 **file_info.rb** 中,`processfiles` 方法是递归的: 167 | 168 | def processfiles( aDir ) 169 | totalbytes = 0 170 | Dir.foreach( aDir ){ 171 | |f| 172 | mypath = "#{aDir}\\#{f}" 173 | s = "" 174 | if File.directory?(mypath) then 175 | if f != '.' and f != '..' then 176 | bytes_in_dir = processfiles(mypath) # <==== recurse! 177 | puts( " ---> #{mypath} contains [#{bytes_in_dir/1024}] KB" ) 178 | end 179 | else 180 | filesize = File.size(mypath) 181 | totalbytes += filesize 182 | puts ( "#{mypath} : #{filesize/1024}K" ) 183 | end 184 | } 185 | $dirsize += totalbytes 186 | return totalbytes 187 | end 188 | 189 | 你将看到,当首次调用该方法时,向下到源代码的底部,它将在变量 `dirname` 中传递一个目录的名称: 190 | 191 | processfiles( dirname ) 192 | 193 | 我已经将当前目录的父级(由两个点给出,`".."`)分配给 `dirname`。如果你在其原始位置运行此程序(即,从本书的源代码存档中提取其位置),则将引用包含所有示例代码文件的子目录的目录。或者,你可以将硬盘上某个目录的名称分配给代码中指定的变量 `dirname`。如果你这样做,不要指定包含大量文件和目录的目录(**"C:\ Program Files"** 不是一个好的选择!),因为程序需要一些时间来执行。 194 | 195 | 让我们仔细看看 `processfiles` 方法中的代码。再次,我使用 `Dir.foreach` 查找当前目录中的所有文件,并一次传递一个文件 `f`,由花括号之间的块中的代码处理。如果 `f` 是一个目录但不是当前目录(`"."`)或其父目录(`".."`),那么我将目录的完整路径传递回 `processfiles` 方法: 196 | 197 | if File.directory?(mypath) then 198 | if f != '.' and f != '..' then 199 | bytes_in_dir = processfiles(mypath) 200 | 201 | 如果 `f` 不是目录,而只是一个普通的数据文件,我用 `File.size` 计算它的大小(以字节为单位)并将其分配给变量 `filesize`: 202 | 203 | filesize = File.size(mypath) 204 | 205 | 由于每个连续文件 `f` 由代码块处理,因此计算其大小并将此值添加到变量 `totalbytes`: 206 | 207 | totalbytes += filesize 208 | 209 | 将当前目录中的每个文件传递到块后,`totalbytes` 将等于目录中所有文件的总大小。 210 | 211 | 但是,我还需要计算所有子目录中的字节数。由于该方法是递归的,因此这是自动完成的。请记住,当 `processfiles` 方法中大括号之间的代码确定当前文件f是一个目录时,它会将此目录名称传递回自身 - `processfiles` 方法。 212 | 213 | 让我们假设首先使用 **C:\test** 目录调用 `processfiles`。在某些时候,变量 `f` 被赋予其子目录之一的名称 - 比如 **C:\test\dir_a**。现在这个子目录被传递回 `processfiles`。在 **C:\test\dir_a** 中找不到更多目录,因此 `processfiles` 只计算该子目录中所有文件的大小。当它完成计算这些文件时,`processfiles` 方法结束并将当前目录中的字节数 `totalbytes` 返回到首先调用该方法的代码位置: 214 | 215 | return totalbytes 216 | 217 | 在这种情况下,`processfiles` 方法本身内部的这段代码以递归方式调用 `processfiles` 方法: 218 | 219 | bytes_in_dir = processfiles(mypath) 220 | 221 | 因此,当 `processfiles` 完成处理子目录 **C:\test\dir_a** 中的文件时,它返回在那里找到的所有文件的总大小,并将其分配给 `bytes_in_dir` 变量。`processfiles` 方法现在从它停止的地方继续(也就是说,它从它自己处理子目录的地方继续)以处理原始目录 **C:\test** 中的文件。 222 | 223 | 无论此方法遇到多少级别的子目录,每当它找到目录时都会调用它自己的事实确保它会自动沿着它找到的每个目录路径向下移动,计算每个子目录中的总字节数。 224 | 225 | 最后要注意的是,在每个递归级别完成时,分配给 `processfiles` 方法内部声明的变量的值将更改回其“之前”的值。因此,`totalbytes` 变量首先包含 **C:\test\test_a\test_b** 的大小,然后是 **C:\test\test_a** 的大小,最后是 **C:\test** 的大小。为了保证运行结果总和是所有目录的组合大小,我们需要将值分配给在方法外部声明的变量。为此,我使用全局变量 `$dirsize` 来实现这个目的,将处理的每个子目录计算的 `totalbytes` 值增加到该变量: 226 | 227 | $dirsize += totalbytes 228 | 229 | 顺便提一下,虽然字节(byte)对于非常小的文件来说可能是很方便的测量单位,但通常更好的是以千字节(kilobyte)描述更大的文件,以兆字节(megabytes)描述非常大的文件或目录。要将字节转换为千字节或将千字节转换为兆字节,你需要除以 1024。要将字节转换为兆字节,除以 1048576。 230 | 231 | 我程序中的最后一行代码执行这些计算,并使用 Ruby 的 `printf` 方法以格式化字符串显示结果: 232 | 233 | printf( "Size of this directory and subdirectories is #{$dirsize} bytes, #{$dirsize/1024}K, %0.02fMB", "#{$dirsize/1048576.0}" ) 234 | 235 | 请注意,我在第一个字符串中嵌入了格式化占位符 "%0.02fMB",并在逗号后面添加了第二个字符串: 236 | 237 | "#{$dirsize/1048576.0}". 238 | 239 | 第二个字符串计算目录大小(以兆字节为单位),然后将该值替换为第一个字符串中的占位符。占位符的格式选项 `"%0.02f"` 确保兆字节值显示为浮点数 `"f"`,带有两个小数位,`"0.02"`。 240 | 241 | ### 根据大小排序 242 | 243 | 目前,该程序按字母顺序打印文件和目录名称及其大小。但我对它们的相对大小更感兴趣。因此,如果文件按大小而不是按名称排序,则会更有用。 244 | 245 | 为了能够对文件进行排序,我们需要一些方法来存储所有文件大小的完整列表。一种显而易见的方法是将文件大小添加到数组中。在 **file_info2.rb** 中,我创建了一个空数组 `$files`,并且每次处理文件时,我都会将其大小附加到数组中: 246 | 247 |
file_info2.rb
248 | 249 | $files << fsize 250 | 251 | 然后,我可以对文件大小进行排序,以显示从低到高的值或(通过排序然后反转数组),从高到低的值: 252 | 253 | $files.sort # sort low to high 254 | $files.sort.reverse # sort high to low 255 | 256 | 唯一的问题是我现在最终得到一个没有相关文件名的文件大小数组。更好的解决方案是使用 Hash 而不是 Array。我在 **file_info3.rb** 中完成了这个。首先,我创建两个空 Hash: 257 | 258 |
file_info3.rb
259 | 260 | $dirs = {} 261 | $files = {} 262 | 263 | 现在,当 `processfiles` 方法遇到目录时,它会向 `$dirs` 哈希添加一个新元素,使用完整目录路径 `mypath` 作为键,目录大小 `dsize` 作为值: 264 | 265 | $dirs[mypath] = dsize 266 | 267 | 同样的将键值对添加到 `$files` 哈希中。当通过递归调用 `processfiles` 方法处理子目录和文件的整个结构时,`$dirs` 哈希变量将包含目录名和大小的键值对,`$files` 哈希将包含文件名的键值对和大小。 268 | 269 | 现在剩下的就是对这些哈希进行排序和显示。Hash 的标准排序方法是对键进行排序,而不是值。 我想根据值(大小)排序,而不是根据键(名称)。为了做到这一点,我已经定义了这个自定义排序方法: 270 | 271 | $files.sort{|a,b| a[1]<=>b[1] 272 | 273 | 这里 `sort` 遍历将(directory-walking) `$files` 哈希转换为 `[key,value]` 对的嵌套数组,并将其中的两个作为 `a` 和 `b` 传递到花括号之间的块中。每个 `[key,value]` 对的第二项(在索引 `[1]` 处)提供值。使用 Ruby 的 `<=>` 比较方法对值进行排序。最终结果是,该程序现在首先按升序(按大小)显示文件列表,然后类似的显示排序的目录列表。 274 | 275 | ## 深入探索 276 | 277 | ### 简单递归 278 | 279 |
recursion.rb
280 | 281 | 如果你之前从未使用过递归(recursion),则本章中的递归“目录遍历”(directory-walking)方法可能需要一些说明。为了阐明递归是如何工作的,让我们看一个更简单的例子。加载 **recursion.rb** 程序: 282 | 283 | $outercount = 0 284 | 285 | def addup( aNum ) 286 | aNum += 1 287 | $outercount +=1 288 | puts( "aNum is #{aNum}, $outercount is #{$outercount}" ) 289 | if $outercount < 3 then 290 | addup( aNum ) #<= recursive call to addup method 291 | end 292 | puts( "At END: aNum is #{aNum}, outercount is #{$outercount}" ) 293 | end 294 | 295 | addup( 0 ) #<= This is where it all begins 296 | 297 | 这包含递归方法 `addup`,其唯一的目的是从 1 到 3 计数。`addup` 方法接收一个整数值作为传入参数 `aNum`。 298 | 299 | addup( aNum ) 300 | 301 | 还有全局变量 `$outercount`,它存在于 `addup` 方法之外。每当 `addup` 方法执行时,1 将添加到 `aNum`,1 也会添加到 `$outercount`。然后,只要 `$outercount` 小于 3,`addup` 方法中的代码就会再次调用相同的方法(`addup`),并将 `aNum` 的新值传递给它: 302 | 303 | if $outercount < 3 then 304 | addup( aNum ) 305 | end 306 | 307 | 让我们来看看会发生什么。通过值 0 来调用 `addup` 以启动整个过程: 308 | 309 | addup( 0 ) 310 | 311 | `addup` 方法将 `aNum` 和 `$outercount` 都加 1,因此两个变量现在都具有值 1。测试 `test($outercount < 3)` 的计算结果为 true,因此 `aNum` 作为参数传递给 `addup`。再次向两个变量添加 1,因此 `aNum` 现在为 2,`$outercount` 也为 2。现在 `aNum` 再次传递给 `addup`。然后再将 1 添加到两个变量中,给出每个值 3。然而,这次测试条件失败,因为 `$outercount` 不再小于 3。因此调用 `addup` 的代码被跳过,我们到达方法的最后一行: 312 | 313 | puts( "At END: aNum is #{aNum}, outercount is #{$outercount}" ) 314 | 315 | 这会打印出 `aNum` 和 `$outercount` 的值,正如我们所料,它们都是 3。 316 | 317 | 现在已经到达此方法的末尾,“控制流”会在最初调用该方法的代码之后立即返回到代码行。这里,调用 `addup` 方法的代码行恰好位于方法本身内部。这里是: 318 | 319 | addup( aNum ) 320 | 321 | 此后的第一个可执行代码行是(再次)方法的最后一行,它打印出两个变量的值: 322 | 323 | puts( "At END: aNum is #{aNum}, outercount is #{$outercount}" ) 324 | 325 | 所以我们回到了之前的“执行点” - 我们递归调用 `addup` 方法的点。那时,`aNum` 的值是 2,也是它现在的值。如果这看起来令人困惑,那就试着想想如果 `aNum` 已经是 2 ,然后我们调用其它一些不相关的方法,那么会发生什么。从该方法返回时,`aNum` 当然仍然具有值 2。这就是发生在这里的一切。唯一的区别是这种方法恰好调用自己而不是其它方法。 326 | 327 | 该方法再一次退出,控制再次返回到调用该方法的代码之后的下一个可执行代码行 - 并且 `aNum` 的值又回到了自己的历史记录中 - 它现在具有值 1。但是,`$outercount` 变量存在于方法之外,不受递归的影响,因此它仍然是 3。 328 | 329 |
330 | 331 | 如果你可以访问可视化调试器,那么如果在第 9 行放置一个断点(`if $outercount < 3 then`),将 `aNum` 和 `$outercount` 添加到 Watch 窗口,并在你命中断点之后重复进入代码,整个过程将变得更加清晰。 332 | 333 |
334 | 335 |

336 | 此屏幕截图显示了在 Ruby In Steel 中调试的递归程序。我可以单步执行源代码,使用调用堆栈来跟踪当前递归的“级别”(调用 addup 方法的次数),并使用Watch 窗口监视变量的当前值。 337 |

338 |
339 |
-------------------------------------------------------------------------------- /markdown/16-chapter14.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | { 4 | "title": "第十四章", 5 | "ctime": "2018-12-25 00:02:00", 6 | "mtime": "2018-12-25 00:02:00" 7 | } 8 | 9 | --- 10 | 11 | # 第十四章 12 | 13 | *** 14 | 15 | ## YAML 16 | 17 | 在某些时候,大多数桌面应用程序都希望在磁盘上保存和读取结构化数据。我们已经看到了如何使用简单的 IO 例程(如 `gets` 和 `puts`)读取和写入数据。但是,如何编写保存和恢复混合对象类型列表中的数据?使用 Ruby 执行此操作的一种简单方法是使用 YAML。 18 | 19 |
20 | 21 | **YAML** 是 "Yet An-other Markup Language"(仍是一种标记语言,有争议)或 "YAML Ain't Markup Language" (不是标记语言,递归的)的首字母缩写。 22 |
23 | 24 | ### 转换成 YAML 25 | 26 | YAML 定义了一种序列化(数据保存)格式,它将信息存储为人类可读的文本。YAML 可以与各种编程语言一起使用,为了在 Ruby 中使用它,你的代码需要使用 **yaml.rb** 文件。通常,这可以通过在代码单元的顶部加载或“引入”(requiring)文件来完成,如下所示: 27 | 28 | require 'yaml' 29 | 30 | 完成此操作后,你将可以访问各种方法将 Ruby 对象转换为 YAML 格式,以便将其数据写入文件。随后,你将能够回读已保存的数据并使用它来重新构造 Ruby 对象。 31 | 32 | 要将对象转换为 YAML 格式,可以使用 `to_yaml` 方法。它可以转换任何对象 - 字符串,整数,数组,哈希等。例如,这是转换字符串的方式: 33 | 34 |
to_yaml1.rb
35 | 36 | "hello world".to_yaml 37 | 38 | 这是如何转换数组: 39 | 40 | ["a1", "a2" ].to_yaml 41 | 42 | 这是你通过此数组转换获得的 YAML 格式: 43 | 44 | --- 45 | - a1 46 | - a2 47 | 48 | 请注意定义新 YAML '文档'的开头的三个破折号以及定义列表中每个新元素的单个破折号。有关 YAML 格式的更多信息,请参阅本章末尾的“深入探索”部分。 49 | 50 | 你还可以将非标准类型的对象转换为 YAML。例如,假设你创建了此类和对象... 51 | 52 |
to_yaml2.rb
53 | 54 | class MyClass 55 | def initialize( anInt, aString ) 56 | @myint = anInt 57 | @mystring =aString 58 | end 59 | end 60 | 61 | ob1 = MyClass.new( 100, "hello world" ).to_yaml 62 | 63 | 此对象的 YAML 表示形式将以文本 `!ruby/object:` 开头,后跟类名,每行一个变量名称附加冒号(但减去 `@`)及其值: 64 | 65 | --- !ruby/object:MyClass 66 | myint: 100 67 | mystring: hello world 68 | 69 | 如果要打印出对象的 YAML 表示,可以使用方法 `y()`,它是一种 YAML 的方法,等同于我们熟知的用来查看并打印正常的 Ruby 对象的 `p()` 方法: 70 | 71 |
yaml_test1.rb
72 | 73 | y( ['Bert', 'Fred', 'Mary'] ) 74 | 75 | 这将显示: 76 | 77 | --- 78 | - Bert 79 | - Fred 80 | - Mary 81 | 82 | 你可以同样的显示一个哈希对象... 83 | 84 | y( { 'fruit' => 'banana', :vegetable => 'cabbage', 'number' => 3 } ) 85 | 86 | ...在这种情况下,每个键/值对都放在一个新行上: 87 | 88 | --- 89 | number: 3 90 | fruit: banana 91 | :vegetable: cabbage 92 | 93 | 或者你可以显示自己的“自定义”对象... 94 | 95 | t = Treasure.new( 'magic lamp', 500 ) 96 | y( t ) 97 | 98 | ...它显示的数据,如前面我使用 `to_yaml` 的示例一样,顶部是类名以及连续行上是一对变量名和值: 99 | 100 | --- !ruby/object:Treasure 101 | name: magic lamp 102 | value: 500 103 | 104 |
yaml_test2.rb
105 | 106 | 你甚至可以使用 `y()` 来显示非常复杂的对象,例如嵌套数组: 107 | 108 | arr1 = [ ["The Groovesters", "Groovy Tunes", 12 ], 109 | [ "Dolly Parton", "Greatest Hits", 38 ] 110 | ] 111 | 112 | y( arr1 ) 113 | 114 | ...或包含任意类型对象的数组: 115 | 116 | arr2 = [ CD.new("The Beasts", "Beastly Tunes", 22), 117 | CD.new("The Strolling Bones", "Songs For Senior Citizens", 38) 118 | ] 119 | 120 | y( arr2 ) 121 | 122 | ### 嵌套序列 123 | 124 | 当相关的数据序列(例如数组)嵌套在其它数据序列中时,这种关系由缩进表示。所以,例如,假设我们在 Ruby 中声明了这个数组... 125 | 126 |
nested_arrays.rb
127 | 128 | arr = [1,[2,3,[4,5,6,[7,8,9,10],"end3"],"end2"],"end1"] 129 | 130 | 当呈现为 YAML(例如,通过 `y(arr)`)时,这变为: 131 | 132 | --- 133 | - 1 134 | - - 2 135 | - 3 136 | - - 4 137 | - 5 138 | - 6 139 | - - 7 140 | - 8 141 | - 9 142 | - 10 143 | - end3 144 | - end2 145 | - end1 146 | 147 | ### 保存 YAML 数据 148 | 149 | `dump` 方法提供了另一种方便的方式将 Ruby 对象转换为 YAML 格式。最简单的是,它会将你的 Ruby 数据转换为 YAML 格式并将其转储为字符串: 150 | 151 |
yaml_dump1.rb
152 | 153 | arr = ["fred", "bert", "mary"] 154 | yaml_arr = YAML.dump( arr ) # yaml_arr is now: "--- \n- fred\n- bert\n- mary\n" 155 | 156 | 更有用的是,`dump` 方法可以接收第二个参数,它是某种 IO 对象,通常是文件(file)。你可以打开文件并将数据转储给它... 157 | 158 |
yaml_dump2.rb
159 | 160 | f = File.open( 'friends.yml', 'w' ) 161 | YAML.dump( ["fred", "bert", "mary"], f ) 162 | f.close 163 | 164 | ...或者你可以打开文件(或其它类型的 IO 对象)并将其传递到关联的块中: 165 | 166 | File.open( 'morefriends.yml', 'w' ){ |friendsfile| 167 | YAML.dump( ["sally", "agnes", "john" ], friendsfile ) 168 | } 169 | 170 | 如果使用块,则退出块时文件将自动关闭,否则应使用 `close` 方法显式关闭文件。顺便提一下,你也可以以类似的方式使用块来打开文件并读入 YAML 数据: 171 | 172 | File.open( 'morefriends.yml' ){ |f| 173 | $arr= YAML.load(f) 174 | } 175 | 176 | ### 保存时忽略变量 177 | 178 | 如果由于某种原因,在序列化对象时要省略某些实例变量,可以通过定义名为 `to_yaml_properties` 的方法来实现。 179 | 180 | 在此方法的主体中,放置一个字符串数组。每个字符串应与要保存的实例变量的名称匹配。任何未指定的变量都不会被保存。看看这个示例: 181 | 182 |
limit_y.rb
183 | 184 | class Yclass 185 | def initialize(aNum, aStr, anArray) 186 | @num = aNum 187 | @str = aStr 188 | @arr = anArray 189 | end 190 | 191 | def to_yaml_properties 192 | ["@num", "@arr"] #<= @str will not be saved! 193 | end 194 | end 195 | 196 | 这里 `to_yaml_properties` 限制了当你调用 `YAML.dump` 时被保存的变量仅为 `@num` 和 `@arr`。字符串变量 `@str` 将不会被保存。如果你以后希望根据保存的 YAML 数据重建对象,则你有义务确保“缺失”变量是不被需要的(在这种情况下可以忽略它们),或者如果需要,它们应该用一些有意义的值初始化: 197 | 198 | ob = Yclass.new( 100, "fred", [1,2,3] ) # ...creates object with @num=100, @str="fred", @arr=[1,2,3] 199 | 200 | yaml_ob = YAML.dump( ob ) #...dumps to YAML only the @num and @arr data (omits @str) 201 | 202 | ob2 = YAML.load( yaml_ob ) #...creates ob2 from dumped data with @num=100, @arr=[1,2,3] , but without @str 203 | 204 | ### 一个文件中多个文档 205 | 206 | 早些时候,我提到过三个破折号用于标记新的 YAML “文档”(document)的开头。在 YAML 术语中,文档是离散的组或片段。单个文件可能包含许多此类“文档”。 207 | 208 | 例如,假设你要将两个数组 `arr1` 和 `arr2` 保存到文件 **'multidoc.yml'**。 这里 `arr1` 是一个包含两个嵌套数组的数组,`arr2` 是一个包含两个 CD 对象的数组: 209 | 210 |
multi_docs.rb
211 | 212 | arr1 = [ ["The Groovesters", "Groovy Tunes", 12 ], 213 | [ "Dolly Parton", "Greatest Hits", 38 ] 214 | ] 215 | 216 | arr2 = [ CD.new("Gribbit Mcluskey", "Fab Songs", 22), 217 | CD.new("Wayne Snodgrass", "Singalong-a-Snodgrass", 24) 218 | ] 219 | 220 | 这是我将这些数组转储到 YAML 并将它们写入文件的例程(如第 13 章所述,`'w'` 参数导致文件以写入模式被打开): 221 | 222 | File.open( 'multidoc.yml', 'w' ){ |f| 223 | YAML.dump( arr1, f ) 224 | YAML.dump( arr2, f ) 225 | } 226 | 227 | 查看文件 **'multidoc.yml'**,你将看到数据已保存为两个单独的'文档' - 每个文档以三个破折号开头: 228 | 229 | --- 230 | - - The Groovesters 231 | - Groovy Tunes 232 | - 12 233 | - - Dolly Parton 234 | - Greatest Hits 235 | - 38 236 | --- 237 | - !ruby/object:CD 238 | artist: Gribbit Mcluskey 239 | name: Fab Songs 240 | numtracks: 22 241 | - !ruby/object:CD 242 | artist: Wayne Snodgrass 243 | name: Singalong-a-Snodgrass 244 | numtracks: 24 245 | 246 | 现在,我需要找到一种通过将数据作为两个文档读取来重建这些数组的方法。`load_documents` 方法提供了该解决方式。 247 | 248 | `load_documents` 方法调用一个块并将每个连续文档传递给它。下面是一个如何使用此方法从两个 YAML 文档重建两个数组(放在另一个数组 `$new_arr` 中)的示例: 249 | 250 | File.open( 'multidoc.yml' ) {|f| 251 | YAML.load_documents( f ) { |doc| 252 | $new_arr << doc 253 | } 254 | } 255 | 256 | 你可以通过执行以下操作来验证是否已使用两个数组初始化 `$new_arr`: 257 | 258 | puts( "$new_arr contains #{$new_arr.size} elements" ) 259 | p( $new_arr[0] ) 260 | p( $new_arr[1] ) 261 | 262 | 或者,这是一种更通用的做同样事情的方法,它适用于任何长度的数组: 263 | 264 | $new_arr.each{ |arr| p( arr ) } 265 | 266 | ### YAML 数据库 267 | 268 | 有关以 YAML 格式保存和加载数据的稍微复杂的应用程序的示例,你可能需要查看 **cd_db.rb**。这里实现了一个简单的 CD 数据库。它定义了三种类型的 CD 对象 - 一个基本 CD,其中包含有关名称,艺术家和轨道数量的数据以及两个更专业的后代类 - PopCD,它添加了关于类型(例如“摇滚”或“乡村”)的数据以及 ClassicalCD 添加了导师和作曲家的数据。 269 | 270 | 当程序运行时,用户可以输入数据以创建这三种类型中的任何一种的新 CD 对象。还有一个将数据保存到磁盘的选项。随后运行应用程序时,将重新加载现有数据。 271 | 272 | 数据本身在代码中被组织得非常简单(甚至微不足道!),在创建对象本身之前将每个对象的数据读入数组。整个 CD 对象数据库被保存到全局变量 `$cd_arr` 中,并将其写入磁盘并使用 YAML 方法重新加载到内存中: 273 | 274 |
cd_db.rb
275 | 276 | def saveDB 277 | File.open( $fn, 'w' ) { 278 | |f| 279 | f.write($cd_arr.to_yaml) 280 | } 281 | end 282 | 283 | def loadDB 284 | input_data = File.read( $fn ) 285 | $cd_arr = YAML::load( input_data ) 286 | end 287 | 288 | 在现实世界的应用程序中,我确信你希望创建一些更优雅的数据结构来管理你的 Dolly Parton 集合! 289 | 290 | ### YAML 冒险游戏 291 | 292 | 作为使用 YAML 的最后一个示例,我为冒险游戏(**gamesave_y.rb**)提供了一个基本框架。这会创建一些 Treasure 对象和一些 Room 对象。Treasure 对象被放入 Room 对象中(也就是说,它们被放置在 Rooms 包含的数组中),然后 Room 对象被放入 Map 对象中。这具有构造中等复杂数据结构的效果,其中一种类型的对象(Map)包含任意数量的另一种类型的对象(Rooms),每个 Room 对象可以包含零个或多个其它类型的对象(Treasures))。 293 | 294 | 乍一看,找到一种将混合对象类型的整个网络存储到磁盘并在稍后重建该网络的方法可能看起来像编程噩梦。 295 | 296 | 事实上,由于 Ruby 的 YAML 库提供的序列化功能,保存和恢复这些数据几乎没有更容易的了。这是因为序列化(serialization)减轻了你逐个保存每个对象的繁琐工作。相反,你只需要“转储”(dump)顶级对象 - 这里就是 Map 对象 `mymap`。 297 | 298 | 完成此操作后,将自动为你保存顶级对象“包含”的任何对象(如 Rooms)或被包含对象本身包含的对象(如 Treasures)。然后可以通过在单个操作中加载所有已保存的数据并将其分配给“顶级”对象(此处为 map)来重建它们: 299 | 300 |
gamesave_y.rb
301 | 302 | # Save mymap 303 | File.open( 'game.yml', 'w' ){ |f| 304 | YAML.dump( mymap, f ) 305 | } 306 | 307 | # Reload mymap 308 | File.open( 'game.yml' ){ |f| 309 | mymap = YAML.load(f) 310 | } 311 | 312 | ## 深入探索 313 | 314 | ### YAML 的简要指南 315 | 316 | 在 YAML 中,数据被分成包含“序列”(sequences)数据的“文档”。每个文档以三个短划线字符 `---` 开头,列表中的每个单独元素都以单个短划线字符 `-` 开头。因此,例如,这是一个 YAML 数据文件,包含一个文档和两个列表项: 317 | 318 | --- 319 | - artist: The Groovesters 320 | name: Groovy Tunes 321 | numtracks: 12 322 | - artist: Dolly Parton 323 | name: Greatest Hits 324 | numtracks: 38 325 | 326 | 在上面的示例中,你可以看到每个列表项由两部分组成 - 名称如 `artist:`(在每个列表项中相同)和右侧的一段数据,例如 `Dolly Parton`,可能因每个列表项而异。这些项类似于 Ruby 的 Hash 中的键值对。YAML 将键值列表称为“映射”(maps)。 327 | 328 | 下面是一个包含两个项目的列表的 YAML 文档,每个项目包含三个项目 - 换句话说,它是包含两个三项“嵌套”数组的数组的 YAML 表示形式: 329 | 330 | --- 331 | - - The Groovesters 332 | - Groovy Tunes 333 | - 12 334 | - - Dolly Parton 335 | - Greatest Hits 336 | - 38 337 | 338 | 现在让我们看看 YAML 如何处理嵌套的哈希(Hashes)。 339 | 340 | 思考这个 Hash: 341 | 342 |
hash_to_yaml.rb
343 | 344 | hsh = { :friend1 => 'mary', 345 | :friend2 => 'sally', 346 | :friend3 => 'gary', 347 | :morefriends => { :chap_i_met_in_a_bar => 'simon', 348 | :girl_next_door => 'wanda' 349 | } 350 | } 351 | 352 | 正如我们已经看到的,Hash 在 YAML 中很自然地表示为键值对列表。但是,在上面显示的示例中,关键字 `:morefriends` 与嵌套哈希值相关联。YAML 如何表示?事实证明,与数组一样(参见本章前面的*“嵌套序列”*),它只是缩进嵌套的哈希: 353 | 354 | :friend1: mary 355 | :friend2: sally 356 | :friend3: gary 357 | :morefriends: 358 | :chap_i_met_in_a_bar: simon 359 | :girl_next_door: wanda 360 | 361 |
362 | 363 | 有关 YAML 的详细信息,请访问 **http://yaml.org** 364 |
365 | 366 | 随 Ruby 提供的 YAML 库非常庞大且复杂,并且有许多方法可供你使用,而不仅是本章所述的。但是,你现在应该对 YAML 有了足够的了解,以便在你自己的程序中使用它。你可以在闲暇时慢慢地探索 YAML 库。 367 | 368 | 但事实证明,YAML 并不是在 Ruby 中序列化数据的唯一方法。我们将在下一章中讨论另一种方式。 -------------------------------------------------------------------------------- /markdown/17-chapter15.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | { 4 | "title": "第十五章", 5 | "ctime": "2018-12-27 23:29:00", 6 | "mtime": "2018-12-27 23:29:00" 7 | } 8 | 9 | --- 10 | 11 | # 第十五章 12 | 13 | *** 14 | 15 | ## Marshal 16 | 17 | Ruby 的 Marshal 库提供了另一种保存和加载数据的方式。它有一组类似于 YAML 中的方法,使你可以将数据保存到磁盘上,并可以从磁盘上加载数据。 18 | 19 | ### 保存与加载数据 20 | 21 | 将该程序与前一章中的 **yaml_dump2.rb** 进行比较: 22 | 23 |
marshal1.rb
24 | 25 | f = File.open( 'friends.sav', 'w' ) 26 | Marshal.dump( ["fred", "bert", "mary"], f ) 27 | f.close 28 | 29 | File.open( 'morefriends.sav', 'w' ){ |friendsfile| 30 | Marshal.dump( ["sally", "agnes", "john" ], friendsfile ) 31 | } 32 | 33 | File.open( 'morefriends.sav' ){ |f| 34 | $arr= Marshal.load(f) 35 | } 36 | 37 | myfriends = Marshal.load(File.open( 'friends.sav' )) 38 | morefriends = Marshal.load(File.open( 'morefriends.sav' )) 39 | 40 | p( myfriends ) 41 | p( morefriends ) 42 | p( $arr ) 43 | 44 | 除了每次出现的 `YAML`(如 `YAML.dump` 和 `YAML.load`)都已被 Marshal 替换之外,这两个程序几乎完全相同。此外,Marshal 作为标准“内置”(built in)于 Ruby 中,因此你无需“引入”(require)任何额外的文件即可使用它。 45 | 46 | 但是,如果你查看生成的数据文件(例如 'friends.sav'),你会立即看到存在的重要差异。YAML 文件采用纯文本格式,而 Marshal 文件采用二进制格式。因此,虽然你可以阅读某些字符,例如字符串中的字符,但你不能简单地在文本编辑器中加载已保存的数据并对其进行修改。 47 | 48 | 与 YAML 一样,大多数数据结构都可以使用 Marshal 自动序列化,只需转储顶级(top-level)对象并在想要重建其下的所有对象时加载它。举个例子,看看我的小冒险游戏程序。在上一章中,我解释了如何通过转储和加载 Map 对象 `mymap`(参见 **gamesave_y.rb**)来保存和恢复包含了包含 Treasures 的 Rooms 的 Map 对象。使用 Marshal 替代 YAML 可以做同样的事情: 49 | 50 |
gamesave_m.rb
51 | 52 | File.open( 'game.sav', 'w' ){ |f| 53 | Marshal.dump( mymap, f ) 54 | } 55 | 56 | File.open( 'game.sav' ){ |f| 57 | mymap = Marshal.load(f) 58 | } 59 | 60 | 在一些特殊情况下,对象不能如此容易地被序列化。Ruby 的 Marshal 模块(**marshal.c**)中的代码记录了这些异常:如果要转储的对象包括绑定(bindings),例程(procedure)或方法(method)对象,IO 类的实例或单例对象(singleton objects),则会抛出 TypeError。稍后在考虑如何通过编排(marshaling)来保存单例(singletons)对象时,我会看一个与之相关的示例。 61 | 62 | ### 保存时忽略变量 63 | 64 | 与 YAML 序列化一样,可以限制使用 Marshal 进行序列化时要保存的变量。在 YAML 中,我们通过编写一个名为 `to_yaml_properties` 的方法来完成此目的。而在使用 Marshal 时,我们需要编写一个名为 `marshal_dump` 的方法。在这个方法的代码中,你应该创建一个包含要保存的实际变量名的数组(在 YAML 中,我们创建了一个包含变量名的字符串数组)。这是一个示例: 65 | 66 |
limit_m.rb
67 | 68 | def marshal_dump 69 | [@num, @arr] 70 | end 71 | 72 | 另一个不同之处在于,使用 YAML 我们只需加载数据即可重新创建对象。而使用 Marshal 时,我们需要添加一个名为 `marshal_load` 的特殊方法,任何已加载的数据都作为参数传递给该方法。当你调用 `Marshal.load` 时,它将被自动调用,它将以数组的形式传递加载的数据。可以从此数组中解析之前保存的对象。你还可以为任何在保存数据时被省略的变量(例如 `@str`)赋值: 73 | 74 | def marshal_load(data) 75 | @num = data[0] 76 | @arr = data[1] 77 | @str = "default string" 78 | end 79 | 80 | 这是一个完整的程序,它保存并恢复了变量 `@num` 和 `@arr` 但省略了 `@str`: 81 | 82 | class Mclass 83 | def initialize(aNum, aStr, anArray) 84 | @num = aNum 85 | @str = aStr 86 | @arr = anArray 87 | end 88 | 89 | def marshal_dump 90 | [@num, @arr] 91 | end 92 | 93 | def marshal_load(data) 94 | @num = data[0] 95 | @arr = data[1] 96 | @str = "default string" 97 | end 98 | end 99 | 100 | ob = Mclass.new( 100, "fred", [1,2,3] ) 101 | p( ob ) 102 | 103 | marshal_data = Marshal.dump( ob ) 104 | ob2 = Marshal.load( marshal_data ) 105 | p( ob2 ) 106 | 107 | 请注意,尽管序列化在内存中完成,但使用 Marshal 在磁盘上保存和加载对象时可以使用相同的技术。 108 | 109 | ### 保存单例对象 110 | 111 | 让我们看一下前面提到的问题的一个具体示例 - 即,无法使用编排(marshaling)来保存和加载单例对象(singleton)。在 **singleton_m.rb** 中,我创建了一个 Object 的实例 `ob`,然后以单例类的形式扩展它,附加了方法 `xxx`: 112 | 113 |
singleton_m.rb
114 | 115 | ob = Object.new 116 | 117 | class << ob 118 | def xxx( aStr ) 119 | @x = aStr 120 | end 121 | end 122 | 123 | 当我尝试使用 `Marshal.dump` 将此数据保存到磁盘时会抛出该问题。Ruby 显示一条错误消息,指出:“单例对象不能被转储(类型错误,TypeError)”。 124 | 125 | ### YAML 与单例对象 126 | 127 | 在思考我们如何处理这个问题之前,让我们先简单地看看 YAML 将如何应对这种情况。程序 **singleton_y.rb** 尝试使用 `YAML.dump` 保存上面显示的单例对象,并且与 `Marshal.dump` 不同,它成功了 - 嗯,可以说是的... 128 | 129 |
singleton_y.rb
130 | 131 | ob.xxx( "hello world" ) 132 | 133 | File.open( 'test.yml', 'w' ){ |f| 134 | YAML.dump( ob, f ) 135 | } 136 | 137 | ob.xxx( "new string" ) 138 | 139 | File.open( 'test.yml' ){ |f| 140 | ob = YAML.load(f) 141 | } 142 | 143 | 如果你看一下保存的 YAML 文件 **'test.yml'**,你会发现它定义了一个普通泛类型(vanilla)对象的实例,它附加了一个名为 `x` 的变量,它有一个字符串值 "hello world"。这一切都很好。除了通过加载保存的数据重建对象时,新的 `ob` 将是恰好包含一个额外的实例变量 `@x` 的 Object 的标准实例。然而,它不再是原来的单例对象,所以新的 `ob` 会无法访问该单例中定义的任何方法(此处为 `xxx` 方法)。因此,虽然 YAML 序列化更容易保存和加载在单例中创建的数据项,但在重新加载被保存的数据时,它不会自动重新创建单例本身。 144 | 145 | 现在让我们回到这个程序的 Marshal 版本。我需要做的第一件事是找到一种至少使它可以保存和加载数据项的方法。一旦我做完了,我将试着弄清楚如何在重新加载时重建单例对象。 146 | 147 | 为了保存特定的数据项,我可以定义 `marshal_dump` 和 `marshal_load` 方法,如前所述(参见 **limit_m.rb**)。这些通常应该在单例的派生类中定义 - 而不是单例本身。 148 | 149 | 这是因为,如已经说明的那样,当保存数据时,它将被存储为单例的派生类的表示。这意味着,虽然你确实可以将 `marshal_dump` 添加到从类 `X` 派生的单例中,但在重构对象时,你将加载泛型类型 `X` 的对象的数据,而不是特定单例实例的对象。 150 | 151 | 此代码创建类 `X` 的单例 `ob`,保存其数据,然后重新创建类 `X` 的通用对象: 152 | 153 |
singleton_m2.rb
154 | 155 | class X 156 | def marshal_dump 157 | [@x] 158 | end 159 | 160 | def marshal_load(data) 161 | @x = data[0] 162 | end 163 | end 164 | 165 | ob = X.new 166 | 167 | class << ob 168 | def xxx( aStr ) 169 | @x = aStr 170 | end 171 | end 172 | 173 | ob.xxx( "hello" ) 174 | 175 | File.open( 'test2.sav', 'w' ){ |f| 176 | Marshal.dump( ob, f ) 177 | } 178 | 179 | File.open( 'test2.sav' ){ |f| 180 | ob = Marshal.load(f) 181 | } 182 | 183 | 就其包含的数据而言,保存的对象和重新加载的对象是相同的。但是,重新加载的对象对单例类没有任何了解,并且单例类包含的方法 `xxx` 不构成重构对象的一部分。然后,以下将失败: 184 | 185 | ob.xxx( "this fails" ) 186 | 187 | 因此,该 Marshal 版本的代码等同于之前给出的 YAML 版本。它可以正确保存和恢复数据,但不会重建单例。 188 | 189 | 那么,如何从保存的数据中重建单例呢?毫无疑问,有许多聪明而巧妙的方式可以实现这一目标。但是,我会选择一种非常简单的方式: 190 | 191 |
singleton_m3.rb
192 | 193 | FILENAME = 'test2.sav' 194 | 195 | class X 196 | def marshal_dump 197 | [@x] 198 | end 199 | 200 | def marshal_load(data) 201 | @x = data[0] 202 | end 203 | end 204 | 205 | ob = X.new 206 | 207 | if File.exists?(FILENAME) then 208 | File.open(FILENAME){ |f| 209 | ob = Marshal.load(f) 210 | } 211 | else 212 | puts( "Saved data can't be found" ) 213 | end 214 | 215 | # singleton class 216 | class << ob 217 | def xxx=( aStr ) 218 | @x = aStr 219 | end 220 | 221 | def xxx 222 | return @x 223 | end 224 | end 225 | 226 | 此代码首先检查是否可以找到包含已保存数据的文件(此示例有意保持简单 - 在实际的应用程序中,你当然需要编写一些异常处理代码来处理可能读取无效数据的问题)。如果找到该文件,则将数据加载到通用 `X` 类型的对象中: 227 | 228 | ob = X.new 229 | 230 | if File.exists?(FILENAME) then 231 | File.open(FILENAME){ |f| 232 | ob = Marshal.load(f) 233 | } 234 | 235 | 只有在完成此操作后,此对象才会“转换”为单例对象。完成此操作后,代码可以在重构单例上使用单例方法 `xxx`。然后,我们可以将新数据保存回磁盘并在稍后重新加载并重新创建修改后的单例: 236 | 237 | if ob.xxx == "hello" then 238 | ob.xxx = "goodbye" 239 | else 240 | ob.xxx = "hello" 241 | end 242 | 243 | File.open( FILENAME, 'w' ){ |f| 244 | Marshal.dump( ob, f ) 245 | } 246 | 247 | 如果你希望在实际的应用程序中保存和加载单例,单独的“重建”代码自然可以给出自己的方法: 248 | 249 |
singleton_m4.rb
250 | 251 | def makeIntoSingleton( someOb ) 252 | class << someOb 253 | def xxx=( aStr ) 254 | @x = aStr 255 | end 256 | 257 | def xxx 258 | return @x 259 | end 260 | end 261 | return someOb 262 | end 263 | 264 | ## 深入探索 265 | 266 | ### Marshal 版本号 267 | 268 | Marshal 库(一个名为 **'marshal.c'** 的 C 语言文件)的嵌入式文档说明如下: 269 | 270 | > 编排(Marshaled)数据具有与对象信息一起存储的主要(major)和次要(minor)版本号。 在正常使用中,编排只能加载使用相同主版本号和相同或较低版本号编写的数据。 271 | 272 | 这显然提出了通过编排(marshaling)创建的数据文件格式可能与当前 Ruby 应用程序不兼容的潜在问题。另外地,Marshal 版本号不依赖于 Ruby 版本号,因此仅基于 Ruby 版本进行兼容性假设是不安全的。 273 | 274 | 这种不兼容的可能性意味着我们应该尝试在加载已保存数据之前检查其版本号。但是我们如何获得版本号呢?嵌入式文档再一次提供了线索。它指出: 275 | 276 | > 你可以通过读取编排(marshaled )数据的前两个字节来提取版本号。 277 | 278 | 它提供了这个示例: 279 | 280 | str = Marshal.dump("thing") 281 | RUBY_VERSION #=> "1.8.0" 282 | str[0] #=> 4 283 | str[1] #=> 8 284 | 285 | 好的,让我们在一段完整的代码中尝试这一点。开始... 286 | 287 |
version_m.rb
288 | 289 | x = Marshal.dump( "hello world" ) 290 | print( "Marshal version: #{x[0]}:#{x[1]}\n" ) 291 | 292 | 打印出: 293 | 294 | "Marshal version: 4:8" 295 | 296 | 当然,如果你使用的是不同版本的 Marshal 库,则显示的数字会有所不同。在上面的代码中,`x` 是一个字符串,它的前两个字节是主要和次要版本号。Marshal 库还声明了两个常量 `MAJOR_VERSION` 和 `MINOR_VERSION`,它们存储了当前正在使用的 `Marshal` 库的版本号。因此,乍一看,似乎很容易将保存数据的版本号与当前版本号进行比较。 297 | 298 | 只有一个问题:当你将数据保存到磁盘上的文件中时,`dump` 方法接受 的是IO 或 File 对象,它返回 IO(或 File)对象而不是字符串: 299 | 300 |
version_error.rb
301 | 302 | f = File.open( 'friends.sav', 'w' ) 303 | x = Marshal.dump( ["fred", "bert", "mary"], f ) 304 | f.close #=> x is now: # 305 | 306 | 如果你现在尝试获取 `x[0]` 和 `x[1]` 的值,你将收到错误消息。从文件加载数据不再具有意义: 307 | 308 | File.open( 'friends.sav' ){ |f| 309 | x = Marshal.load(f) 310 | } 311 | puts( x[0] ) 312 | puts( x[1] ) 313 | 314 | 这里的两个 `puts` 语句没有(如我希望)打印出编排(marshaled)数据的主要和次要版本号;事实上,它们打印出了名称,"fred" 和 "bert",即从数据文件 'friends.sav' 加载到数组 `x` 中的前两项。 315 | 316 | 那么我们如何才能从保存的数据中获取版本号?我必须承认,我被迫在 **marshal.c** 中的 C 代码中获取可能的方式(不是我最喜欢的活动!)并检查保存的文件中的十六进制数据以便弄清楚这一点。事实证明,正如文档所述,你可以通过读取编排(marshaled)数据的前两个字节来提取版本号。但是,你不适合这么做。你必须明确地读取这些数据 - 像这样: 317 | 318 | f = File.open('test2.sav') 319 | vMajor = f.getc() 320 | vMinor = f.getc() 321 | f.close 322 | 323 | 这里,`getc` 方法从输入流读取下一个 8 位字节。我的示例项目 **version_m2.rb** 给出了一种简单的方法,可以将保存数据的版本号与当前 Marshal 库的版本号进行比较,以确定在尝试重新加载数据之前数据格式是否可能兼容。 324 | 325 |
version_m2.rb
326 | 327 | if vMajor == Marshal::MAJOR_VERSION then 328 | puts( "Major version number is compatible" ) 329 | if vMinor == Marshal::MINOR_VERSION then 330 | puts( "Minor version number is compatible" ) 331 | elsif vMinor < Marshal::MINOR_VERSION then 332 | puts( "Minor version is lower - old file format" ) 333 | else 334 | puts( "Minor version is higher - newer file format" ) 335 | end 336 | else 337 | puts( "Major version number is incompatible" ) 338 | end -------------------------------------------------------------------------------- /markdown/2-introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | { 4 | "title": "序言", 5 | "ctime": "2016-11-03 20:30:00", 6 | "mtime": "2016-11-03 20:30:00" 7 | } 8 | 9 | --- 10 | 11 | # 序言 12 | 13 | *** 14 | 15 | ## Ruby 入门 16 | 17 | 正因为你现在在阅读一本关于 Ruby 的书,我认为你不需要我去利用 Ruby 语言的优点说服你接受它这个假设是完全成立的。所以,我将采取特别的方式开始,需要注意的是:许多人被 Ruby 吸引是因为其简单的语法和易用性。然而,他们错了。Ruby 的语法一眼看去很简单,但是当你对它了解得越来越多时你会意识到,相反地,它是非常复杂的。事实就是,对于不熟悉 Ruby 的程序员来说却有很多陷阱。 18 | 19 | 在这本书中,我的目标是引导你摆脱这些陷阱,带领你穿越 Ruby 的语法和类库的浪潮。在探索的过程中会经历平坦的高速路以及有些曲折颠簸的小路。在旅途结束时,你应该能够安全、有效的使用 Ruby 了,而不会被沿途的任何意外所绊倒。 20 | 21 | 本书中使用的 Ruby 版本主要集中于 1.8.x 版本。虽然 Ruby 的 1.9 版本可能已经发布了,但 1.8 版本的 Ruby 仍然远远没有被广泛的使用。Ruby 的 1.9 版本可能被视为 2.0 版本的铺垫。大多数人同意 Ruby 1.9 版本的语法接近 1.8 版本,但你应该注意其中的一些差异,完全兼容是不可能的。 22 | 23 | > 译注:目前(2016年11月)Ruby 的版本已经更新到 2.4(开发版),而稳定版为 2.3.x 。 24 | 25 | ### 如何阅读本书 26 | 27 | 这本书被分成了多个小部分。每一章都会介绍一个主题,而此又细分为多个子话题。并且,每个编程主题中都会伴随一个或多个彼此独立,直接就能运行的 Ruby 程序。 28 | 29 | 如果你想跟随一个结构良好的教程学习,那就按顺序阅读每个章节。如果你更喜欢学习到实用的方法,你也可以先运行程序,在你需要解释说明的时候再去参考文本。当然,如果你已经有了一些 Ruby 的实践经验,你可以按任意的顺序选择你觉得对你有用的章节进行阅读。在本书中没有大篇幅的相关联的应用程序代码或主题讨论,所以你不必担心由于没有按顺序阅读而导致你错过了一些重点。 30 | 31 | ### 深入探索 32 | 33 | 除了第一章,每一章都有一个部分叫做“深入探索”。在这部分我们将更深入地探索 Ruby 更具体的某些方面(包括一些我刚才提到的可能历经的曲折)。在大多数情况下,你可以直接跳过深入探索这部分,继续学习你今后必须要用到的知识。另一方面来说的话,在深入探索这一部分我们经常会接触到关于 Ruby 内部运作机制的知识,所以,如果你跳过这部分可能会错过一些非常有趣的东西。 34 | 35 | ### 文本格式约定 36 | 37 | 在这本书中,任何 Ruby 源代码都是像下面这样写的: 38 | 39 | def saysomething 40 | puts("Hello") 41 | end 42 | 43 | 当有一个示例程序伴随代码时,程序名将显示在页面右侧的一个框中,像这样: 44 | 45 |
helloname.rb
46 | 47 | 说明注释(通常是为在文本中提到的一些要点提供一些提示或给予一个更深入的解释)显示在这样一个方框中: 48 | 49 |
50 | 这是一个解释性说明。如果你想的话可以跳过它,不过你这么做的话,可能会错过一些有趣的东西。 51 |
52 | 53 | ### 什么是 Ruby ? 54 | 55 | Ruby 是一种跨平台解释型语言,它具有许多与其他“脚本”语言(如 Perl 和 Python )相同的特性。第一眼看上去它具有类似 Pascal 风格的英语语法。它是完全面向对象(object oriented)的,并且与著名的纯 OO 语言的鼻祖 Smalltalk 语言有很多共同点。据说,对 Ruby 语言的开发具有影响力的语言有:Perl、Smalltalk、Eiffel、Ada 和 Lisp。Ruby 语言是由 Yukihiro Matsumoto (通常称为 “Matz”,译注:日籍,中文译名为松本行弘)开发创造的,并于 1995 年首次发布。 56 | 57 | ### 什么是 Rails ? 58 | 59 | 目前,围绕 Ruby 最高的热度可以归功于一个 Web 开发框架,Rails —— 通常称为 “Ruby On Rails” 。Rails 是一个令人印象深刻的框架,但它并不代表 Ruby 的全部。事实上,你在没有首先精通 Ruby 的情况下直接去接触 Rails 开发框架,你可能会发现你最终创建的应用程序(applications),连你自己都不能理解(实际上,这在 Ruby On Rails 新手中太常见了)。学习 Ruby 是理解 Rails 的必要前提条件。 60 | 61 | ### 下载 Ruby 62 | 63 | 你可以在 http://www.ruby-lang.org 下载最新版本的 Ruby 。确保你下载了二进制文件(不仅仅是源代码)。在 PC 上,你可以使用 Ruby Installer for Windows 安装 Ruby :http://rubyinstaller.rubyforge.org/wiki/wiki.pl 。 64 | 65 | 或者,如果你使用的是 Ruby In Steel IDE,你可以使用网站的下载页面上提供的 Ruby In Steel “一体化安装程序”安装 Ruby,Rails,Ruby In Steel 和所有其他需要使用的工具:http://www.sapphiresteel.com/ 。 66 | 67 | ### 获取示例程序源码 68 | 69 | 本书每章中的所有程序都可以从 http://www.sapphiresteel.com/The-Book-Of-Ruby 下载 Zip 文件获取。当你解压缩这些程序包之后,你会发现它们是被按章节分到一组目录中的。使用 Ruby In Steel (由本书作者的公司开发的 Visual Studio IDE)的好处就是,你可以将在一个项目树分支中的每个章节的源程序作为一个 Visual Studio solutions 全部加载到 Ruby In Steel For Visual Studio 2008 中。如果你正在使用其它编辑器或者 IDE ,你需要一个一个的加载每个 Ruby 程序。使用 Ruby In Steel for Visual Studio 2005 的用户可以(通过 文件 新建/打开 菜单)导入或者转换该项目。 70 | 71 | 72 | ### 运行 Ruby 程序 73 | 74 | 经常在包含有你的 Ruby 程序的源目录中打开一个命令行窗口是非常有用的。假设 Ruby 解释器(interpreter)在你的系统中路径是正确的,你就可以通过(在命令行)输入 **ruby <filename>** 来运行你的程序: 75 | 76 | ruby 1helloworld.rb 77 | 78 | 如果你使用的是 Ruby In Steel ,你可以在交互式控制台通过按 CTRL+F5 运行你的程序,或者按 F5 调试程序。 79 | 80 | ### Ruby 的库文档 81 | 82 | 这本书涵盖了标准 Ruby 库(Standard Ruby Library)中的许多类和方法,但并不是全部。因此,在某些时候你需要参考 Ruby 所使用的所有类的文档。幸运的是,Ruby 类库包含有被提取编译为易于浏览的多种合适格式的嵌入式文档。例如,请参考被放在 Web 网站上的在线文档:http://www.ruby-doc.org/core/ 。 83 | 84 | 或者,你也可以按字母顺序浏览:http://www.ruby-doc.org/stdlib/ 。 85 | 86 | 上面这些页面都包含有如何下载离线文档的说明。还有一个页面,你可以从其中下载多种格式、版本和语言的文档:http://www.ruby-doc.org/downloads 。 87 | 88 | OK,这些序言已经足够了,让我们开始吧。是时候直接去阅读第一章了。 89 | 90 | *** 91 | 92 |

93 | 《The Book Of Ruby》由 SapphireSteel 软件赞助,并提供开发了 Ruby In Steel IDE for Visual Studio 。
94 | http://www.sapphiresteel.com 95 |

-------------------------------------------------------------------------------- /markdown/23-appendix.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | { 4 | "title": "附录", 5 | "ctime": "2019-01-11 01:38:00", 6 | "mtime": "2019-01-11 01:38:00" 7 | } 8 | 9 | --- 10 | 11 | # 附录 12 | 13 | *** 14 | 15 | ## 附录 A:使用 RDOC 记录 Ruby 16 | 17 | RDoc 是描述源代码文档格式的工具。RDoc 工具是 Ruby 的标准工具,可以处理 Ruby 代码文件和 C 代码 Ruby 类库,以便提取文档并对其进行格式化,以便它可以显示,例如在 Web 浏览器中。可以以源代码注释的形式显式添加 RDoc 文档。RDoc 工具还可以提取源代码本身的元素,以提供类,模块和方法的名称以及方法所需的任何参数的名称。 18 | 19 | 以 RDoc 处理器可访问的方式记录你自己的代码很容易。你可以在记录代码之前编写一组普通的单行注释(例如类或方法),也可以编写由 `=begin rdoc` 和 `=end` 分隔的嵌入式多行注释。请注意,`rdoc` 必须跟随 `=begin` 后,否则 RDoc 处理器将忽略注释块。 20 | 21 | 如果要生成文档,只需从命令提示符运行 RDoc 处理器即可。要为单个文件生成文档,请输入 `rdoc`,后跟文件名: 22 | 23 | rdoc rdoc1.rb 24 | 25 | 要为多个文件生成文档,请在 `rdoc` 命令后输入以空格分隔的文件名: 26 | 27 | rdoc rdoc1.rb rdoc2.rb rdoc3.rb 28 | 29 | Rdoc 工具将创建一个格式良好的 HTML 文件(*index.html*),顶部有三个窗格,底部有四个较大的窗格。三个顶部窗格显示文件,类和方法的名称,而底部窗格显示文档。 30 | 31 | HTML 包含超链接,以便你可以单击类和方法名称以导航到关联的文档。文档放在它自己的子目录 *\doc* 中,还有许多必需的 HTML 文件和一个样式表来应用格式。 32 | 33 | 你可以通过在单个单词或多个单词周围的标签周围放置格式字符来为 RDoc 注释添加额外的格式。使用 `*` 和 `*` 表示粗体,`_` 和 `_` 表示斜体,`+` 和 `+` 表示'打字机'字体。较长文本的等效标记为 `` 和 `` 表示粗体,`` 和 `` 表示斜体,`` 和 `` 表示打字机字体。 34 | 35 | 如果要从 RDoc 文档中排除注释或注释的一部分,可以将它放在 `#--` 和 `#++` 注释标记之间,如下所示: 36 | 37 | #-- 38 | # This comment won‟t appear 39 | # in the documentation 40 | #++ 41 | # But this one will 42 | 43 | 还有各种特殊说明,包含在冒号对之间。例如,如果要添加要在浏览器栏中显示的标题,请使用 `:title:`,像这样: 44 | 45 | #:title: My Fabulous RDoc Document 46 | 47 | RDoc 提供了更多选项,使你能够以各种方式格式化文档,并以其它格式输出来替代 HTML 格式。如果你真的想要掌握 RDoc,请务必阅读完整的文档: 48 | 49 | **http://rdoc.sourceforge.net/doc/index.html** 50 | 51 | ## 附录 B:为 Ruby On Rails 安装 MySQL 52 | 53 | 如果你正在使用 Rails,则需要安装数据库。虽然有很多可能的选择,但最广泛使用的是 MySQL。如果你之前从未使用过 MySQL,你可能会发现一些设置选项令人困惑。在这里,我将尝试引导你完成整个过程,以避免潜在的问题。 54 | 55 |
56 | 57 | 本指南基于 Windows 下的 MySQL 5.0 安装。在其它操作系统上安装其它版本时可能会有所不同。有关其它指南,请参阅 MySQL 站点。 58 |
59 | 60 | MySQL 主站点位于 **http://www.mysql.com/**,你可以从此处导航到当前版本的下载页面。 61 | 62 | ### 下载 MySQL 63 | 64 | 我假设你将使用 MySQL 的免费版本。可以从 **http://dev.mysql.com/downloads** 下载。在撰写本文时,当前版本是 MySQL 5 Community Server。当然,名称和版本号会随着时间的推移而变化。下载当前(即将发布的,alpha 或 beta)版本。选择为你的操作系统推荐的特定版本(例如,Win32 和 Win64 可能有不同的版本)。 65 | 66 | 你需要在此页面的某处向下滚动以找到适用于你的操作系统的安装程序。对于 Windows,你可以下载完整的 MySQL 包或较小的 Windows Essentials 包。完整的包包含数据库开发人员的额外工具,但这些不是简单的 Rails 开发所必需的。因此,对于大多数人来说,可以获得较小的 Windows Essentials 下载文件。 67 | 68 | 你应该单击此选项旁边的“选择镜像”(Pick A Mirror)链接。然后,你将看到一份问卷,如果你愿意,可以填写。如果你不希望这样做,只需向下滚动页面并选择一个区域下载站点。单击一个链接并保存文件,其名称类似(数字可能不同):**mysql-essential-5.0.41-win32.msi**,磁盘上任意合适的目录。 69 | 70 | ### 安装 MySQL 71 | 72 | 下载完成后,通过在下载对话框中选择*“打开”(Open)*或_“运行”(Run)_(如果仍然可见)或通过 Windows 资源管理器双击安装文件来运行程序。 73 | 74 |
75 | 76 | **注意:**在安装 MySQL 期间,可能会在屏幕上出现一些广告。单击按钮以浏览这些。某些安全警告还可能会提示你验证是否有意安装该软件。出现提示时,应单击必要的选项以继续安装。 77 |
78 | 79 | 现在将出现安装向导的第一页。 单击“下一步”(Next)按钮。如果你愿意将软件安装到 C:\Program Files\ 下的默认 MySQL 目录中,则可以选择“典型”(Typical)设置选项。但是,如果要安装到其它目录,请选择“自定义”(Custom)。然后单击下一步(Next)。单击“更改”(Change)以更改目录。 80 | 81 | 准备好继续后,单击*“下一步”(Next)*。 82 | 83 | 你将看到屏幕上显示“准备安装程序”。验证目标文件夹是否正确,然后单击*“安装”(Install)*按钮。 84 | 85 | 根据 MySQL 的版本,你现在可能会看到显示的一些营销信息,或者可能会提示你创建一个新的 MySQL 帐户,以便你收到更改和更新的消息。这些不是软件安装的重要部分,你可以单击*“下一步”(Next)*或_“跳过”(Skip)_按钮继续安装。 86 | 87 | 现在出现向导已完成对话框。 88 | 89 | 点击*“完成”(Finish)*按钮。 90 | 91 | ### 配置 MySQL 92 | 93 | 事实上,安装并没有结束。对于某些安装程序,会弹出一个新屏幕,欢迎你使用 MySQL 服务器实例配置向导。如果没有发生这种情况,你需要自己加载。在 Windows 上,单击“开始”菜单,然后在程序组中导航到 MySQL-> MySQL Server 5.0(或你使用的任何版本号),然后再运行 MySQL Server Instance Config Wizard。点击*下一步*。 94 | 95 | 假设这是你第一次在此计算机上安装 MySQL,你可以选择标准配置(如果你要从旧版本的 MySQL 升级,则需要选择详细配置 - 这超出了此简单设置指南的范围)。点击*下一步*。在下一个对话框中,保留选中的默认选项(即,*安装为 Windows 服务;服务名称 = 'MySQL' 并自动启动 MySQL 服务器*)。然后单击*下一步*。 96 | 97 | 在下一个屏幕中,选中“修改安全设置”(Modify Security Settings),然后在前两个文本字段中输入相同的密码(你选择的密码)。你将在以后需要此密码,因此请记住它或将其记录在安全的位置。如果你可能需要从另一台计算机访问 MySQL,可以选中“从远程计算机启用 root 访问权限”(Enable root access from remote machines)。然后单击*下一步*。 98 | 99 |
100 | 101 | **注意:**默认的 MySQL 用户名是 "root"。密码是你刚输入的密码。以后在创建 Rails 应用程序时,你将需要这两项信息。 102 |
103 | 104 | 下一个屏幕只显示有关即将执行的任务的一些信息。单击*“执行”(Execute)*按钮。 105 | 106 |
107 | 108 | 如果你以前安装或配置了 MySQL,则可能会看到一条错误消息,告诉你跳过安装。你可以单击*“重试”(Retry)*以查看是否可以绕过此问题。如果没有,请按 _Skip_ 键,然后重新启动 MySQL 配置过程,在出现提示时选择 Reconfigure Instance 和 Standard Instance。 109 |
110 | 111 | 安装完所有内容后,将出现此屏幕。单击*完成*。 112 | 113 | 就是这样! 114 | 115 | ## 附录 C:进一步阅读 116 | 117 | 以下是 Ruby 和 Rails 上一些最有用的书籍的简短列表... 118 | 119 | ### 书籍 120 | 121 | - **Beginning Ruby: From Novice To Professional** 122 | 123 | by Peter Cooper $39.99
124 | APress: http://www.apress.com
125 | ISBN: 1590597664
126 | 这本书编写得很好,布局合理,解释清楚,代码示例很有用。简而言之,如果你已经拥有一些编程经验并希望获得 Ruby 世界的可访问介绍,那么这本书就是你的最佳选择。 127 | 128 | - **Programming Ruby: The Pragmatic Programmer’s Guide** 129 | 130 | by Dave Thomas, with Chad Fowler and Andy Hunt $44.95
131 | ISBN: 0-9745140-5-5 (2 nd edition)
132 | ISBN: 9781934356081 (3 rd edition)
133 | Pragmatic: http://www.pragmaticprogrammer.com/titles/ruby/index.html 134 | 有关 Ruby 语言和库的大量(超过 860 页)指南,所谓的“镐书”(pickaxe book)通常被认为是必不可少的 Ruby 参考。但是,阅读起来并不是轻松,(在我看来)它不是学习 Ruby 最好的“第一本书”。尽管如此,你可能迟早会需要它。第二版涵盖Ruby 1.8;第 3 版涵盖了 Ruby 1.9。 135 | 136 | - **The Ruby Way** 137 | 138 | by Hal Fulton $39.99
139 | Addison-Wesley: http://www.awprofessional.com/ruby
140 | ISBN: 0-672-32884-4
141 | 在介绍部分,作者指出,由于相对缺乏“教程”材料,“你可能不会从这本书中学习 Ruby”。他把它描述为一种“反向引用”。不是按方法或类的名称查找,而是按功能或目的查找。我认为他大大低估了 The Ruby Way 的教程价值。然而,作者假定只你在编程方面相当熟练。 142 | 143 | - **Ruby On Rails Up and Running** 144 | 145 | by Bruce A. Tate & Curt Hibbs $29.99
146 | O’Reilly: www.oreilly.com
147 | ISBN: 0-596-10132-5
148 | 我更喜欢编程书籍,而不需要太多的华夫饼干。坦率地说,我没有耐心通过 1000 多页的书籍或按照逐步指南来构建应用程序。所以这书吸引了我。在七章中,它涵盖了有关 Rails 的所有重要内容 - 它的设计模式和类;它的脚本和应用程序生成工具;它的模型,视图,控制器和脚手架; 以及使用 Ajax 和单元测试的概述。 149 | 150 | - **Ruby For Rails** 151 | 152 | by David A. Black $44.95
153 | Manning : www.manning.com/black
154 | ISBN 1-932394-69-9
155 | 虽然本书主要关注 Rails 开发,但在每一步中都深入研究底层 Ruby 代码的内部工作方式。在十七章和不到 500 页的篇幅中,它将带你从第一次看从 Ruby 语言到创建 Rails 模型,控制器,视图,帮助和模板的细节。在此过程中,它解释了很多关于 Ruby 的内容,包括它的数组和散列,类,方法,块和迭代器。简而言之,如果你是 Ruby 新手,但想尽快加快 Rails 的学习速度,那么本书可能就是你所需要的。 156 | 157 | - **Agile Web Development With Rails (3 rd edition)** 158 | 159 | by Sam Ruby, Dave Thomas and David Heinemeier Hansson $43.95
160 | Pragmatic: http://pragprog.com/titles/rails3/agile-web-development-with-rails-third-edition
161 | ISBN: 9781934356166
162 | 这是关于 Rails “必备”的一本书。有几本 Ruby 编程书籍可能会争夺“必不可少”的主张,但我知道没有任何其它 Rails 书可以与 Agile Web Development 相媲美,因为它可以全面覆盖其主题。'Nuff 说:如果你认真对待 Ruby On Rails,那么买这本书吧!第 3 版涵盖了 Rails 2。 163 | 164 | ### E-Books 165 | 166 | - **Learn To Program** 167 | 168 | 第一版 Chris Pine 的书提供了对 Ruby 的简单介绍。
169 | http://pine.fm/LearnToProgram/ 170 | 171 | - **Programming Ruby: The Pragmatic Programmer’s Guide** 172 | 173 | 可能是你读过的最奇怪的编程书 - 连同会说话的狐狸!
174 | http://poignantguide.net/ruby/ 175 | 176 | - **The Little Book Of Ruby** 177 | 178 | 你正在读的这本书的配套书。
179 | http://www.sapphiresteel.com/The-Little-Book-Of-Ruby 180 | 181 | ## 附录 D:Web 站点 182 | 183 | 有无数的网站致力于 Ruby,Rails 和相关技术。以下是一些开始探索的内容... 184 | 185 | ### Ruby 和 Rails 信息 186 | 187 | - **Ruby 语言站点** 188 | 189 | http://www.ruby-lang.org 190 | 191 | - **Ruby 文档站点** 192 | 193 | http://www.ruby-doc.org/ 194 | 195 | - **Ruby 类库参考 (在线)** 196 | 197 | http://www.ruby-doc.org/core/ 198 | 199 | - **Ruby 类库参考(下载地址)** 200 | 201 | http://www.ruby-doc.org/downloads 202 | 203 | - **Ruby On Rails 站点** 204 | 205 | http://www.rubyonrails.org/ 206 | 207 | - **The Book Of Ruby 作者的博客...** 208 | 209 | http://www.sapphiresteel.com/-Blog- 210 | 211 | ## 附录 E:Ruby 和 Rails 的开发软件 212 | 213 | ### 集成开发环境 IDE/编辑器 214 | 215 | - **3rd Rail** 216 | 217 | http://www.codegear.com/products/3rdrail/
218 | Eclipse 的商业版 Rails-centric 集成开发环境。 219 | 220 | - **Aptana IDE/ RADRails** 221 | 222 | http://www.aptana.com/
223 | Eclipse 的免费版 Rails-centric 集成开发环境。 224 | 225 | - **Komodo** 226 | 227 | http://www.activestate.com/
228 | 多语言(Ruby,Python,PHP,Perl,Pcl),跨平台商业集成开发环境。免费版也可用。 229 | 230 | - **NetBeans** 231 | 232 | http://www.netbeans.org/products/ruby/
233 | NetBeans 的免费 Ruby 集成开发环境。 234 | 235 | - **Ruby In Steel** 236 | 237 | http://www.sapphiresteel.com/
238 | 适用于 Visual Studio 的商业 Ruby 和 Rails 集成开发环境。免费版也可用。 239 | 240 | - **TextMate** 241 | 242 | http://www.macromates.com/
243 | Mac OS X 的 Ruby 编辑器。 244 | 245 | ### Web 服务器 246 | 247 | 以下是一些与 Ruby On Rails 一起使用的流行 Web 服务器。 248 | 249 | - **WEBrick** 250 | 251 | http://www.webrick.org/ 252 | 253 | - **LightTPD** 254 | 255 | http://www.lighttpd.net/ 256 | 257 | - **Mongrel** 258 | 259 | http://mongrel.rubyforge.org/ 260 | 261 | - **Apache** 262 | 263 | http://www.apache.org/ 264 | 265 | ### 数据库 266 | 267 | - **MySQL** 268 | 269 | http://www.mysql.com/ 270 | 271 | - **SQLite** 272 | 273 | http://www.sqlite.org/ 274 | 275 | - **PostgreSQL** 276 | 277 | http://www.postgresql.org/ 278 | 279 | - **SQL Server Express** 280 | 281 | http://www.microsoft.com/sql/editions/express/ 282 | 283 | ## 附录 F:Ruby 实现 284 | 285 | 在撰写本文时,Ruby 1.8.x 和 1.9.1 的版本都可用,并且将在未来某个日期发布版本 2.0。目前 Ruby 1.8.6 可能是 Ruby 使用最广泛的版本,本书的大部分内容适用于 Ruby 1.8。实际上,尽管 Ruby 1.9 已经发布,但为了支持使用该版本开发的项目,Ruby 1.8.x 仍将是一个重要的 Ruby 平台,包括使用 Ruby On Rails 和其它框架构建的应用程序。其它 Ruby 解释器,编译器和虚拟机也可用或正在开发中。以下是一个简短的网站列表,它将提供有关 Ruby 实现的更多信息(和下载)... 286 | 287 | - **Ruby** 288 | 289 | “标准” Ruby 实现。
290 | http://www.ruby-lang.org/en/downloads/ 291 | 292 | - **JRuby** 293 | 294 | Ruby For Java。
295 | http://www.headius.com/ 296 | 297 | - **Iron Ruby** 298 | 299 | 微软正在开发的 "Ruby For .NET"。
300 | http://www.ironruby.net/ 301 | 302 | - **Rubinius** 303 | 304 | 用于 Ruby 的编译器/虚拟机(主要用 Ruby 编写)。
305 | http://rubini.us/ 306 | 307 | - **Maglev** 308 | 309 | 快速 Ruby 实现(开发中)。
310 | http://maglev.gemstone.com/ -------------------------------------------------------------------------------- /markdown/3-chapter1.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | { 4 | "title": "第一章", 5 | "ctime": "2016-11-04 12:30:00", 6 | "mtime": "2016-11-04 12:30:00" 7 | } 8 | 9 | --- 10 | 11 | # 第一章 12 | 13 | *** 14 | 15 | ## 字符串、数字、类和对象 16 | 17 | 关于 Ruby 语言,首先要知道的是它是易于使用的。为了证明这一点,让我们来看一看经典的 "Hello world" 程序代码: 18 | 19 |
1helloworld.rb
20 | 21 | puts 'hello world' 22 | 23 | 这就是完整的代码。用到了一个 `puts` 方法和一个 `'Hello world'` 字符串。没有文件头或者类定义,也不需要导入其他代码和 `main` 方法。这真的就是那么简单。(自己)加载这段代码,**1helloworld.rb**,试着运行一下。 24 | 25 | ### 获取并保存输入信息 26 | 27 | 首先将一个提示字符串输出后(这里是命令行窗口),显然下一步就是获取一个字符串。正如你可能猜到的,Ruby 为此提供的是 `gets` 方法。**2helloname.rb** 这段程序提示用户输入他们的名字,假设输入的是 'Fred',随后将显示一句问候语:"Hello Fred"。 28 | 29 |
2helloname.rb
30 | 31 | print( 'Enter your name: ' ) 32 | name = gets() 33 | puts( "Hello #{name}" ) 34 | 35 | 虽然说这仍然非常地简单,但有一些重要的细节需要说明。首先,注意我输出提示的时候使用的是 `print` 方法而不是 `puts` 方法。这是因为 `puts` 方法会在末尾自动添加一个换行符,但 `print` 方法则不会;而当前我希望光标和提示能在同一行显示。 36 | 37 | 在下一行,当用户按下 Enter 键时,我使用 `gets()` 方法读取用户的输入并以字符串类型保存。该字符串会被赋值给 `name` 变量(variable)。我没有预先声明该变量,也没有指定它的类型。在 Ruby 中,你可以根据需要去创建变量,并且 Ruby 会自动去推断该变量的类型。现在我将一个字符串赋值给了 `name`,因此 Ruby 推断 `name` 变量的类型一定是字符串(String)。 38 | 39 |
40 | 注意:Ruby 是大小写敏感的。一个名为 myvar 的变量和名为 myVar 的变量是不同的。一个和示例程序中 name 一样的变量,它的名字必须以小写字母开头(如果以大写字母开头,Ruby 会认为它是一个常量(constant),关于常量在后面的章节我会详细说明。) 41 |
42 | 43 | 顺便说一下,`gets()` 方法的括号是可选的,它与 `print` 和 `puts` 方法用来包围字符串的括号是一样的,如果你移除了括号,仍然会得到相同的结果。但是,括号可以帮助你解决某些语义冲突,并且在某些情况下,如果你省略它们,解释器将会发出警告。 44 | 45 | ### 字符串与内嵌表达式 46 | 47 | 在我们的示例代码中,最后一行是相当有趣的。 48 | 49 | puts( "Hello #{name}" ) 50 | 51 | 这里的 `name` 变量被嵌入到字符串(String)本身中。这是通过将变量放置于两个花括号中并在花括号前面加一个 # 字符实现,也就是 `#{}` 。这种嵌入式表达式仅限于使用双引号分隔的字符串中起作用。如果你尝试在单引号分隔的字符串中使用它,该变量将不会被执行(解释),恰恰显示的将会是字符串 **'Hello #{name}'**。 52 | 53 | 不仅仅只有变量可以嵌入到双引号分隔的字符串中。你也可以嵌入非打印(转义)字符,例如换行符 `\n` 和制表符 `\t` 。你甚至也可以嵌入程序代码和数学表达式。让我们假设你拥有一个方法 `showname` ,它的返回值为字符串 'Fred'。 54 | 55 | 下面这个字符串在执行过程中将会调用 `showname` 方法,因此,最终结果将会显示为 "Hello Fred": 56 | 57 | puts "Hello #{showname}" 58 | 59 | 看你是否能弄清楚下面这段程序将会显示什么结果: 60 | 61 |
3string_eval.rb
62 | 63 | puts("\n\t#{(1 + 2) * 3}\nGoodbye") 64 | 65 | 现在运行一下 **3string_eval.rb** 程序看看你对了吗。 66 | 67 | ### 数字 68 | 69 | 数字(Numbers)和字符串一样容易使用。例如,你想基于税率值和合计值来计算一些东西的销售价格或者总的合计值。为此,你需要将合计值乘以合适的税率并将结果加上合计值。假设合计值为 100 美元,税率为 17.5% ,这个 Ruby 程序会进行计算并显示结果: 70 | 71 |
4calctax.rb
72 | 73 | subtotal = 100.00 74 | taxrate = 0.175 75 | tax = subtotal * taxrate 76 | puts "Tax on $#{subtotal} is $#{tax}, so grand total is $#{subtotal+tax}" 77 | 78 | 显然,如果这个程序可以计算不同的合计值的话,相比于计算相同的合计值是更有用的。这是一个简单的可以提示用户输入合计值的计算程序: 79 | 80 | taxrate = 0.175 81 | print "Enter price (ex tax): " 82 | s = gets 83 | subtotal = s.to_f 84 | tax = subtotal * taxrate 85 | puts "Tax on $#{subtotal} is $#{tax}, so grand total is $#{subtotal+tax}" 86 | 87 | 这里的 `s.to_f` 是 String 类的一个方法,它会尝试将该字符串转换成一个浮点数。例如,字符串 "145.45" 将被转换成浮点数 145.45 。如果字符串不能被转换,将会返回 0.0 。所以,对于 **"Hello world".to_f** 将会返回 0.0 。 88 | 89 |
90 |
91 |
注释
92 |
本书附带的许多示例源代码都有会被 Ruby 解释器忽略的注释。注释可以放置于 # 字符之后,该字符之后的一行文本都将会被视为注释:
93 |
94 | 95 | # this is a comment 96 | 97 | puts( "hello" ) # this is also a comment 98 | 99 | 如果你想注释掉多行文本你可以在文本的首行添加 **=begin** 以及在末行添加 **=end**(**=begin** 与 **=end** 必须左对齐顶格写): 100 | 101 | =begin 102 | This is a 103 | multiline 104 | comment 105 | =end 106 |
107 | 108 | ### 测试条件语句:if ... then 109 | 110 | 上面的税率值计算代码的问题是允许负的合计值和税率,这种情况在政府看来可能是不利的。因此,我需要测试负数,如果出现负数将其置为 0 。这是我的新版代码: 111 | 112 |
5taxcalculator.rb
113 | 114 | taxrate = 0.175 115 | print "Enter price (ex tax): " 116 | s = gets 117 | subtotal = s.to_f 118 | 119 | if (subtotal < 0.0) then 120 | subtotal = 0.0 121 | end 122 | 123 | tax = subtotal * taxrate 124 | puts "Tax on $#{subtotal} is $#{tax}, so grand total is $#{subtotal+tax}" 125 | 126 | Ruby 中的 `if` 测试语句与其他编程语言中的 `if` 相似。注意,这里的括号也是可选的,`then` 也一样。但是,你如果在测试条件之后没有换行符的情况下继续写代码,那么 `then` 不能省略: 127 | 128 | if (subtotal < 0.0) then subtotal = 0.0 end 129 | 130 | 将所有代码写在同一行不会增加代码的清晰度,我会避免这么写。我长期习惯于 Pascal 书写风格所以导致我经常在 `if` 条件之后添加 `then`,然而这真的是不需要的,你可以将其看成我的一个癖好。`if` 代码块末尾的 `end` 关键字不是可选的,忘记添加它的话你的代码将不会运行。 131 | 132 | ### 局部变量与全局变量 133 | 134 | 在前面的示例中,我将值赋给了变量,例如 `subtotal`、`tax` 和 `taxrate` 。这些以小写字母开头的变量都是局部变量(Local variables),这意味着它们只存在于程序的特定部分。换句话说,它们被限制一个定义明确的作用域(scope)内。这是一个实例: 135 | 136 |
variables.rb
137 | 138 | localvar = "hello" 139 | $globalvar = "goodbye" 140 | 141 | def amethod 142 | localvar = 10 143 | puts(localvar) 144 | puts($globalvar) 145 | end 146 | 147 | def anotherMethod 148 | localvar = 500 149 | $globalvar = "bonjour" 150 | puts(localvar) 151 | puts($globalvar) 152 | end 153 | 154 | 这里有三个名为 `localvar` 的局部变量,一个在 main 作用域内被赋值为 "hello" ;其它的两个分别在独立的方法作用域内被赋值为整数(Integers):因为每一个局部变量都有不同的作用域,赋值并不影响在其它作用域中同名的局部变量。你可以通过调用方法来验证: 155 | 156 | amethod #=> localvar = 10 157 | anotherMethod #=> localvar = 500 158 | amethod #=> localvar = 10 159 | puts( localvar ) #=> localvar = "hello" 160 | 161 | 另一方面,一个以 **$** 字符开头的全局变量拥有全局作用域。当在一个方法中对一个全局变量进行赋值,同时也会影响程序中其它任意作用域中的同名全局变量: 162 | 163 | amethod #=> $globalvar = "goodbye" 164 | anotherMethod #=> $globalvar = "bonjour" 165 | amethod #=> $globalvar = "bonjour" 166 | puts($globalvar) #=> $globalvar = "bonjour" 167 | 168 | ### 类与对象 169 | 170 | 现在先跳过其余的 Ruby 语法,例如类型(type)、循环(loops)、模块(modules)等等(不要怕,我们会很快回过头来),让我们迅速去看看如何创建类(class)和对象(object)。 171 | 172 |
173 |
174 |
类、对象和方法
175 |
176 | 类是对象的大纲蓝图,它定义对象包含的数据以及行为方式。许多不同的对象可以从单一的类创建,所以你可能有一个 Cat 类(class),但是有三个 cat 对象(objects):tiddlescuddlesflossy。一个方法(method)就像一个定义在类中的函数或子例程。 177 |
178 |
179 |
180 | 181 | Ruby 是面向对象(object oriented)的似乎没什么特别可说的,现代所有的语言不是如此吗?好吧,说一点。大多数现代的“面向对象”语言(Java、C++、C#、Object Pascal 等等)或多或少都具有面向对象编程(OOP)的特性。另一方面,Ruby 是纯粹面向对象的。事实上,除非你使用过 Smalltalk 或 Eiffel (比 Ruby 更纯粹的面向对象的语言),否则 Ruby 就是你曾经使用过的语言中最面向对象的语言。从简单的数字和字符串到复杂的文件和模块,每一块数据都被视为一个对象。并且你用对象做的每一件事都是通过方法来完成,甚至“运算符”(operators)也是一个方法,例如加 + 和减 - 。看下面这个程序: 182 | 183 | x = 1 + 2 184 | 185 | 这里的 `+` 是 Fixnum (Integer) 对象 1 的一个方法,值 2 被传入该方法;结果 3 被返回并赋值给 x 对象。顺便地说一下,运算符 `=` 是“使用对象做任何事情都是通过方法来完成”这条规则的罕见例外。赋值运算符是一个内置的东西(这不是一个术语,我没有添加)并且它不是用来完成任何事情的一个方法。 186 | 187 | 现在让我们来看看如何创建我们自己的对象。和大多数其它 OOP(面向对象编程)的语言一样,一个 Ruby 对象由类来定义,这个类就像一个从中构建多个单个对象的蓝图。例如,这个类定一只狗: 188 | 189 | class Dog 190 | def set_name( aName ) 191 | @myname = aName 192 | end 193 | end 194 | 195 | 注意,类的定义以关键字 `class`(全部小写)和类名开始,并且类名必须以大写字母开头。这个类包含一个 `set_name` 方法,它需要传入一个参数 `aName`,方法体则是将 `aName` 赋值给一个 `@myname` 变量。 196 | 197 | ### 实例变量 198 | 199 | 以 `@` 符号开头的变量就是“实例变量”——这意味着它们属于单独的对象或者类的实例。实例变量不需要提前声明。我可以通过调用类的 `new` 方法来创建 Dog 类的实例(即 dog 对象)。在这里我创建两个两个 dog 对象(注意,虽然类名是以大写字母开头的,而实例对象名则是以小写字母开头的): 200 | 201 | mydog = Dog.new 202 | yourdog = Dog.new 203 | 204 | 目前,这两只狗还没有名字。所以,接下来我将要做的是调用 `set_name` 方法来给它们起个名字: 205 | 206 | mydog.set_name( 'Fido' ) 207 | yourdog.set_name( 'Bonzo' ) 208 | 209 | 现在每只狗都有了名字,但是我以后需要通过某些途径能获知它们的名字。我该怎么办?我不能在对象内部获取 `@name` 变量,因为每个对象的内部细节只能被它自己所知道。这是纯粹的面向对象的根本:每个对象内部的数据是私有的。每个对象都有其对应的被定义的输入(例如,`set_name` 方法)和输出接口。只有对象自身才能让它的内部状态变得混乱,外部世界是不能做到的。这被称为“数据隐藏”,并且它是“封装”(encapsulation)原理的一部分。 210 | 211 |
212 |
213 |
封装(Encapsulation)
214 |
215 | 在 Ruby 中,封装并不像最初它出现时的那么严格地被遵守,有一些不好的技巧可以让你使一个对象内部变得混乱。为了清楚起见(并确保你和我不会有恶梦),现在我们默默的了解下面这些语言的特性。 216 |
217 |
218 |
219 | 220 | 因为我们需要每一只狗都能知道它的名字,让我们给 Dog 类提供一个 `get_name` 方法: 221 | 222 | def get_name 223 | return @myname 224 | end 225 | 226 | 这里的 `return` 关键字是可选的。当它被省略时,Ruby 会返回最后一个表达式的值。 227 | 228 | 为了清楚起见(并为了避免发生意外的结果),我习惯于明确的返回我所期望的值。 229 | 230 | 最后,我们可以让狗拥有说话的能力。这是最终的类定义: 231 | 232 | class Dog 233 | def set_name( aName ) 234 | @myname = aName 235 | end 236 | 237 | def get_name 238 | return @myname 239 | end 240 | 241 | def talk 242 | return 'woof!' 243 | end 244 | end 245 | 246 | 现在,我们可以创建一个 dog 对象,给它命名、显示它的名字并且让它说话: 247 | 248 | mydog = Dog.new 249 | mydog.set_name( 'Fido' ) 250 | puts(mydog.get_name) 251 | puts(mydog.talk) 252 | 253 |
6dogs.rb
254 | 255 | 我已经在 **6dogs.rb** 这个文件中编写了这个代码的扩展版本。这个文件也包含了一个类似于 Dog 类的 Cat 类,除过 `talk` 方法不同,很自然的它的返回值是 miaow 而不是 woof 。 256 | 257 |
258 |

糟糕!这个程序似乎包含一个错误。

259 |

260 | 名为 someotherdog 的对象从未给它的 @name 变量赋值。幸运的是,在我们要显示这只狗的名字时 Ruby 并不会发生错误,而只会打印“nil”。我们将很快看到一个简单的方式来确保这样的错误不再发生... 261 |

262 |
263 | 264 | ### 消息、方法与多态 265 | 266 | 顺便的说一句,这是一个基于经典的 Smalltalk 示例程序的例子,说明了如何将相同的“消息”(例如 `talk`)发送给不同的对象(例如 cats 和 dogs),并且每个不同的对象会对相同的消息使用它们自己特有的方法(这里是 `talk` 方法)产生不同的响应。这种不同的类拥有相同的方法的能力有一个面向对象的名字“多态”——这个词可以不用记住。 267 | 268 | 当你运行一个程序,例如 **6dogs.rb** ,它的代码是顺序执行的。但是,直到类的实例(即对象)被后面的代码创建类的代码本身不会被执行。你会发现,我经常将类定义与程序运行时就会被执行的独立自由地代码混合着写。这可能不是你想写一个应用程序的主要方式,但这仅仅是尝试,而且它非常方便。 269 | 270 |
271 |

什么是自由独立的代码?

272 |

273 | 如果 Ruby 真的是一个面向对象的语言,你可能会因为我们可以写“自由浮动”的方法而感到奇怪。事实上被证明的是,当你运行一个程序时,Ruby 会创建一个 main 对象并且任何出现在其内部的代码不是自由浮动的,实际上是在 main 对象内部运行。你可以很容易的验证这一点,创建一个新的源文件,添加这些代码然后运行它来查看输出信息: 274 |

275 | 276 | puts self 277 | puts self.class 278 |
279 | 280 | 我的程序有一个明显的缺陷就是 Cat 和 Dog 类是高度重复的。也许更有意义的做法是,创建一个包含 `get_name` 和 `set_name` 方法的 Animal 类,并且它有两个仅仅包含特定行为——woofing 或 miaowing——的后代类 Cat 和 Dog。我们将在下一章中找到如何做到这一点。 281 | 282 | ### 构造方法——new 与 initialize 283 | 284 | 现在,来看看另一个用户自定义类的例子。加载 **7treasure.rb** ,这是制作了一个冒险游戏。它包含两个类 Thing(东西) 和 Treasure(宝藏),Thing 类与 Cat 和 Dog 类特别的相似,除了它不包含 woof 或者 miaow。 285 | 286 | Treasure 类没有 `get_name` 和 `set_name` 方法,相反地它包含一个名为 `initialize` 的方法,这个方法接受两个参数并将参数值分配给 `@name` 和 `@description` 变量: 287 | 288 |
7treasure.rb
289 | 290 | def initialize( aName, aDescription ) 291 | @name = aName 292 | @description = aDescription 293 | end 294 | 295 | 当一个类包含名为 `initialize` 的方法,它会在使用 `new` 方法创建对象时自动地被调用。使用 `initialize` 来设置一个对象的实例变量的值是不错的主意。 296 | 297 | 这相对于使用方法(例如 `set_name`)设置每个实例变量的值有两个明显的好处。首先,一个复杂的类可能包含许多实例变量,你可以通过一个 `initialize` 方法设置它们全部的值,而不是通过许多独立的“set”方法。其次,如果这些变量在对象创建时都被自动的初始化,你就不会以空的变量结束程序(例如在前面的程序中我们尝试显示 `someotherdog` 的名字时会返回 nil 值)。 298 | 299 | 最后,我创建了一个名为 `to_s` 的方法用来返回一个表示宝物对象的字符串。这个 `to_s` 方法名不是随意的,相同的方法名已被在 Ruby 标准对象库中使用。实际上,`to_s` 方法被定义在 Object 类中,该类是其它类的祖先。通过重新定义 `to_s` 方法,我添加了新的行为,这比默认的方法更适合于 Treasure 类。换句话说,我已经“覆盖”(overridden)了它的 `to_s` 方法。 300 | 301 | `new` 方法可以创建一个对象,所以它可以被认为是对象的“构造方法”。然而,你通常不应该实现你自己的 `new` 方法(这是可能的,但它通常不可取)。相反,当你想要执行任何“设置”操作(例如为对象的内部变量赋值)时,应在 `initialize` 方法中完成,Ruby 会在一个新对象创建后立即执行 `initialize` 方法。 302 | 303 |
304 |
305 |
垃圾回收(Garbage Collection,GC)
306 |
307 | 在许多语言中(例如 C++ 和 Delphi for Win32),销毁任何已经创建并且不再需要的对象是程序员的职责。换句话说,对象被赋予析构函数以及构造函数。在 Ruby 中,你不必做这些了,因为 Ruby 有一个内置的“垃圾回收器”,它会在你的程序不再引用对象时销毁它并回收内存。 308 |
309 |
310 |
311 | 312 | ### 查看对象 313 | 314 | 顺便提一下,Treasure 对象 **t1** 内部使用了 `inspect` 方法: 315 | 316 | t1.inspect 317 | 318 | `inspect` 是为所有的 Ruby 对象定义的,它将返回一个包含人类可读的表示该对象的字符串。在本例中,它显示这样一些信息: 319 | 320 | # 321 | 322 | 它是以类名 Treasure 开始,后面跟随一个数字,这是 Ruby 内部用来识别特定对象的的识别码;随后就是对象的名字和变量的值。 323 | 324 | Ruby 还提供了p方法作为打印和查看对象细节的快捷语法,像这样: 325 | 326 |
p.rb
327 | 328 | p(anobject) 329 | 330 | 来看看如何用 `to_s` 方法将各种对象以及测试将一个 Treasure 对象在没有重写 `to_s` 方法的情况下转换成字符串,场试运行 **8to_s.rb** 程序: 331 | 332 |
8to_s.rb
333 | 334 | puts(Class.to_s) #=> Class 335 | puts(Object.to_s) #=> Object 336 | puts(String.to_s) #=> String 337 | puts(100.to_s) #=> 100 338 | puts(Treasure.to_s) #=> Treasure 339 | 340 | 正如你将看到的,当 `to_s` 方法被调用时,类(如 Class,、Object、String 以及 Treasure )只是简单的返回它们的名字;而一个对象,例如 Treasure 对象 **t** ,返回与 `inspect` 方法返回值一样的对象标识符: 341 | 342 | t = Treasure.new( "Sword", "A lovely Elvish weapon" ) 343 | puts(t.to_s) 344 | #=> # 345 | puts(t.inspect) 346 | #=> # 347 | 348 | 虽然 **7treasure.rb** 程序可能作为一个游戏的基础代码包含了不同类型的对象,其代码仍然是重复的。毕竟,为什么 Thing 类有一个 name 而 Treasure 也要包含一个 name 呢?如果把 Treasure 视为一类东西可能是更有意义的。在一个完整的游戏中,其它对象,例如 Rooms(房间) 和 Weapons(武器)可能也是其它的一类东西。是时候应该在一个合适的类层次上开始工作了,这就是我们下一章将要讲到的... -------------------------------------------------------------------------------- /markdown/5-chapter3.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | { 4 | "title": "第三章", 5 | "ctime": "2016-11-11 22:34:00", 6 | "mtime": "2016-11-11 22:34:00" 7 | } 8 | 9 | --- 10 | 11 | # 第三章 12 | 13 | *** 14 | 15 | ## 字符串和范围 16 | 17 | 到目前为止,我已经在我的程序中使用了很多字符串(String)。事实上,在本书中的第一个程序中就有字符串。这里再给出来: 18 | 19 | puts 'hello world' 20 | 21 | 在第一个程序中字符串使用单引号作为分隔符,我在第二个程序中则使用了双引号来替换: 22 | 23 | print('Enter your name: ') 24 | name = gets() 25 | puts("Hello #{name}") 26 | 27 |
1strings.rb
28 | 29 | 双引号字符串要比单引号字符串做更多的工作。尤其地,即便字符串是代码它也有能力去执行。要执行代码的话,请使用 `#` 字符与大括号将其包含进去。 30 | 31 | 在上面的示例中,`#{name}` 在双引号字符串中指示 Ruby 获取 `name` 变量的值并将其插入到字符串中。所以,如果 `name` 等于 "Fred",将会显示 "Hello Fred"。**1strings.rb** 示例程序提供了双引号字符串中嵌入式表达式的更多示例。 32 | 33 | 双引号字符串不仅能够执行获取属性或者变量的值,例如 `ob.name`,还有诸如 `2*3` 的表达式,方法调用 `ob.ten`(**ten** 是一个方法名)以及转义字符换行符 "\n" 和制表符 "\t" 都能被识别执行。 34 | 35 | 单引号字符串则不会执行这些。并且,单引号字符串可以使用反斜杠表示下一个字符仅仅表示其字面意思。当一个单引号字符串中包含单引号时,这是非常有用的: 36 | 37 | 'It\'s my party' 38 | 39 | 假设名为 `ten` 的方法返回值为10,你可以写出下面的代码: 40 | 41 | puts("Here's a tab\t a new line\n a calculation #{2*3} and a method-call #{ob.ten}") 42 | 43 | 由于这是双引号字符串,因此将执行嵌入式表达式,并显示以下内容: 44 | 45 | Here's a tab a new line 46 | a calculation 6 and a method-call 10 47 | 48 | 接下来,让我们看当使用单引号时会发生什么: 49 | 50 | puts('Here\'s a tab\t a new line\n a calculation #{2*3} and a method-call #{ob.ten}') 51 | 52 | 这一次,嵌入式表达式将不会被执行,所以将显示: 53 | 54 | Here's a tab\t a new line\n a calculation #{2*3} and a method-call #{ob.ten} 55 | 56 | ### 用户自定义字符串分隔符 57 | 58 | 如果由于某些原因,使用单双引号不方便,例如你的字符串包含很多的引号,而你不想总是使用反斜杠去转义,那么你也可以通过其它方式去分割字符串。 59 | 60 |
2strings.rb
61 | 62 | 双引号的标准替代分隔符是 **%Q** 和 **/** 或者 **%/** 和 **/** ,然而单引号则为 **%q** 和 **/** 。因此: 63 | 64 | %Q/This is the same as a double-quoted string./ 65 | %/This is also the same as a double-quoted string./ 66 | %q/And this is the same as a single-quoted string/ 67 | 68 | 你甚至可以定义自己的字符串分隔符。它们必须是非字母数字字符,可以包含非打印字符,比如换行符和通常在 Ruby 中有特殊含义的字符,例如 `#`。你选择的字符应该放在 **%Q** 或 **%q** 之后,并且应该确保终止字符串的是同样的字符。如果你使用的分隔符是一个开括号,相应的在字符串结尾处应该使用闭括号,像这样: 69 | 70 | %Q[This is a string] 71 | 72 |
3strings.rb
73 | 74 | 你可以在示例程序 **3strings.rb** 中发现许多种字符串分隔符。不用说,有时候使用一些深奥的字符(比如换行符和星号)分割字符串很有用,但在许多情况下这些方式的缺点可能会掩盖掉其优点。 75 | 76 | ### 反引号 77 | 78 | 一个其它类型的字符串值得特别提及:一个由反引号括起来的字符串——也就是在键盘左上角的向内指向的引号字符 `。 79 | 80 | Ruby 认为任何由反引号括起来的都是一个可以使用 `print` 或 `puts` 方法传递给操作系统执行的命令。到目前为止,你可能已经猜到 Ruby 提供了不仅仅一种方式去实现这些。事实证明,`%x/some command/` 与 `somecommand` 具有相同的效果,当然 `%x{some command}` 也是如此。例如,在 Windows 操作系统上,如下所示的三行代码都是将命令 **dir** 传递给操作系统执行,显示目录列表: 81 | 82 |
4backquotes.rb
83 | 84 | puts(`dir`) 85 | puts(%x/dir/) 86 | puts(%x{dir}) 87 | 88 | 你也可以在双引号字符串中嵌入命令,如下所示: 89 | 90 | print( "Goodbye #{%x{calc}}" ) 91 | 92 | 如果你这么做,要小心的是,命令首先会被执行。你的 Ruby 程序会进行等待,直到开始的进程终止。在这种情况下,计算器将先弹出来。你可以做一些计算,只有当你关闭计算器的时候,字符串 “Goodbye” 才会显示。 93 | 94 | ### 字符串处理 95 | 96 | 在结束字符串这个话题之前,我们将快速查看一看字符串的处理操作。 97 | 98 | #### 连接 99 | 100 |
string_concat.rb
101 | 102 | 你可以使用 `<<` 或 `+`,或者说在中间放置一个空格来连接字符串。这里有三个字符串连接示例,在每一种情况下 `s` 都被赋值为字符串 “Hello World” : 103 | 104 | s = "Hello " << "world" 105 | s = "Hello " + "world" 106 | s = "Hello " "world" 107 | 108 | 但是请注意,当你使用 `<<` 方法时,你可以直接附加一个整数(0到255),而不必先将它转换为字符串;当使用 `+` 或者空格时,整数必须使用 `to_s` 方法先转换为字符串。 109 | 110 |
111 |

关于逗号

112 |

113 | 你有时可能会看到 Ruby 代码使用逗号分割来分割字符串或者其它类型数据。在某些情况下,这些逗号似乎有连接字符串的效果。例如,下面的代码一眼看上去似乎是创建和显示了一个由三个子字符串加整数组成的字符串: 114 |

115 | 116 | s4 = "This ", "is", " not a string!", 10 117 | print("print (s4):", s4, "\n") 118 | 119 |

120 | 事实上,用逗号分割的列表是创建了一个数组——一个基于字符串的有序列表。string_concat.rb 程序包含的示例证明了这一点。 121 |

122 |

123 | 请注意,当你将一个数组传递给一个方法比如puts,数组中的每个元素将被单独处理。你可以像下面一样将x传递给puts: 124 |

125 | 126 | puts(x) 127 | 128 |

在这种情况下,输出将会是:

129 | 130 | This 131 | is 132 | not a string! 133 | 10 134 | 135 |

我们将在下一章中更深入的研究数组。

136 |
137 | 138 | #### 字符串赋值 139 | 140 | Ruby 的 String 类提供了许多有用的字符串处理方法。大多数的方法将会创建一个新的字符串对象。例如,在下面的代码中,第二行左侧的 `s` 与右侧的 `s` 不是同一个对象: 141 | 142 | s = "hello world" 143 | s = s + "!" 144 | 145 |
string_assign.rb
146 | 147 | 一些字符串方法实际上是在不创建新的字符串对象的情况下改变其本身。这些方法通常以感叹号结尾(例如,`capitalize!`)。 148 | 149 | 如果有疑问,可以使用 `object_id` 方法来检查对象。我已经在 **string_assign.rb** 程序中提供了几个不需要创建新字符串对象的操作示例。运行它们并且在字符串操作执行完之后检查 `s` 的 `object_id`。 150 | 151 | #### 字符串索引 152 | 153 | 你可以将字符串视为一个字符数组,并使用方括号以及索引查找在该数组中特定索引处的字符。Ruby 中的字符串和数组索引是从 0 开始的。所以,要将字符串 `s` “Hello world” 中的 “e” 替换为 “a”,你应该在索引 1 处赋值一个新的字符: 154 | 155 | s[1] = 'a' 156 | 157 | 但是,如果你要在一个字符串中使用索引查找特定位置的字符,Ruby 不会返回这个字符本身,返回的是该字符的 ASCII 值: 158 | 159 | s = "Hello world" 160 | puts(s[1]) # prints out 101 – the ASCII value of 'e' 161 | 162 | 为了获得实际的字符,可以这么做: 163 | 164 | s = "Hello world" 165 | puts(s[1,1]) # prints out 'e' 166 | 167 | 这将告诉 Ruby 将字符串中索引 1 处的字符返回。如果你想要返回索引 1 处开始的3个字符,你可以输入: 168 | 169 | puts(s[1,3]) # prints "ell" 170 | 171 | 这告诉 Ruby 返回从索引 1 处开始接下来的3个字符。或者,你也可以使用双点符号 “范围(range)”来表示: 172 | 173 | puts(s[1..3]) # also prints "ell" 174 | 175 |
176 | 关于范围(Ranges),你可以参阅本章末尾的深入探索部分。 177 |
178 | 179 | 字符串还可以使用负值索引,在这种情况下,-1 代表最后一个字符的位置,并且可以再次指定要返回的字符数: 180 | 181 | puts(s[-1,1]) # prints 'd' 182 | puts(s[-5,1]) # prints 'w' 183 | puts(s[-5,5]) # prints "world" 184 | 185 |
string_index.rb
186 | 187 | 使用减号指定范围时,起始和终止索引必须均用负值: 188 | 189 | puts(s[-5..5]) # this prints an empty string! 190 | puts(s[-5..-1]) # prints "world" 191 | 192 |
string_methods.rb
193 | 194 | 最后,你可能需要尝试一些可用于操作字符串的标准方法。这些方法包括改变字符串大小写,反转字符串,插入子字符串,删除重复字符等等。我在 **string_methods.rb** 提供了几个示例。 195 | 196 | #### 删除换行符 chop 与 chomp 197 | 198 | 一些方便的字符串处理方法值得提及一下。`chop` 与 `chomp` 方法可用于从字符串的末尾删除字符。`chop` 方法会返回一个字符串,这个字符串是删除最后一个字符得到的,但如果在字符串末尾处发现了回车符和换行符 “\r\n” 则删除一个回车符或换行符之后返回字符串。`chomp` 方法返回一个字符串,这个字符串是删除了末尾一个回车符或换行符得到的。 199 | 200 | 这些方法在你需要删除用户输入或者文件输入的换行符时是非常有用的。例如,当你使用 `gets` 方法读取一行文本时,它将返回该文本以及在末尾的“记录分隔符”,默认地通常是换行符。 201 | 202 |
203 |

记录分隔符- $/

204 |

205 | Ruby 预定义了一个变量 $/ 来作为“记录分隔符”。这个变量被用于一些方法中,例如 getschomp 中。gets 方法读入一个字符串时将会包含一个记录分隔符。chomp 方法将会返回一个字符串,该字符串末尾存在记录分隔符时删除后返回,不存在时直接返回未修改的原始字符串。如果你愿意,可以重新定义记录分隔符,如下所示: 206 |

207 | 208 | $/="*" # the "*" character is now the record separator 209 | 210 | 当你重新定义了记录分隔符之后,新的字符或字符串现在将会在一些方法中被使用,例如 `gets` 和 `chomp` 方法。例如: 211 | 212 | $/= "world" 213 | s = gets() # user enters "Had we but world enough and time…" 214 | puts(s) # displays "Had we but world" 215 | 216 |
217 | 218 | 你可以使用 `chop` 和 `chomp` 方法来删除换行符。在大多数情况下,`chomp` 方法是最好的,因为它不会删除最后的字符,除非末尾是记录分隔符,而 `chop` 方法无论最后一个字符是什么都会删除。这里是一些示例: 219 | 220 |
chop_chomp.rb
221 | 222 | # Note: s1 includes a carriage return and linefeed 223 | s1 = "Hello world 224 | " 225 | s2 = "Hello world" 226 | s1.chop # returns "Hello world" 227 | s1.chomp # returns "Hello world" 228 | s2.chop # returns "Hello worl" – note the missing "d"! 229 | s2.chomp # returns "Hello world" 230 | 231 | `chomp` 方法允许你指定要用做分隔符的字符或字符串: 232 | 233 | s2.chomp("rld") # returns "Hello wo" 234 | 235 | #### 格式化字符串 236 | 237 | Ruby 提供了 `v` 方法来打印包含以百分号 **%** 开头的说明符的“格式化字符串”。格式化字符串之后可以是一个或多个用逗号分隔的数据项;数据项的列表应与格式说明符的类型相匹配。实际数据项替换字符串中匹配的说明符,并相应地进行格式化。这是一些常见的格式说明符: 238 | 239 | %d – decimal number 240 | %f – floating point number 241 | %o – octal number 242 | %p – inspect object 243 | %s – string 244 | %x – hexadecimal number 245 | 246 | 你可以通过在浮点数说明符 **%f** 前面放置一个点号来控制浮点精度。例如,这将显示两位浮点值: 247 | 248 | printf("%0.02f", 10.12945) # displays 10.13 249 | 250 | ## 深入探索 251 | 252 | ### 范围 253 | 254 | 在 Ruby 中,Range 是一个表示指定的起始和终止值之间一组数的类。通常,使用整数定义范围,但也可以使用其它有序值(比如浮点数或字符)来定义范围。值可以是负的,尽管如此,你应该注意起始值应该始终小于终止值。这有一些示例: 255 | 256 |
ranges.rb
257 | 258 | a = (1..10) 259 | b = (-10..-1) 260 | c = (-10..10) 261 | d = ('a'..'z') 262 | 263 | 你也可以使用三个点而不是两个点指定范围:这将创建一个不包含终止值范围: 264 | 265 | d = ('a'..'z') # this two-dot range = 'a'..'z' 266 | e = ('a'...'z') # this three-dot range = 'a'..'y' 267 | 268 | 你可以使用 `to_a` 方法创建一个由范围定义的数的数组: 269 | 270 | (1..10).to_a 271 | 272 | 请注意,由于两个浮点数之间的数不是有限的,所以 `to_a` 方法并没有为浮点数所定义。 273 | 274 |
str_range.rb
275 | 276 | 你甚至可以创建字符串范围,你需要非常的小心,这样做的话最终可能会超出你的预想。例如,看看你是否知道这个范围指定的值: 277 | 278 | str_range = ('abc'..'def') 279 | 280 | 一眼看去,从"abc"到"def"这个范围的值可能不多。实际上,这个范围之间的值不少于 2110 个!它们具体如下:"abc"、"abd"、"abe"等等,直到"a"结束;然后再从"b"开始,"baa"、"bab"、"bac"等等。准确的说,这种范围是不常用的,最好要非常小心或者直接不使用。 281 | 282 | ### 范围迭代器 283 | 284 | 你可以使用范围从起始值迭代到终止值。例如,以下是将数字从1到10打印出来的方法: 285 | 286 |
for_to.rb
287 | 288 | for i in (1..10) do 289 | puts( i ) 290 | end 291 | 292 | ### HereDocs 293 | 294 | 虽然你可以在单双引号之间写跨行的长字符串,但许多 Ruby 程序员更喜欢使用名为"heredoc"这种替代类型的字符串。hererdoc 是一个以特定结束标记开始的文本块,这可以是你自己选择的结束标记。这里,我选择了 EODOC 作为结束标记: 295 | 296 |
heredoc.rb
297 | 298 | hdoc1 = <literals.rb 350 | 351 | p %q/dog cat #{1+2}/ #=> "dog cat \#{1+2}" 352 | p %Q/dog cat #{1+2}/ #=> "dog cat 3" 353 | p %/dog cat #{1+2}/ #=> "dog cat 3" 354 | p %w/dog cat #{1+2}/ #=> ["dog", "cat", "\#{1+2}"] 355 | p %W/dog cat #{1+2}/ #=> ["dog", "cat", "3"] 356 | p %r|^[a-z]*$| #=> /^[a-z]*$/ 357 | p %s/dog/ #=> :dog 358 | p %x/vol/ #=> " Volume in drive C is OS [etc...]" -------------------------------------------------------------------------------- /markdown/7-chapter5.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | { 4 | "title": "第五章", 5 | "ctime": "2016-11-17 23:13:00", 6 | "mtime": "2016-11-17 23:13:00" 7 | } 8 | 9 | --- 10 | 11 | # 第五章 12 | 13 | *** 14 | 15 | ## 循环(Loop)和迭代器(Iterator) 16 | 17 | 大部分程序都是与重复行为相关的。也许你希望你的程序发出十次嘟嘟(beep)声,读取一个长文件的更多行直到用户按下某个键,显示一个警告信息。Ruby 提供了许多执行此类重复行为的方式。 18 | 19 | ### for 循环 20 | 21 | 在许多编程语言中,当你想将一些代码运行一定的次数时你可以把它放在 `for` 循环中。在大多数语言中,为 `for` 循环可以提供一个初始化的变量作为起始值,该起始值在每个循环中递增 1,循环到直到它满足某个特定的结束值。当满足结束值时,`for` 循环停止运行。这是用 Pascal 写的常规类型的 `for` 循环: 22 | 23 | (* This is Pascal code, not Ruby! *) 24 | for i := 1 to 3 do 25 | writeln( i ); 26 | 27 |
for_loop.rb
28 | 29 | 您可能还记得上一章中 Ruby 的 `for` 循环根本不是这样的!我们会给 `for` 循环一个元素列表,而不是一个起始值和结束值,然后逐个迭代它们,将每个元素值依次分配给一个循环变量,直到它到达列表的末尾。 30 | 31 | 例如,这是一个 `for` 循环,它迭代数组中的项目,并依次显示: 32 | 33 | # This is Ruby code… 34 | for i in [1,2,3] do 35 | puts( i ) 36 | end 37 | 38 | `for `循环更像是其它编程语言中提供的 'for each' 迭代器。循环迭代的项目不必是整数(integers)。这也可以... 39 | 40 | for s in ['one','two','three'] do 41 | puts( s ) 42 | end 43 | 44 | Ruby 的作者描述 `for` 循环是集合类型,例如 Arrays、Sets、Hashes 和 Strings(字符串实际上是一个字符集合)中实现的 `each` 方法的语法糖(syntax sugar)。为了便于比较,这是上面的 `for` 循环使用 `each` 方法进行重写的: 45 | 46 |
each_loop.rb
47 | 48 | [1,2,3].each do |i| 49 | puts( i ) 50 | end 51 | 52 | 正如你所看到的,并没有太大的区别。要将 `for `循环转换为 `each` 迭代器,所要做的就是删除 `for` 和 `in`,并将 `.each` 附加到数组中。然后把迭代变量 `i` 放在 `do` 后的两条竖线中。比较一下其它示例,看看 `for` 循环和 `each` 迭代器的相似程度: 53 | 54 |
for_each.rb
55 | 56 | # --- Example 1 --- 57 | # i) for 58 | for s in ['one','two','three'] do 59 | puts( s ) 60 | end 61 | 62 | # ii) each 63 | ['one','two','three'].each do |s| 64 | puts( s ) 65 | end 66 | 67 | # --- Example 2 --- 68 | # i) for 69 | for x in [1, "two", [3,4,5] ] do puts( x ) end 70 | 71 | # ii) each 72 | [1, "two", [3,4,5] ].each do |x| puts( x ) end 73 | 74 | 顺便提一下,请注意,`do` 关键字在跨越多行的 `for` 循环中是可选的,但是当它写在一行上时是必须的: 75 | 76 | # Here the "do" keyword can be omitted 77 | for s in ['one','two','three'] 78 | puts( s ) 79 | end 80 | 81 | # But here it is required 82 | for s in ['one','two','three'] do puts( s ) end 83 | 84 |
for_to.rb
85 | 86 |
87 |

如何编写一个“普通(normal)”的 for 循环...

88 | 89 | 如果你习惯了常规类型的 `for` 循环,你可以随时通过使用 Ruby 中的 `for` 循环迭代范围中的值来实现。例如,这显示了如何使用一个 `for` 循环变量从 1 到 10 计数,并在每次循环过程中显示其值: 90 | 91 | for i in (1..10) do 92 | puts( i ) 93 | end 94 |
95 | 96 |
for_each2.rb
97 | 98 | 此示例显示了 `for` 和 `each` 两者如何用于迭代范围内的值: 99 | 100 | # for 101 | for s in 1..3 102 | puts( s ) 103 | end 104 | 105 | # each 106 | (1..3).each do |s| 107 | puts(s) 108 | end 109 | 110 | 顺便提一下,要注意在使用 `each` 方法时,范围(range)表达式,例如 `1..3` 必须使用圆括号包围,否则 Ruby 会假设你试图使用整数(一个 Fixnum)作为 `each` 方法的最终值,而不是整个表达式(范围,Range)。range 用在 `for` 循环中时圆括号是可选的。 111 | 112 | ### 多迭代参数 113 | 114 |
multi_array.rb
115 | 116 | 你可能还记得在上一章中我们使用了一个带有多个循环变量的 `for` 循环。我们这样做是为了迭代一个多维数组。在每次进入 `for` 循环中,一个变量将被赋值为外层数组中的一行数据(子数组): 117 | 118 | # Here multiarr is an array containing two "rows" 119 | # (sub-arrays) at index 0 and 1 120 | multiarr = [ 121 | ['one','two','three','four'], 122 | [1,2,3,4] 123 | ] 124 | 125 | # This for loop runs twice (once for each "row" of multiarr) 126 | for (a,b,c,d) in multiarr 127 | print("a=#{a}, b=#{b}, c=#{c}, d=#{d}\n" ) 128 | end 129 | 130 | 上面的循环将会打印出: 131 | 132 | a=one, b=two, c=three, d=four 133 | a=1, b=2, c=3, d=4 134 | 135 | 我们可以使用 `each` 方法来迭代四个元素的数组,将四个“块参数” `a`,`b`,`c`,`d` 传入 `do` 和 `end` 限定的块中: 136 | 137 | multiarr.each do |a,b,c,d| 138 | print("a=#{a}, b=#{b}, c=#{c}, d=#{d}\n" ) 139 | end 140 | 141 |
142 |

块参数(Block Parameters)

143 | 144 | 在 Ruby 中,迭代器的主体称为“块”(block),在块顶部的两个竖直线中声明的任何变量都称为“块参数”(block parameters)。在某种程度上,块的工作方式类似于函数(function),块参数的工作方式类似于函数的参数列表(argument list)。`each` 方法运行块(block)内的代码,并将集合(例如数组,`multiarr`)提供的参数传递给块。在上面的示例中,`each` 方法重复地将有四个元素的数组传递给块,并且这四个数组内的元素初始化为四个块参数 `a`,`b`,`c`,`d`。除了迭代集合之外,块还可以用于其它方面。 我将在第 10 章中对块(block)进行更多说明。 145 |
146 | 147 | ### 块(Blocks) 148 | 149 |
block_syntax.rb
150 | 151 | Ruby 有一种用于限定块的替代语法。你可以不使用 `do..end`,而是像这样使用花括号 `{..}`: 152 | 153 | # do..end 154 | [[1,2,3], [3,4,5], [6,7,8]].each do 155 | |a,b,c| 156 | puts( "#{a}, #{b}, #{c}" ) 157 | end 158 | 159 | # curly braces {..} 160 | [[1,2,3], [3,4,5], [6,7,8]].each { 161 | |a,b,c| 162 | puts( "#{a}, #{b}, #{c}" ) 163 | } 164 | 165 | 无论你使用哪个块限定符,都必须确保开放限定符,`'{'` 或 `'do'` 与 `each` 方法放在同一行。 在 `each` 和开放块限定符之间插入一个换行符是错误的语法。 166 | 167 | ### while 循环 168 | 169 | Ruby 也有一些其它的循环结构。这是一个 `while` 循环: 170 | 171 | while tired 172 | sleep 173 | end 174 | 175 | 或者,以另一种方式: 176 | 177 | sleep while tired 178 | 179 | 即使这两个示例的语法不同,它们也会执行相同的操作。在第一个示例中,`while` 和 `end` 之间的代码(这里是一个名为 `sleep` 方法的调用)会在布尔测试(在这里,是一个名为 `tired` 的方法的返回值)为 true 时执行。与 `for` 循环一样,关键字 `do` 可选的可以放置于出现在不同行的测试条件与要执行的循环体代码中间,当测试条件与循环代码出现在同一行时关键字 `do` 则是必须的。 180 | 181 | ### while 修饰符 182 | 183 | 在第二个版本的循环中(`sleep while tired`),要执行的循环代码(`sleep`)优先于测试条件(`while tired`)。该语法被称为“while 修饰符”(while modifie)。如果你想要使用此语法执行多个表达式,可以将它们放在 `begin` 和 `end` 关键字之间: 184 | 185 | begin 186 | sleep 187 | snore 188 | end while tired 189 | 190 |
1loops.rb
191 | 192 | 这个示例展示了各种替代语法: 193 | 194 | $hours_asleep = 0 195 | 196 | def tired 197 | if $hours_asleep >= 8 then 198 | $hours_asleep = 0 199 | return false 200 | else 201 | $hours_asleep += 1 202 | return true 203 | end 204 | end 205 | 206 | def snore 207 | puts('snore....') 208 | end 209 | 210 | def sleep 211 | puts("z" * $hours_asleep ) 212 | end 213 | 214 | while tired do sleep end # a single-line while loop 215 | 216 | while tired # a multi-line while loop 217 | sleep 218 | end 219 | 220 | sleep while tired # single-line while modifier 221 | 222 | begin # multi-line while modifier 223 | sleep 224 | snore 225 | end while tired 226 | 227 | 上面的最后一个示例(多行 `while` modifier)需要多加注意,因为它引入了一些重要的新特性。当使用 `begin` 和 `end` 限定的代码块优先于 `while` 测试时,该代码总是至少执行一次。在其它类型的 `while` 循环中,代码可能永远都不会执行,除非布尔测试开始为 true。 228 | 229 |
230 |

确保循环至少执行一次

231 | 232 | 通常 `while` 循环会执行 0 次或多次,因为布尔测试*先于*循环体执行;如果布尔测试在开始时就返回 false,则循环体内的代码永远不会运行。 233 | 234 | 但是,当 `while` 循环属于 `begin` 和 `end` 包裹的代码块类型时,循环将执行 1 次或多次,因为循环体内的代码*先于*布尔表达式执行。 235 |
236 | 237 |
2loops.rb
238 | 239 |
240 | 要了解这两种类型的 while 循环的行为差异,请运行 2loops.rb。 241 | 242 | 这些示例应该有助于阐明该问题: 243 | 244 | x = 100 245 | 246 | # The code in this loop never runs 247 | while (x < 100) do puts('x < 100') end 248 | 249 | # The code in this loop never runs 250 | puts('x < 100') while (x < 100) 251 | 252 | # But the code in loop runs once 253 | begin puts('x < 100') end while (x < 100) 254 |
255 | 256 | ### until 循环 257 | 258 | Ruby 也有一个 `until` 循环,可以被认为是 *'while not'* 循环。它的语法和选项与应用于 `while` 的那些相同——即测试条件与循环体代码可以放置于同一行中(此时 `do` 关键字是必须的),或者也可以放在不同行中(这时 `do` 是可选的)。 259 | 260 | 还有一个 `until` 修饰符,可以让你将循环体代码放置于测试条件之前,以及可选的是可以将循环体代码包含在 `begin` 和 `end` 之间来确保循环体代码块至少运行一次。 261 | 262 |
until.rb
263 | 264 | 这里有一些 `until` 循环的简单示例: 265 | 266 | i = 10 267 | 268 | until i == 10 do puts(i) end # never executes 269 | 270 | until i == 10 # never executes 271 | puts(i) 272 | i += 1 273 | end 274 | 275 | puts(i) until i == 10 # never executes 276 | 277 | begin # executes once 278 | puts(i) 279 | end until i == 10 280 | 281 | `while` 和 `until` 循环都可以像 `for` 循环一样用于迭代数组和其他集合。例如,这是迭代数组中所有元素的方法: 282 | 283 | while i < arr.length 284 | puts(arr[i]) 285 | i += 1 286 | end 287 | 288 | until i == arr.length 289 | puts(arr[i]) 290 | i +=1 291 | end 292 | 293 | ### 循环(Loop) 294 | 295 |
3loops.rb
296 | 297 | **3loops.rb** 中的示例应该看起来都很熟悉 - 除了最后一个: 298 | 299 | loop { 300 | puts(arr[i]) 301 | i+=1 302 | 303 | if (i == arr.length) then 304 | break 305 | end 306 | } 307 | 308 | 这里使用 `loop` 方法来重复地执行花括号内的代码块。这就像我们之前在 `each` 方法中使用的迭代器块一样。同样地,我们可以选择块的界定符 - 花括号或者 `do` 和 `end`: 309 | 310 | puts( "\nloop" ) 311 | i=0 312 | 313 | loop do 314 | puts(arr[i]) 315 | i+=1 316 | 317 | if (i == arr.length) then 318 | break 319 | end 320 | end 321 | 322 | 这段代码通过递增计数器变量 `i` 来遍历数组 `arr`,当 `(i == arr.length)` 条件求值为 true 时,跳出循环。你必须以这种方式跳出循环,因为不同于 `while` 或 `until` 323 | ,`loop` 方法执行测试条件以确定是否继续循环。 没有 `break`,它将永远循环。 324 | 325 | ## 深入探索 326 | 327 | Hashes, Arrays, Ranges 和 Sets 都包含(include)了一个名为 Enumerable 的 Ruby 模块(module)。模块是一种代码库(我将在第 12 章中更多地讨论模块)。在第 4 章中,我使用了 Comparable 模块为数组添加比较方法,例如 `<` 和 `>`。你可能还记得我是通过继承 Array 类并将 Comparable 模块 "including" 到子类中来完成此操作: 328 | 329 | class Array2 < Array 330 | include Comparable 331 | end 332 | 333 | ### Enumerable 模块 334 | 335 |
enum.rb
336 | 337 | Enumerable 模块已经被包含进了 Ruby 的 Array 类中,它提供了很多有用的方法,例如 `include?` 方法会在数组中找到一个特定的值时返回 true,`min` 方法则会返回最小的元素值,`max` 方法返回最大的元素值,`collect` 方法会创建一个由块(block)返回的值组成的新数组。 338 | 339 | arr = [1,2,3,4,5] 340 | y = arr.collect{ |i| i } #=> y = [1, 2, 3, 4] 341 | z = arr.collect{ |i| i * i } #=> z = [1, 4, 9, 16, 25] 342 | 343 | arr.include?( 3 ) #=> true 344 | arr.include?( 6 ) #=> false 345 | arr.min #=> 1 346 | arr.max #=> 5 347 | 348 |
enum2.rb
349 | 350 | 只要其它集合类包含 Enumerable 模块,就可以使用这些相同的方法。Hash 就是一个这样的类。但请记住,Hash 中的元素索引是没有顺序的,因此当你使用 `min` 和 `max` 方法时,将根据其数值返回最小和最大元素值 - 当元素值为字符串时,其数值由键(key)中字符的 ASCII 码确定。 351 | 352 | ### 自定义比较 353 | 354 | 但是我们假设你更喜欢 `min` 和 `max` 根据一些其它标准(比如字符串的长度)返回元素?最简单的方法是在块(block)内定义比较的本质。这与我在第 4 章中定义的排序块类似。你可能还记得我们通过将块(block)传递给 `sort` 方法来对 Hash(此处为变量 `h`)进行排序,如下所示: 355 | 356 | h.sort{ |a,b| a.to_s <=> b.to_s } 357 | 358 | 两个参数 `a` 和 `b` 表示来自 Hash 的两个元素,使用 `<=>` 比较方法进行比较。我们可以类似地将块(block)传递给 `max` 和 `min` 方法: 359 | 360 | h.min { |a,b| a[0].length <=> b[0].length } 361 | h.max { |a,b| a[0].length <=> b[0].length } 362 | 363 | 当 Hash 将元素传递给块时,它会以包含键值对(key-value)的数组形式传递。所以,如何一个 Hash 包含这样的元素... 364 | 365 | {"one"=>"for sorrow", "two"=>"for joy"} 366 | 367 | ...两个块参数,`a` 和 `b` 将会被初始化为两个数组: 368 | 369 | a = ["one", "for sorrow"] 370 | b = ["two", "for joy"] 371 | 372 | 这解释了为什么我在为 `max` 和 `min` 方法定义的自定义比较中特意比较的是两个块参数中位于索引 0 处的首个元素: 373 | 374 | a[0].length <=> b[0].length 375 | 376 | 这确保了比较是基于哈希中的*键*(keys)的。 377 | 378 | 如果你要比较*值*(values),而不是键(keys),只需要将数组的索引设置为 1: 379 | 380 |
enum3.rb
381 | 382 | p( h.min {|a,b| a[1].length <=> b[1].length } ) 383 | p( h.max {|a,b| a[1].length <=> b[1].length } ) 384 | 385 | 当然,你可以在块中定义其他类型的自定义比较。例如,假设你希望字符串 'one','two','three' 等按照我们说它们的顺序进行执行。这样做的一种方法是创建一个有序的字符串数组: 386 | 387 | str_arr=['one','two','three','four','five','six','seven'] 388 | 389 | 现在,如果一个 Hash,`h` 包含这些字符串作为键(key),则块可以使用 `str_array` 作为键的引用以确定最小值和最大值: 390 | 391 | h.min { |a,b| str_arr.index(a[0]) <=> str_arr.index(b[0])} 392 | #=> ["one", "for sorrow"] 393 | 394 | h.max { |a,b| str_arr.index(a[0]) <=> str_arr.index(b[0])} 395 | #=> ["seven", "for a secret never to be told"] 396 | 397 | 上面所有的示例都使用了 Array 和 Hash 类的 `min` 和 `max` 方法。请记住,是 Enumerable 模块给这些类提供了这些方法。 398 | 399 | 在某些情况下,能够将诸如 `max`,`min` 和 `collect` 之类的 Enumerable 方法应用于不是从现有的实现这些方法的类(例如 Array)中派生出来的类中是有用的。你可以在你的类中包含 Enumerable 模块,然后编写一个名为 `each` 的迭代器方法: 400 | 401 |
include_enum1.rb
402 | 403 | class MyCollection 404 | include Enumerable 405 | 406 | def initialize( someItems ) 407 | @items = someItems 408 | end 409 | 410 | def each 411 | @items.each { |i| 412 | yield( i ) 413 | } 414 | end 415 | end 416 | 417 | 在这里,你可以使用数组初始化一个 MyCollection 对象,该数组将存储在实例变量 `@items` 中。当你调用 Enumerable 模块提供的方法之一(例如 `min`,`max` 或 `collect`)时,这将“在幕后”(behind the scenes)调用 `each` 方法,以便一次获取一个数据。 418 | 419 | things = MyCollection.new(['x','yz','defgh','ij','klmno']) 420 | 421 | p( things.min ) #=> "defgh" 422 | p( things.max ) #=> "yz" 423 | p( things.collect{ |i| i.upcase } ) 424 | #=> ["X", "YZ", "DEFGH", "IJ", "KLMNO"] 425 | 426 |
include_enum2.rb
427 | 428 | 你可以类似地使用 `MyCollection` 类来处理数组,例如 Hashes 的键(keys)或值(values)。目前,`min` 和 `max` 方法采用基于数值执行比较的默认行为,因此基于字符的ASCII 值,'xy' 将被认为比 'abcd''更大'。如果你想执行一些其它类型的比较 - 例如,通过字符串长度来比较,以便 'abcd' 被认为大于 'xz' - 你可以覆盖 `min` 和 `max `方法: 429 | 430 |
include_enum3.rb
431 | 432 | def min 433 | @items.to_a.min { |a,b| a.length <=> b.length } 434 | end 435 | 436 | def max 437 | @items.to_a.max { |a,b| a.length <=> b.length } 438 | end 439 | 440 |
441 |

Each and Yield…

442 | 443 | 那么,当 Enumerable 模块中的方法调用你编写的 `each` 方法时,真正发生了什么?事实证明,Enumerable 方法(`min`,`max`,`collect` 等)给 `each` 方法传递了一个代码块(block)。这段代码期望一次接收一个数据(即来自某种集合的每个元素)。你的 `each` 方法以块参数的形式为其提供该项,例如此处的参数 `i`: 444 | 445 | def each 446 | @items.each{ |i| 447 | yield( i ) 448 | } 449 | end 450 | 451 | 关键字 `yield` 是一个特殊的 Ruby 魔术,它告诉代码运行传递给 `each` 方法的块 - 也就是说,运行 Enumerator 模块的 `min`,`max` 或 `collect` 方法传递的代码块。这意味着这些方法的代码块可以应用于各种不同类型的集合。你所要做的就是,i)在你的类中包含 Enumerable 模块;ii)编写 `each` 方法,确定 Enumerable 方法将使用哪些值。 452 |
-------------------------------------------------------------------------------- /md_2_html.js: -------------------------------------------------------------------------------- 1 | /*! convert markdown to html file */ 2 | 3 | const marked = require('marked'); 4 | 5 | const fs = require('fs'), 6 | path = require('path'); 7 | 8 | const SRC = 'markdown/', 9 | DEST = 'docs/html/'; 10 | 11 | let category = []; 12 | 13 | 14 | /** 15 | * -- Config tools 16 | */ 17 | marked.setOptions({ 18 | renderer : new marked.Renderer(), 19 | highlight: function (code) { 20 | return require('highlight.js').highlightAuto(code).value; 21 | }, 22 | pedantic : false, 23 | gfm : true, 24 | tables : true, 25 | breaks : false, 26 | sanitize : false, 27 | smartLists : true, 28 | smartypants: false, 29 | xhtml : false 30 | }); 31 | 32 | 33 | /** 34 | * Clear directory 35 | * 36 | * @param {any} dir_path 37 | */ 38 | function dir_clear(dir_path) { 39 | fs.readdirSync(dir_path).forEach(fileName => { 40 | const _file_path = path.join(dir_path, fileName); 41 | /* Type of judgment */ 42 | const _stats = fs.statSync(_file_path); 43 | if (_stats.isFile()) { 44 | fs.unlinkSync(_file_path); 45 | } else if (_stats.isDirectory()) { 46 | /* Recursive */ 47 | return dir_clear(_file_path) || fs.rmdirSync(_file_path); 48 | } 49 | }); 50 | } 51 | 52 | 53 | /** 54 | * Convert markdown to html 55 | * 56 | * @param {any} dir_path 57 | */ 58 | function convert_md_2_html(dir_path) { 59 | 60 | fs.readdirSync(dir_path).forEach(fileName => { 61 | 62 | const file_path = path.join(dir_path, fileName), 63 | target_path = path.join(DEST, path.relative(SRC, file_path)); 64 | 65 | const stats = fs.statSync(file_path); 66 | 67 | // markdown file 68 | if (stats.isFile() && path.extname(fileName) === '.md') { 69 | 70 | let content = fs.readFileSync(file_path, 'utf-8'), 71 | info = {}, 72 | new_file_path = path.join(path.dirname(target_path), path.basename(target_path, '.md') + '.html'); 73 | 74 | // get file info 75 | try { 76 | info = content.match(/---[\s\S]*?(\{[\s\S]*?\})[\s\S]*?---/); 77 | info && (content = content.replace(/---[\s\S]*?---/, '')) && (info = JSON.parse(info[1])) || (info = {}); 78 | } catch (err) { 79 | console.log(err.message); 80 | console.log(file_path + ': no document info !'); 81 | info = {}; 82 | } 83 | 84 | // convert markdown 2 html 85 | fs.writeFile( 86 | new_file_path, 87 | marked(content), 88 | err => err && console.log(err.message) 89 | ); 90 | 91 | // add info 92 | category.push({ 93 | 'order': +path.basename(new_file_path).match(/\d+/)[0], 94 | 'path' : path.relative(DEST, new_file_path), 95 | 'title': info.title || '无标题文档', 96 | 'ctime': info.ctime || stats.ctime.toLocaleString(), 97 | 'mtime': info.mtime || stats.mtime.toLocaleString() 98 | }); 99 | 100 | } 101 | 102 | }); 103 | 104 | } 105 | 106 | 107 | dir_clear(DEST); 108 | console.log('- Clear directory complete!'); 109 | 110 | convert_md_2_html(SRC); 111 | console.log('- Convert markdown to html complete!'); 112 | 113 | /* Create category json file */ 114 | fs.writeFile( 115 | path.join(DEST, 'category.json'), 116 | JSON.stringify(category.sort((a, b) => a.order - b.order)), err => !err && console.log('- The category json file has been generated!') 117 | ); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "the_book_of_ruby", 3 | "version": "1.0.0", 4 | "description": "Chinese translation of the book - 《The Book Of Ruby》", 5 | "main": "app.js", 6 | "dependencies": { 7 | "lodash": "^4.17.11" 8 | }, 9 | "devDependencies": { 10 | "@babel/core": "^7.1.2", 11 | "@babel/plugin-proposal-class-properties": "^7.1.0", 12 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0", 13 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 14 | "@babel/preset-env": "^7.1.0", 15 | "autoprefixer": "^9.2.1", 16 | "babel-eslint": "^10.0.1", 17 | "babel-loader": "^8.0.4", 18 | "browser-sync": "^2.26.5", 19 | "copy-webpack-plugin": "^4.5.4", 20 | "css-loader": "^1.0.0", 21 | "del": "^3.0.0", 22 | "eslint": "^5.7.0", 23 | "file-loader": "^2.0.0", 24 | "highlight.js": "^9.13.1", 25 | "html-webpack-plugin": "^3.2.0", 26 | "imagemin-jpeg-recompress": "^6.0.0", 27 | "imagemin-webpack-plugin": "^2.4.2", 28 | "marked": "^0.6.2", 29 | "mini-css-extract-plugin": "^0.4.4", 30 | "node-sass": "^4.12.0", 31 | "optimize-css-assets-webpack-plugin": "^5.0.1", 32 | "postcss-loader": "^3.0.0", 33 | "sass-loader": "^7.1.0", 34 | "style-loader": "^0.23.1", 35 | "uglifyjs-webpack-plugin": "^2.0.1", 36 | "url-loader": "^1.1.2", 37 | "webpack": "^4.22.0" 38 | }, 39 | "scripts": { 40 | "start": "set NODE_ENV=development & node index.js", 41 | "build": "node index.js", 42 | "webpack": "npx webpack --config ./config/webpack.config.js" 43 | }, 44 | "author": "mrwang", 45 | "license": "ISC" 46 | } 47 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | The Book Of Ruby - Chinese 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 28 | 32 | 33 | 34 | 35 |
36 | 37 | 42 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | /* App entry file */ 2 | 3 | import './app.scss'; 4 | 5 | import controller from 'utils/controller.js'; 6 | 7 | import { generate_category, auto_active_nav, add_header, add_footer } from 'components/category.js'; 8 | import { insert_line_num } from 'components/line-numbers.js'; 9 | import { generate_navigation } from 'components/navigation.js'; 10 | 11 | 12 | generate_category(); 13 | 14 | controller([ 15 | () => (document.body.scrollTop = 0) || (document.documentElement.scrollTop = 0), 16 | auto_active_nav, 17 | add_header, 18 | add_footer, 19 | insert_line_num, 20 | generate_navigation 21 | ]); -------------------------------------------------------------------------------- /src/app.scss: -------------------------------------------------------------------------------- 1 | /* App style sheet */ 2 | 3 | @import './utils/base.scss'; 4 | @import './utils/github-gist.css'; 5 | 6 | 7 | @media (min-width: 1600px) { 8 | .d-gl-block { 9 | display: block !important; 10 | } 11 | } 12 | 13 | 14 | .category { 15 | overflow: auto; 16 | position: fixed; 17 | left: 0; 18 | top: 0; 19 | height: 100%; 20 | padding: 2rem 1rem 2rem 2rem; 21 | font-size: 1.35rem; 22 | 23 | > .chapter { 24 | color: gray; 25 | text-decoration: none; 26 | 27 | &:hover, &.active { 28 | color: orange; 29 | } 30 | } 31 | 32 | } 33 | 34 | // content navigation 35 | .navigation { 36 | overflow: auto; 37 | position: absolute; 38 | top: 0; 39 | right: 0; 40 | transform: translate(100%, 0); 41 | padding: 100px 10px 20px; 42 | font-size: 1rem; 43 | 44 | > div { 45 | border-left: 2px solid transparent; 46 | 47 | &:hover, &.active { 48 | color: #2196f3; 49 | border-color: #2196f3; 50 | cursor: pointer; 51 | } 52 | } 53 | 54 | > .h1 { 55 | font-size: 2em; 56 | } 57 | 58 | > .h2 { 59 | padding-left: .5rem; 60 | font-size: 1.75em; 61 | } 62 | 63 | > .h3 { 64 | padding-left: 1rem; 65 | font-size: 1.5em; 66 | } 67 | 68 | > .h4 { 69 | padding-left: 1.5rem; 70 | font-size: 1.25em; 71 | } 72 | } 73 | 74 | // book content style 75 | .file-content { 76 | position: relative; 77 | padding: 4rem 1rem 6rem 1rem; 78 | font-size: 1.35rem; 79 | line-height: 1.8; 80 | 81 | > .file-header { 82 | padding: 0 .5rem; 83 | border-bottom: 1px solid #9e9e9e; 84 | } 85 | 86 | > .file-footer { 87 | padding: 0 .5rem; 88 | border-top: 1px solid #9e9e9e; 89 | } 90 | 91 | h3, h4, h5, h6 { 92 | padding: .5rem; 93 | background-color: #e9ecef; 94 | } 95 | 96 | h1, h2 { 97 | text-align: center; 98 | } 99 | 100 | p { 101 | text-indent: 2em; 102 | 103 | > * { 104 | text-indent: 0; 105 | } 106 | 107 | > em { 108 | margin-right: .5rem; 109 | } 110 | } 111 | 112 | ul { 113 | padding-left: 2em; 114 | 115 | > li { 116 | list-style-type: disc; 117 | 118 | > ul { 119 | padding-left: 1em; 120 | 121 | > li { 122 | list-style-type: circle; 123 | } 124 | } 125 | 126 | > p { 127 | text-indent: 0; 128 | } 129 | } 130 | 131 | } 132 | 133 | blockquote { 134 | padding: .25rem 0 .25rem .5rem; 135 | margin: 1rem 0; 136 | background-color: #f8f9fa; 137 | border-left: 4px solid #2196f3; 138 | 139 | > p:last-child { 140 | text-indent: 0; 141 | margin-bottom: 0; 142 | } 143 | } 144 | 145 | pre { 146 | overflow: auto; 147 | padding: .5rem 0; 148 | margin: 1rem 0; 149 | background-color: #f8f9fa; 150 | 151 | > code { 152 | display: block; 153 | position: relative; 154 | padding: 0 0 0 3rem; 155 | 156 | /* line-number */ 157 | &::before { 158 | content: attr(line-number); 159 | position: absolute; 160 | left: 0; 161 | width: 2.5rem; 162 | padding-right: .5rem; 163 | color: #bbb; 164 | text-align: right; 165 | border-right: 1px solid #ced4da; 166 | } 167 | } 168 | } 169 | 170 | table { 171 | width: 100%; 172 | margin-bottom: 1rem; 173 | border: 1px solid black; 174 | background-color: transparent; 175 | 176 | th { 177 | padding: 0 .75rem; 178 | border-bottom: 1px solid black; 179 | border-right: 1px solid black; 180 | vertical-align: bottom; 181 | 182 | &:last-child { 183 | border-right: none; 184 | } 185 | } 186 | 187 | td { 188 | padding: 0 .75rem; 189 | border-top: 1px solid black; 190 | border-right: 1px solid black; 191 | vertical-align: top; 192 | 193 | &:last-child { 194 | border-right: none; 195 | } 196 | } 197 | } 198 | 199 | .note { 200 | overflow: auto; 201 | padding: .5rem 1rem; 202 | margin: 1rem 0; 203 | border: 1px dashed black; 204 | 205 | > p:last-child { 206 | margin-bottom: 0; 207 | } 208 | } 209 | 210 | .code-file { 211 | text-align: right; 212 | margin-bottom: .5rem; 213 | 214 | > span { 215 | display: inline-block; 216 | padding: 0 .5rem; 217 | font-weight: bold; 218 | border: 1px solid black; 219 | } 220 | } 221 | 222 | } -------------------------------------------------------------------------------- /src/components/category.js: -------------------------------------------------------------------------------- 1 | /*! generate category */ 2 | 3 | import category from 'html/category.json'; 4 | 5 | 6 | function generate_category() { 7 | let str = ''; 8 | 9 | category.forEach(file => { 10 | str += '' + file.title + ''; 11 | }); 12 | 13 | document.getElementById('nav').innerHTML = str; 14 | } 15 | 16 | function auto_active_nav(file_path) { 17 | for (let ele of [].slice.apply(document.getElementById('nav').children)) { 18 | if (ele.getAttribute('href').slice(2) === file_path) { 19 | ele.classList.add('active'); 20 | } else { 21 | ele.classList.remove('active'); 22 | } 23 | } 24 | } 25 | 26 | function add_header(file_path) { 27 | const file = category.find(file => file.path === file_path), 28 | content_ele = document.getElementById('content'), 29 | header_ele = document.createElement('header'); 30 | 31 | header_ele.classList.value = 'mb-5 pb-2 file-header'; 32 | header_ele.innerHTML = '' + file.ctime + ' 添加' + 33 | '' + file.mtime + ' 更新' + 34 | 'on GitHub' + 35 | 'PDF of book'; 36 | 37 | content_ele.insertBefore(header_ele, content_ele.firstChild); 38 | } 39 | 40 | function add_footer(file_path) { 41 | const file = category.find(file => file.path === file_path), 42 | index = category.indexOf(file), 43 | footer_ele = document.createElement('footer'); 44 | 45 | let last = category[index - 1], 46 | next = category[index + 1]; 47 | 48 | footer_ele.innerHTML = '' + 49 | (last && ('<< ' + last.title + '') || '') + 50 | (next && ('' + next.title + ' >>') || ''); 51 | 52 | footer_ele.classList.value = 'file-footer clearfix mt-5 pt-2'; 53 | 54 | document.getElementById('content').appendChild(footer_ele); 55 | } 56 | 57 | 58 | export { 59 | generate_category, 60 | auto_active_nav, 61 | add_header, 62 | add_footer 63 | }; 64 | 65 | export default generate_category; -------------------------------------------------------------------------------- /src/components/line-numbers.js: -------------------------------------------------------------------------------- 1 | /* code line-numbers */ 2 | 3 | function insert_line_num() { 4 | 5 | document.querySelectorAll('.file-content pre > code').forEach(ele => { 6 | const style_obj = getComputedStyle(ele), 7 | _height = +style_obj.height.match(/[\d.]+/), 8 | _lh = +style_obj.lineHeight.match(/[\d.]+/); 9 | 10 | let _num = Math.ceil(_height / _lh), 11 | line_number = []; 12 | 13 | while (_num--) { 14 | line_number.push(_num + 1 + '\n'); 15 | } 16 | 17 | ele.setAttribute('line-number', line_number.reverse().join(' ')); 18 | }); 19 | 20 | } 21 | 22 | export { 23 | insert_line_num 24 | }; 25 | 26 | export default insert_line_num; -------------------------------------------------------------------------------- /src/components/navigation.js: -------------------------------------------------------------------------------- 1 | /*! content navigation */ 2 | 3 | // Generate navigation 4 | function generate_navigation(file_path) { 5 | 6 | // none 7 | if (+file_path.split('-')[0] < 2) { 8 | return; 9 | } 10 | 11 | let sroll_top = document.body.scrollTop || document.documentElement.scrollTop, 12 | h_datas = [], 13 | nav_ele = document.createElement('div'), 14 | html = ''; 15 | 16 | ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(h => { 17 | document.querySelectorAll(h).forEach(ele => { 18 | h_datas.push({ 19 | type: h, 20 | top : ele.getBoundingClientRect().top + sroll_top, 21 | text: ele.textContent 22 | }); 23 | }); 24 | }); 25 | 26 | nav_ele.classList.value = 'navigation d-none d-gl-block'; 27 | 28 | h_datas.sort((a, b) => a.top - b.top).forEach(h => { 29 | html += '
' + h.text + '
'; 30 | }); 31 | 32 | nav_ele.innerHTML = html; 33 | 34 | document.querySelector('.file-content').appendChild(nav_ele); 35 | 36 | // scroll 37 | window.onscroll = function() { 38 | nav_ele.style.top = (document.body.scrollTop || document.documentElement.scrollTop) + 'px'; 39 | }; 40 | 41 | // click 42 | setTimeout(() => { 43 | 44 | Array.from(nav_ele.children).forEach(ele => { 45 | ele.onclick = () => { 46 | document.body.scrollTop = +ele.getAttribute('s_top'); 47 | document.documentElement.scrollTop = +ele.getAttribute('s_top'); 48 | }; 49 | }); 50 | 51 | }, 100); 52 | } 53 | 54 | 55 | export { 56 | generate_navigation 57 | }; 58 | 59 | export default generate_navigation; -------------------------------------------------------------------------------- /src/utils/base.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | Sharing basic style sheets 3 | */ 4 | 5 | /* 公共样式表(全局) */ 6 | html, body { 7 | /* 适配移动端 */ 8 | font-size: 12px; 9 | /* 保证字体安全 */ 10 | font-family: 11 | // Safari for OS X and iOS (San Francisco) 12 | -apple-system, 13 | // Chrome < 56 for OS X (San Francisco) 14 | BlinkMacSystemFont, "Helvetica Neue", Helvetica, 15 | // Windows 16 | Arial, "Segoe UI", "Microsoft Yahei", 17 | // Android 18 | "Roboto", 19 | // Basic web fallback 20 | "Hiragino Sans GB", "Heiti SC", "WenQuanYi Micro Hei", sans-serif, 21 | // Emoji fonts 22 | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 23 | /* 统一行高 */ 24 | line-height: 1.5; 25 | /* 页面占满 */ 26 | width: 100%; 27 | height: 100%; 28 | /* 最小宽度 */ 29 | //min-width: 1280px; 30 | /* 最小高度 */ 31 | //min-height: 768px; 32 | /* relative 才能让 absolute 子元素的百分比宽度生效 */ 33 | position: relative; 34 | /* 字颜色 */ 35 | color: black; 36 | /* 背景色 */ 37 | background-color: white; 38 | } 39 | * { 40 | /* 盒子模型统一 */ 41 | box-sizing: border-box; 42 | /* 去除边距 */ 43 | padding: 0; 44 | margin: 0; 45 | } 46 | :focus { 47 | outline: none; 48 | } 49 | a{ 50 | text-decoration: none; 51 | } 52 | ul, li{ 53 | list-style: none; 54 | } 55 | 56 | /** 57 | * 滚动条样式 ----------------------------------------------- 58 | */ 59 | 60 | ::-webkit-scrollbar { 61 | /* 整体 */ 62 | width: 8px; 63 | height: 8px; 64 | background-color: #ccc; 65 | border-radius: 4px; 66 | } 67 | 68 | /* ::-webkit-scrollbar:hover { 69 | width: 16px; 70 | height: 16px; 71 | border-radius: 8px; 72 | } */ 73 | 74 | ::-webkit-scrollbar-button { 75 | /* 上下按钮 */ 76 | display: none; 77 | background-color: auto; 78 | } 79 | 80 | ::-webkit-scrollbar-track { 81 | /* 外层轨道 */ 82 | background-color: transparent; 83 | } 84 | 85 | ::-webkit-scrollbar-track-piece { 86 | /* 内层轨道 */ 87 | background-color: transparent; 88 | } 89 | 90 | ::-webkit-scrollbar-thumb { 91 | /* 拖动部分 */ 92 | background-color: rgba(22, 22, 22, .75); 93 | border-radius: 4px; 94 | } 95 | 96 | /* ::-webkit-scrollbar-thumb:hover { 97 | border-radius: 8px; 98 | } */ 99 | 100 | ::-webkit-scrollbar-corner { 101 | /* 边角 */ 102 | background-color: auto; 103 | } 104 | 105 | ::-webkit-resizer { 106 | /* 缩放 */ 107 | background-color: auto; 108 | } 109 | 110 | /** 111 | * 清除浮动 ---------------------------------------- 112 | */ 113 | .clear-float:before, .clear-float:after { 114 | content: ""; 115 | display: table; 116 | } 117 | .clear-float:after { 118 | clear: both; 119 | } 120 | .clear-float { 121 | zoom: 1; 122 | } 123 | 124 | /* 响应式工具 125 | 126 | .responsive-container : 自适应模块的包裹元素,提供一个自适应容器,全页面自适应应先加在 body html 上 127 | 128 | .responsive-abs : 页面浮动上层元素,width、height 自己设置(例如导航,页头,页尾) 129 | 130 | .responsive-rel : 页面主要内容元素,width、height 自动占满整个容器,可利用 padding 来将 .responsive-abs 元素覆盖的部分隔开 131 | 132 | */ 133 | .responsive-abs { 134 | box-sizing: border-box; 135 | position: absolute; 136 | left: 0; 137 | top: 0; 138 | z-index: 2; 139 | } 140 | .responsive-rel, .responsive-container { 141 | box-sizing: border-box; 142 | width: 100%; 143 | height: 100%; 144 | position: relative; 145 | left: 0; 146 | top: 0; 147 | z-index: 1; 148 | } -------------------------------------------------------------------------------- /src/utils/controller.js: -------------------------------------------------------------------------------- 1 | /* controller router */ 2 | 3 | import category from 'html/category.json'; 4 | 5 | 6 | // controller 7 | function controller(middlewares = []) { 8 | 9 | // register 10 | window.onhashchange = () => { 11 | 12 | let file_path = location.hash.slice(2); 13 | 14 | // get html 15 | fetch('./html/' + file_path).then(response => { 16 | 17 | if (response.status == 200) { 18 | 19 | response.text().then(html => document.getElementById('content').innerHTML = html).then(() => { 20 | // Middleware 21 | middlewares.forEach(callback => callback(file_path)); 22 | }); 23 | 24 | } else { 25 | location.hash = '/' + category[0].path; 26 | } 27 | 28 | }); 29 | 30 | }; 31 | 32 | // auto 33 | window.onhashchange(); 34 | 35 | } 36 | 37 | export default controller; -------------------------------------------------------------------------------- /src/utils/github-gist.css: -------------------------------------------------------------------------------- 1 | /** 2 | * GitHub Gist Theme 3 | * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | background: white; 9 | padding: 0.5em; 10 | color: #333333; 11 | overflow-x: auto; 12 | } 13 | 14 | .hljs-comment, 15 | .hljs-meta { 16 | color: #969896; 17 | } 18 | 19 | .hljs-string, 20 | .hljs-variable, 21 | .hljs-template-variable, 22 | .hljs-strong, 23 | .hljs-emphasis, 24 | .hljs-quote { 25 | color: #df5000; 26 | } 27 | 28 | .hljs-keyword, 29 | .hljs-selector-tag, 30 | .hljs-type { 31 | color: #a71d5d; 32 | } 33 | 34 | .hljs-literal, 35 | .hljs-symbol, 36 | .hljs-bullet, 37 | .hljs-attribute { 38 | color: #0086b3; 39 | } 40 | 41 | .hljs-section, 42 | .hljs-name { 43 | color: #63a35c; 44 | } 45 | 46 | .hljs-tag { 47 | color: #333333; 48 | } 49 | 50 | .hljs-title, 51 | .hljs-attr, 52 | .hljs-selector-id, 53 | .hljs-selector-class, 54 | .hljs-selector-attr, 55 | .hljs-selector-pseudo { 56 | color: #795da3; 57 | } 58 | 59 | .hljs-addition { 60 | color: #55a532; 61 | background-color: #eaffea; 62 | } 63 | 64 | .hljs-deletion { 65 | color: #bd2c00; 66 | background-color: #ffecec; 67 | } 68 | 69 | .hljs-link { 70 | text-decoration: underline; 71 | } 72 | -------------------------------------------------------------------------------- /src/vendors/fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wang1212/the-book-of-ruby/bf325cb73467a8dd3619cec36c259067c79c911d/src/vendors/fonts/.gitkeep -------------------------------------------------------------------------------- /src/vendors/libs/bootstrap-4.1.3/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.1.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2018 The Bootstrap Authors 4 | * Copyright 2011-2018 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /src/vendors/plugins/compatible-plugin/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /src/vendors/plugins/compatible-plugin/respond.min.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl 2 | * Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT 3 | * */ 4 | 5 | !function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b8||!Z){return}var C={"NW":"*.Dom.select","MooTools":"$$","DOMAssistant":"*.$","Prototype":"$$","YAHOO":"*.util.Selector.query","Sizzle":"*","jQuery":"*","dojo":"*.query"};var x;var Y=[];var A=[];var D=0;var d=true;var M="slvzr";var b=/(\/\*[^*]*\*+([^\/][^*]*\*+)*\/)\s*?/g;var r=/@import\s*(?:(?:(?:url\(\s*(['"]?)(.*)\1)\s*\))|(?:(['"])(.*)\3))\s*([^;]*);/g;var F=/(behavior\s*?:\s*)?\burl\(\s*(["']?)(?!data:)([^"')]+)\2\s*\)/g;var G=/^:(empty|(first|last|only|nth(-last)?)-(child|of-type))$/;var W=/:(:first-(?:line|letter))/g;var H=/((?:^|(?:\s*})+)(?:\s*@media[^{]+{)?)\s*([^\{]*?[\[:][^{]+)/g;var E=/([ +~>])|(:[a-z-]+(?:\(.*?\)+)?)|(\[.*?\])/g;var L=/(:not\()?:(hover|enabled|disabled|focus|checked|target|active|visited|first-line|first-letter)\)?/g;var ab=/[^\w-]/g;var s=/^(INPUT|SELECT|TEXTAREA|BUTTON)$/;var q=/^(checkbox|radio)$/;var w=y>6?/[\$\^*]=(['"])\1/:null;var e=/([(\[+~])\s+/g;var P=/\s+([)\]+~])/g;var t=/\s+/g;var c=/^\s*((?:[\S\s]*\S)?)\s*$/;var v="";var p=" ";var aa="$1";function X(ac){return ac.replace(W,aa).replace(H,function(ae,aj,ai){var af=ai.split(",");for(var ak=0,ah=af.length;ak0){A.push({selector:ad.substring(0,al),patches:ag});ag=[]}return ao}else{var aq=(ap)?B(ap):V(an);if(aq){ag.push(aq);return"."+aq.className}return am}})}return aj+af.join(",")})}function V(ac){return(!w||w.test(ac))?{className:I(ac),applyClass:true}:null}function B(ah){var ag=true;var af=I(ah.slice(1));var ae=ah.substring(0,5)==":not(";var ad;var ai;if(ae){ah=ah.slice(5,-1)}var ac=ah.indexOf("(");if(ac>-1){ah=ah.substring(0,ac)}if(ah.charAt(0)==":"){switch(ah.slice(1)){case"root":ag=function(aj){return ae?aj!=z:aj==z};break;case"target":if(y==8){ag=function(ak){var aj=function(){var am=location.hash;var al=am.slice(1);return ae?(am==v||ak.id!=al):(am!=v&&ak.id==al)};o(N,"hashchange",function(){J(ak,af,aj())});return aj()};break}return false;case"checked":ag=function(aj){if(q.test(aj.type)){o(aj,"propertychange",function(){if(event.propertyName=="checked"){J(aj,af,aj.checked!==ae)}})}return aj.checked!==ae};break;case"disabled":ae=!ae;case"enabled":ag=function(aj){if(s.test(aj.tagName)){o(aj,"propertychange",function(){if(event.propertyName=="$disabled"){J(aj,af,aj.$disabled===ae)}});Y.push(aj);aj.$disabled=aj.disabled;return aj.disabled===ae}return ah==":enabled"?ae:!ae};break;case"focus":ad="focus";ai="blur";case"hover":if(!ad){ad="mouseenter";ai="mouseleave"}ag=function(aj){o(aj,ae?ai:ad,function(){J(aj,af,true)});o(aj,ae?ad:ai,function(){J(aj,af,false)});return ae};break;default:if(!G.test(ah)){return false}break}}return{className:af,applyClass:ag}}function S(){var af,ac,ad,ak;for(var an=0;an0){setInterval(function(){for(var ae=0,ac=Y.length;ae0)?f[0].href:m.location.href;h();U(N,function(){for(var ae in C){var ac,af,ad=N;if(N[ae]){ac=C[ae].replace("*",ae).split(".");while((af=ac.shift())&&(ad=ad[af])){}if(typeof ad=="function"){x=ad;T();return}}}}); 27 | /*! 28 | * ContentLoaded.js by Diego Perini, modified for IE<9 only (to save space) 29 | * 30 | * Author: Diego Perini (diego.perini at gmail.com) 31 | * Summary: cross-browser wrapper for DOMContentLoaded 32 | * Updated: 20101020 33 | * License: MIT 34 | * Version: 1.2 35 | * 36 | * URL: 37 | * http://javascript.nwbox.com/ContentLoaded/ 38 | * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE 39 | * 40 | */ 41 | function U(ag,ad){var ac=false,af=true,ai=function(aj){if(aj.type=="readystatechange"&&m.readyState!="complete"){return}(aj.type=="load"?ag:m).detachEvent("on"+aj.type,ai,false);if(!ac&&(ac=true)){ad.call(ag,aj.type||aj)}},ah=function(){try{z.doScroll("left")}catch(aj){setTimeout(ah,50);return}ai("poll")};if(m.readyState=="complete"){ad.call(ag,v)}else{if(m.createEventObject&&z.doScroll){try{af=!ag.frameElement}catch(ae){}if(af){ah()}}o(m,"readystatechange",ai);o(ag,"load",ai)}}})(this); --------------------------------------------------------------------------------