react-router下,基于Switch,可以实现针对未匹配的路由调用指定的component来展现

<Switch>
  <Route exact path="/" component={Home}/>
  <Route path="/about" component={About}/>
  <Route path="/:user" component={User}/>
  <Route component={NoMatch}/>
</Switch>

现在的需求是:在发现有未匹配的路由时,自动跳转到首页

如果把上面的代码改成:

<Switch>
  <Route exact path="/" component={Home}/>
  <Route path="/about" component={About}/>
  <Route path="/:user" component={User}/>
  <Route component={Home}/>
</Switch>

这个可以实现的时,未匹配的路由展现首页的内容,可是当前的url还是那个未被匹配的路由。这样的显示就会有点怪异。

针对这个问题,可以使用react-router里面的另外一个组件Redirect

基于Redirect新建如下组件:

const RouteFallback = (props) => {
    console.log('route fallback with location: ', props.location);
    return <Redirect to={{
        pathname: '/',
        from: props.location
    }} />
}

再重新调整Switch:

<Switch>
  <Route exact path="/" component={Home}/>
  <Route path="/about" component={About}/>
  <Route path="/:user" component={User}/>
  <Route component={RouteFallback}/>
</Switch>

这样就能实现针对非法路径的自动跳转首页的功能了

起因

今天上午把双十二活动相关的页面给上线了,这次的发版,涉及的内容点比较多,上线的时候,没仔细想,打了一个tag就直接在线上git pull,然后活动页挂了…

糟糕的一连串操作

我是用的pm2 list查看的项目的状态,发现状态变成errored才知道出了问题。

这时候一回想才意识到,有些模块没安装。

在本地执行tig -- package.json,快速的查找两个版本之间都安装过哪些模块,然后对应的在线上npm install。但是一直被webpakcuglifyjs-webpack-plugin干扰的没法正常安装。

一狠心,rm -rf webpack直接把webpack给移除了。再次执行安装,还是同样的错误,再找到node_modules/.bin/webpack,移除(后面才知道用npm rm/uninstall 才是更正确的做法)。

webpack的错误总算没了,然后安装又报403的错误,自有的npm服务verdaccio出错了。

这时候终于意识到,问题不是一会儿能解决的,时间来来回回也过了十分钟,赶紧回滚服务到稳定版不:

  1. git checkout v1.18.1,把代码回滚到之前稳定版本。
  2. 修改pm2的配置文件ecosystem.config.js,配置关闭watch模式,这样后面安装模块和分支切换不会干扰正在运行的服务
  3. pm2 restart来重启服务,顺利关闭watch模式,服务本身也没有报错。

回滚完成。

接下来先是登上内网服务器来修复npm服务的问题:

  1. ps aux | grep verdaccio找到verdaccio的进程号,kill掉
  2. verdaccio & 重启verdaccio
  3. 在线上尝试npm install一个模块,还是有问题
  4. 发现线上访问npm服务用的域名是之前废弃的
  5. 编辑~/.npmrc 调整相关配置
  6. 再次安装,成功通过

现在npm服务正常,开始来面对之前出现的webpack出错的问题。我这个是线上服务器,是用不上webpack的,在安装模块的时候,出现这样的问题,很不正常。

Google了一下,发现可以用npm install --only=production来让npm只安装package.json里面dependencies部分的模块。

编辑package.json,整理dependencies和devDependencies,使得对应的模块在对应的分类中。

执行npm install --only=production安装所有更新,之前查package.json的编辑历史那一步是多余的。

更新完毕之后,

  1. git checkout .去掉所有临时修改的文件。
  2. git checkout master回到master分支
  3. pm2 restart来重启服务
  4. pm2 listpm2 log来检测状态和日志

一切正常,完成上线。

后续操作

在本地,重新审查了一遍package.json,一个模块一个模块的查找,去掉代码里面已经没有使用的模块,划分清除相关模块归属。

编辑ecosystem.config.js,配置development环境里面开启watch模式,production环境禁用。

打tag。

整个操作算是完成。

总结

这次的发版算是一次当头棒喝,在尝试用NodeJs来做后端的前台服务这么久,一直顺风顺水,有点忘却应有的严谨。

后端代码的线上环境没法跟前端这样明确,很多时候一个git pull没法解决所有问题。

需要一个持续集成的好的工作流。

这次出问题的一个主要原因就是太长时间的一个大的任务分支,导致整个项目跨度太大,这样在合并主分支并上线的时候风险自然就大了。

下次上线,一个正确的更新应该类似这样:git pull;npm install --only=production;pm2 restart {server}

还是不打算把node_modules/目录的内容放到git仓库里,虽然这样可以保证模块的同步,可是会增加仓库的体积,也会对其他成员的开发环境造成干扰。

或许可以考虑用Docker来包裹服务,保证其代码和环境的一致性。

最近在尝试使用styled-components来做React下的样式开发,这样可以利用js语言的灵活来增强css的能力。

在多屏适配这块,一直是在使用淘宝的lib-flexible

在用styled-components处理px到rem的转换时,有点问题了。styled-components自己本着基础库的原则,是没有附加这些复杂的配置,所以一开始想的是自己写一个函数来做单位换算:

function r(pxValue) {
    const ratio = 20; // 根据项目配置比例的方式自行设定
    pxValue = parseInt(pxValue);

    return pxValue / ratio + 'rem';
}

然后在写样式的时候这样写

// 保留了px的单位而不是传递数字,使得代码具有可读性,方便维护
let AvatarImage = styled(Image)`
    width: ${r('132px')}; 
    height: ${r('132px')};
`;

这样满足了基本需求。

不过作为喜欢偷懒喜欢折腾的程序员,总觉得这样写很啰嗦,所以想着能不能也跟styled-components那样使用标签模版的功能

所以也就有了第二版的function r:

function r(pxValue) {
    const ratio = 20; // 根据项目配置比例的方式自行设定

    // 针对template literals
    if (Array.isArray(pxValue)) {
        pxValue = pxValue[0];
    }

    pxValue = parseInt(pxValue);

    return pxValue / ratio + 'rem';
}

在样式书写上,就可以这样写了:

let AvatarImage = styled(Image)`
    width: ${r`132px`};
    height: ${r`132px`};
`;

少写了两个括号,看着也清晰多了。

不过还是不太满意,因为样式里面如果数值一多,写起来还是有点费事。

既然能够实现单个单位的换算,那用正则表达式匹配所有样式字符串里面的px值,再替换为rem值,再把计算的结果返回给styled-components不就可以了。

想法是好的,不过在读取解析已有的样式模版字符串,正则匹配,还有如何把结果传递给styled-components这些方面,坑还是很多的,折腾了一个多小时,也总算解决了。

这里面,最核心的解析已有的样式模版字符串,styled-components提供了一个helper: css,使得工作量大大减少。

function r(pxValue) {
    const ratio = 20; // 根据项目配置比例的方式自行设定

    // 针对template literals
    if (Array.isArray(pxValue)) {
        pxValue = pxValue[0];
    }

    pxValue = parseInt(pxValue);

    return pxValue / ratio + 'rem';
}

// 把字符串样式里面的px单位换算成rem的
// 支持多行匹配
function transformPxToRem(style) {
    // 避免处理了函数等情况
    if (typeof style !== 'string') {
        return style;
    }

    return style.replace(/\d+px/gm, matched => {
        return r(matched);
    });
}

// 实现在把样式传递给styled之前,预先用transformPxToRem处理
function t(strings, ...interpolations) {
    let styles = css(strings, ...interpolations); // css是styled-components的一个helper
    styles = styles.map(transformPxToRem);

    // 模拟raw的调用
    return [[""], styles]
};

基于以上代码,在最终书写样式的时候就可以这样:

let AvatarImage = styled(Image)(...t`
    width: 132px;
    height: 132px;
`);

以函数的形式调用styled-compoent,而不是标签模版的形式,同时解构t函数的返回值,这样的结果和styled-components直接计算标签模版是一样的。

不过在实现这个功能之后,在写这篇文章的时候,发现lib-flexible不再用rem作为多端适配的方案了,而是改用的vm

不过最核心的预处理部分已经实现了,后面的调整还是很容易做的。

后期的计划:

现在css helper返回的是一个数组,可以考虑join成字符串,去掉多余的空白符,然后实现css to object和object to css。然后再加上plugin机制,使得整个系统更加灵活多变。

说着说着,好像实现了一个简版的postCss。

现在styled-components是有增加babel plugin的计划来打通postCss和styled-compoent,期待。

起因

今天尝试使用webpck的import()来做代码分割。

代码类似如下:

import('./nice-scroll').then(init => init(dom))

结果报错:

ERROR in ./js/utils/auto-set-height.js
Module build failed: SyntaxError: ‘import’ and ‘export’ may only appear at the top level (20:8)

分析

在package.json里面确认了一下,我用的webpack版本是^3.5.4,这个是一定支持import()方法的,那么问题应该就出在babel上了。

先截取我的babel-loader的部分配置:

use: {
    loader: 'babel-loader',
    options: {
        // 不采用.babelrc的配置
        "babelrc": false,
        "presets": [
            ["react"],
            ["es2015"]
        ],
        "plugins": [
            "transform-object-rest-spread",
            "transform-class-properties"
        ]
    }
}

一开始我的猜想是plugin es2015里面的transform-es2015-modules-commonjs先于webpack处理了代码,所以报错。

找了一下禁用的方法,改配置如下:

use: {
    loader: 'babel-loader',
    options: {
        // 不采用.babelrc的配置
        "babelrc": false,
        "presets": [
            ["react"],
            ["es2015", {module: false}]
        ],
        "plugins": [
            "transform-object-rest-spread",
            "transform-class-properties"
        ]
    }
}

还是不行。

后面一直各种关键词的搜索,偶然在github上面找到这个问题Dynamic `import()` crashing in `ModuleConcatenationPlugin`的一个回答:

Nope; babel will always process the code before webpack ever sees it; unless for some reason the file itself is being excluded from being processed by babel-loader.
This error is unrelated to babel; it’s a webpack issue.

这个回答,里面说到在webpack面对的代码都是babel处理过的,这个让我坚信问题还是在babel这块,继续搜索。

意外找到这个这篇文章:代码分离 – 使用import()。里面说到了一个插件:syntax-dynamic-import

Babel官方关于这个插件的描述是:

Syntax only
This plugin only allows Babel to parse this syntax. If you want to transform it then see dynamic-import-webpack or dynamic-import-node.

显然这是一个语法解析的插件,使得babel能够理解dynamic import的语法。再联系上面的报错信息里面的SyntaxError,结果有点明显了。

解决

npm install --save-dev babel-plugin-syntax-dynamic-import

然后调整babel-loader配置如下:

use: {
    loader: 'babel-loader',
    options: {
        // 不采用.babelrc的配置
        "babelrc": false,
        "presets": [
            ["react"],
            ["es2015", { "modules": false }]
        ],
        "plugins": [
            "syntax-dynamic-import",
            "transform-object-rest-spread",
            "transform-class-properties"
        ]
    }
}

重启webpack,顺利通过编译!!!

最近一直有事没看博客,直到昨晚Google给发了封邮件,说检测到服务器上的错误呈现增加趋势

乘今天有点空闲,上了服务器看看。

发现wordpress和mysql这两个docker container都是正常运行,没挂掉。

按照以前的套路

docker restart mysql
docker restart wordpress

结果不行,看来不是小问题了。

针对这两个docker分别用docker logs命令查看了日志,mysql的没发现什么比较明显的错误报告,但是wordpress里面就满屏都是了

Warning: mysqli::mysqli(): (HY000/2002): Connection refused in - on line 10

MySQL Connection Error: (2002) Connection refused

上网找了半天,发现有人说可以通过重启docker来修复

试了一下,我的服务器是CentOS的,命令如下:

service docker stop
service docker start

重启完了之后,发现wordpress的container停掉了,用docker start wordpress给启用了

再次刷新网站,恢复正常了!

起因

18+1大,因为作死的还一直开着我的ss,然后连同着博客一起被墙了。

想上服务器做一些修改,试过各种ssh的代理方法,就是不行。在调用这些手段的时候,突然发现有人提到VPS可以做到月费5$,有点惊呆了。https://www.diycode.cc/topics/738

仔细想想,自己现在这个EC2,每个月开销已经到$10了,其实自己也没怎么用,主要就是挂博客和放代理,那还不如索性乘这个机会迁到更符合实际需求单VPS上。

目标

这次迁站,主要两件事情,一个是搭建ss-server,一个就是wordpress和mysql数据的迁移。

wordpress和mysql都是基于docker部署的,算是其中的一个小难点

先在vultr.com上面创建了instance,等安装好了之后,用给的帐号密码登录指定服务器,成功。

ss-server搭建

使用上面说的文章梨提供的一键安装脚本,ss-server顺利安装成功。

在本地建好配置文件(config.json)之后,使用pip3 install shadowsocks快速安装了客户端,然后ss-local -c config.json启动

可是一直连不上,ss-local里面的日志都是connecting

在网上找了一圈之后,发现可能是本机没开放对应端口,使用命令:

 firewall-cmd --zone=public --list-ports

查看了一下现有开放的端口,发现ss默认使用的8989是开放的,不知道是为啥还被禁。

最后的解决方式是:kill掉ss-server, 修改配置文件,更换端口到8083(随便选的),然后启动ss-server。在防火墙这块:

firewall-cmd --zone=public --add-port=8083/tcp
firewall-cmd --reload

再尝试,可以顺利链接了。

代理配置好之后,我本地也是能方便的使用Google,为接下来查看怎么迁移Docker做了准备。

上面关于firewall-cmd相关的知识,是来自: http://www.111cn.net/sys/CentOS/103509.htm

迁移站点

因为vultr准备的这个instance是一个最小的CentOS安装包,所以得自己使用yum安装了vim和nginx:

yum install vim
yum install nginx

安装好之后,直接IP访问,发现不可以,一看还是防火墙的问题,解决方法跟上面一样:

firewall-cmd --zone=public --add-port=80/tcp
firewall-cmd --reload

关于docker的迁移,我一开始主要参照这篇文章:Docker 容器迁移,可真按照这个完成之后,得到的是一个完全新的博客,能顺利运行,但是之前的数据都没了。

无奈之下,我又单独去了解了一下Docker Mysql的数据迁移,主要参照的这篇文章:Docker Mysql容器间数据简单迁移。这次就靠谱多了,同时还学会了怎么在主机和docker之间互相拷贝文件。

在顺利从docker里面导出sql文件到主机后,再使用scp命令在vultr和aws之间传递文件(忘记说了,登录上vultr之后,我成功的基于vultr的主机ssh上了aws的主机,机智)

scp命令如果遇到错误: Permission denied,可以参照这篇文章: ssh使用scp: /目录: Permission denied

不过这篇文章在目标数据库恢复数据这块,说的不够清楚,用文章里面的方法始终没办法在mysql里面出现一个叫wordpress的数据库。

继续google,找到了一个在sql文件导入导出方面比较专业的文章: linux命令行下导出导入.sql文件,原来导入之前是得先新建对应的数据库,在进入数据库之后,再执行source data.sql就能顺利的导入了。

数据库导入之后,再查看网站,已经能正确展示了,不过一些图片资源报了404,看了看资源路径,在wp-content目录里面。

docker exec进入wordpress所在的docker里面,看了一下wp-content目录,空的,看来跟mysql一样,也是迁移的有问题的。

用了之前从mysql的docker里面导出sql文件的方法,从旧网站的docker里面把wp-content目录给导出来,然后scp下载到vultr主机,再复制到新网站的docker里面(好累),资源顺利加载。

不过一些插件报错了:**Call to undefined function wp_get_upload_dir()
** 
这种错误,我是不想去debug了,很明显,光拷贝wp-content是不行的,索性,那就完全贝把。把docker里面的/var/www/html/完全拷贝出来,再覆盖现有的。覆盖的时候,是打算

mv html hml-bk
tar zxf html.tar.gz

这样来做的,不过不让直接重命名现有的html目录,提示 mv: cannot move ‘html’ to ‘html-bk’: Device or resource busy

那就只好先新建个文件把旧网站的html目录放进去,然后一个个文件夹的替换。替换完成之后。

运气不错,现在新网站顺利运行了。

总结

这次的站点迁移,跟想像的优雅略有差别,以为用上Docker之后,这种迁移会是很轻松的一件事情,却没想到折腾了这么久。

应该是我自己在Docker这块了解的还是太少吧,等以后有时间再好好看看这块。

对了,顺便放上我的Vultr的推广链接:https://www.vultr.com/?ref=7256603,有需要VPS的朋友可以考虑一下点我的链接去注册哈,在此谢谢了

最近项目需要,决定入手React Native。

第一步要做的肯定就是能在本机顺利的把官网的Quick Start 跑出来.

先说一下本机的环境:

  • 系统: macOS Sierra 10.12.5
  • NodeJs: 8.1.0
  • Xcode: 8.3.3 (8E3004b)
  • react-native-cli: 2.0.1
  • react-native: 0.45.1

构建过程

官网是用的brew来安装node,但是我这边根据日常开发的需要,会在多个Node版本之间切换,所以我是用的nvm

接下来是watchmanreact-native-cli,都是按照教程来一步一步安装

brew install watchman
npm install -g react-native-cli

然后是初始化和启动项目

react-native init AwesomeProject
cd AwesomeProject
react-native run-ios

然后就出错了:“:CFBundleIdentifier”, Does Not Exist

debug过程

先是在github上面找到了一个同样错误的issue,发现有这个错误的人很多,给的解释也不少

端口占用

端口8081被别的程序占用了

lsof -n -i4TCP:8081
node    7601 CXI624   23u  IPv6 0xc6b249599e5f1169      0t0  TCP *:sunproxyadmin (LISTEN)
kill - 9 7601 

这里面的7601只是一种情况,具体看真实占用这个端口的是什么程序。

我这边可以确定不是,所以pass

重买一台mac解决了

这… 没钱,pass

react-native upgrade

更新react-native到最新版

我这刚刚下载的,肯定最新版没毛病

sudo

sudo react-native run-ios

这个我是不太想用的,因为用了之后,以后基本都得用sudo,比较麻烦,等确认没别的解决方式再考虑

尝试调整Xcode的配置:

  1. Go to File -> Project settings
  2. Click the Advanced button
  3. Select “Custom” and select “Relative to Workspace” in the pull down
  4. click done, done

按照上面的做了一下,然后直接基于Xcode来运行程序

结果又出了新的问题

double-conversion.build failed

然后是在这个issue下找到了问题的解决方式:

rm -r ~/.rncache
rm -r <your-project>/node_modules/react-native/third_party

不过最终的编译还是失败。

网络问题

最后在刚刚的那个issue里面找到了那么一段话:

The problem is when building react-native for iOS, the process tries to download some files (using curl) into /.rncache. Specifically, they are “boost1630.tar.gz, double-conversion-1.1.5.tar.gz folly-2016.09.26.00.tar.gz, glog-0.3.4.tar.gz”. The sources of them (at least some of them) are from AWS S3, which aren’t accessible in some regions (I’m from China). And that caused the problem.
By using a proxy server and setting the http_proxy and https_proxy environment variables, curl will go through the proxy and the build should succeed.
This is not a problem with React Native. This issue can be closed now.

是说的在build的时候,会从AWS S3里面下载一些资源,但是在咱们国内,很多时候是访问不到这些资源的,也自然就导致了一些文件的缺损。

作者在后面给的方式是给curl配置代理,具体看https://stackoverflow.com/questions/9445489/linux-curl-command-with-proxy

不过我这边用了另外一种方式,把我的蓝灯设置为代理全部流量,接着

rm -r ~/.rncache
rm -r <your-project>/node_modules/

把所有资源删掉,然后重新编译。

最后,成功编译通过!!

字符:5706
平移阅读时长:7分钟

今天在写一个小脚本来把自己写的一个html批量复制并在保持后缀不变的情况下随机命名。

先打算实现文件的拷贝。

Google了一下,发现了一个shutil的库(这个库的名字纠结了发音老半天,后面才理解是sh和util的结合,也就是shell-util)

这个库提供了与shell命令操作文件等同的所有方法:copy, copytree, rmtree, move

代码实现

有了这个库,代码很容易就实现了:

import shutil

src = 'sa-jumper.html'

randomStr = 'ad239sdfasdl2asdf9adsfi23dfladf'
strLen = len(randomStr)

shutil.copy(src, 'tmp/aa.html')

关于随机的部分,就只是提前做了准备。先用的shutil.copy来测试文件拷贝功能。

然后我先用的python3.6执行了这个文件,结果报错:

Traceback (most recent call last):
  File "copy.py", line 2, in <module>
    import shutil
  File "/usr/local/Cellar/python3/3.6.0_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/shutil.py", line 13, in <module>
    import tarfile
  File "/usr/local/Cellar/python3/3.6.0_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/tarfile.py", line 49, in <module>
    import copy
  File "/Library/WebServer/Documents/xxx/copy.py", line 13, in <module>
    shutil.copy(src, 'tmp/aa.html')
AttributeError: module 'shutil' has no attribute 'copy'

后面改用python2.7执行,顺利通过。

debug

打开Python的解释器提示符,执行上述代码,也是成功的。

这下我就没辙了,感觉个人的Python经验只能到此为止,启用Google大法。

在StackOverflow的一篇文章上,发现有人遇到类似的问题,链接如下:

https://stackoverflow.com/questions/22131139/attributeerror-module-object-has-no-attribute-x

在上述链接里面,最终的错误是因为提问的人,python脚本的名称是org.py跟系统的一个文件重名,导致了一个错误的引用。

所以很自然的,我就联想到了我的文件,名字是copy.py,而看上面的错误信息里面,是用一个import copy,估计也是同样错误的把我的脚本当作官方库给错误引用了。

修复

把文件名改成了copy-file.py,再用python3.6执行,一切顺利。

分析

从这个错误里面想到几个问题:

  • 为啥跟官方库里面的文件同名会导致引用错误
  • 为啥python2.7没报错
  • 如何避免这样的问题

关于引用错误

重新翻了一下之前看的《Python简明教程》

关于模块的索引

如果它不是一个已编译好的模块,即用 Python 编写的模块,那么 Python 解释器将从它的 sys.path 变量所提供的目录中进行搜索。如果找到了对应模块,则该模块中的语句将在开始运行,并能够为你所使用。在这里需要注意的是,初始化工作只需在我们第一次导入模块时完成。

关于索引的顺序

sys.path 内包含了导入模块的字典名称列表。你能观察到 sys.path 的第一段字符串是空的——这一空字符串代表当前目录也是 sys.path 的一部分,它与 PYTHONPATH 环境变量等同。这意味着你可以直接导入位于当前目录的模块。否则,你必须将你的模块放置在 sys.path 内所列出的目录中。

从这里可以看到,执行的脚本所在的目录也是在python搜索模块的范围内的,而且是第一位,也就是会优先搜索的,这也就是为啥同名会导致引用错误的原因了。

为啥2.7没错

按照上面StackOverflow那边文章的分析过程,我分别看了python3.6和python2.7版本里面shutil.pytarfile.py源码,发现shutil.py都引用了tarfile.py,而tarfile.py也都import copy

那就不可能是2.7里面没有引用copy了。

然后我又仔细的看了一下各自的实现,终于发现了问题,在2.7里面,不是在初始化的时候就import了tarfile,而是在一个_make_tarball 的函数里面。

我在这个函数的import tarfile前面加了一个print('import tarfile'),再去执行刚刚的copy.py(这块为了测试方便,把copy-file.py复制了一份重命名为copy.py)。

发现没有print任何内容。

而在3.6里面,是在文件头部,一开始就import了tarfile。这也就解释了为啥2.7没问题,3.6有问题了。同时也说明:python的import是按需加载的。写在函数里面的import,只有在函数被调用的时候才会真实的去import

为了反证这个推断是否有效,我找到了2.7里面shutil的一个会触发import tarfile的函数make_archive

使用了官方提供的example:

import shutil
import os
archive_name = os.path.expanduser(os.path.join('~', 'myarchive'))
root_dir = os.path.expanduser(os.path.join('~', '.ssh'))
shutil.make_archive(archive_name, 'gztar', root_dir)

执行python copy.py(我的环境里面,默认是2.7)

果然报错了:

import tarfile
import tarfile
Traceback (most recent call last):
  File "copy.py", line 20, in <module>
    shutil.make_archive(archive_name, 'gztar', root_dir)
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 561, in make_archive
    filename = func(base_name, base_dir, **kwargs)
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 376, in _make_tarball
    import tarfile  # late import so Python build itself doesn't break
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/tarfile.py", line 52, in <module>
    import copy
  File "/Library/WebServer/Documents/xxx/copy.py", line 20, in <module>
    shutil.make_archive(archive_name, 'gztar', root_dir)
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 561, in make_archive
    filename = func(base_name, base_dir, **kwargs)
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 394, in _make_tarball
    tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
AttributeError: 'module' object has no attribute 'open'

然后我尝试把文件名修改为copy-file.py,再执行,顺利通过。

看来2.7和3.6都是一样的索引模块的方式,只是因为shutil模块里面引用tarfile的方式不一样,才造成一开始的执行结果不一样。

如何避免这样的问题

我还是一个python新手,不知道python在命名的时候有没有提供一些合理的规范,比如不能使用的单词。

然后我又一次看了《简明Python教程》,里面关于标识符命名这块:

量是标识符的一个例子。标识符(Identifiers) 是为 某些东西 提供的给定名称。在你命名标识符时,你需要遵守以下规则:
• 第一个字符必须是字母表中的字母(大写 ASCII 字符或小写 ASCII 字符或 Unicode 字符)或下划线()。
• 标识符的其它部分可以由字符(大写 ASCII 字符或小写 ASCII 字符或 Unicode 字符)、下划线(
)、数字(0~9)组成。
• 标识符名称区分大小写。例如,myname 和 myName 并不等同。要注意到前者是小写字母 n 而后者是大写字母 N。
• 有效 的标识符名称可以是 i 或 name_2_3 ,无效 的标识符名称可能是 2things,this is spaced out,my-name 和 >a1b2_c3。

我想,如果我以非这个规范的方式定义文件名是不是就可以了。

然而用类似import copy-file的方式引用自己的模块,会报错,SyntaxError

也有看到有相关的解决方式:

  • python2.7 execfile('foo-bar.py')
  • python3.x exec(open(fn).read())

不过这些方法感觉都不是很自然,还是放弃这样的尝试了。

只能使用另一种方式了,就是统一文件名的前缀。如果你的项目是foo,那么可以给所有项目的文件定义以这个为前缀的名称: foo__copy.py, foo_shutil.py等等。

这样只要foo是一个不会和常见的库的前缀重名的字符串,就能保证项目里面不会再发生这样的名称冲突了。

参考资料

突然发现,我只是想实现一个文件拷贝,咋就绕了这么远呢?

很多程序员,包括我,会在写代码的时候,习惯性的用TODO, FIXME, HACK等作为一些注释的前缀。这样方便自己和后来者预先知道相关注释和代码的状况

不过这样的备注只有是以后恰巧看到这段代码的时候才能看到,没法在一次发版或者空闲的时候,有一个直观的界面能看到这个项目里面所有这样的关键词。

这个时候一个简单的自定义命令兴许能帮到你:

command Todo Ack! 'TODO\|FIXME'

效果如下:

不过要想使这条命令生效,需要一些环境准备工作

Ack

首先我们需要安装Ack或者Ag,这两个都是命令行下的一个全局搜索工具,比系统自带的grep要快很多的。

这里的安装以Ag为例,因为这个是基于C来重写的Ack并新增了很多特性,效率也是比Ack快了很多的。

# OSX
brew install the_silver_searcher
# Archlinux
pacman -S the_silver_searcher
# Ubuntu
apt-get install silversearcher-ag

在安装完成之后,就可以在shell里面测试一下

ag TODO ./

Ack.vim

Ack.vim是一款插件,用来方便在Vim里面使用Ack/Ag, 然后通过Quickfix来提供搜索结果

不过这款插件,默认是使用的Ack作为搜索引擎,为了能用上我们刚刚安装的Ag,需要在~/.vimrc里面作一些配置:

if executable('ag')
  let g:ackprg = 'ag --vimgrep'
endif

这段配置是引用的官方仓库的Readme

不过也看很多人是用的

if executable('ag')
  let g:ackprg = 'ag --nogroup --nocolor --column'
endif

本人测试之后,发现二者并没有什么区别。可能对于Ag来说 --vimgrep是等同于--nogroup --nocolor --column 的。

这个时候在Vim的命令行里面输入

:Ack! TODO 

是可以实现上图所示效果的,而且还可以通过

:AckAdd FIXME

这样的方式来附加其它要查看的。

不过这样的方式,命令还是有点长。所以配置一个alias来替代上面的命令,是一个比较符合懒人的想法

~/.vimrc里面新增

command Todo Ack! 'TODO\|FIXME'

然后在Vim的命令行里面输入:

:Todo

就能实现列出当前项目里面的所有TODO,FIXME了,而且可以根据自己的需要来扩展这样的关键字,像我就扩展为如下代码:

command Todo Ack! 'TODO|FIXME|CHANGED|BUG|HACK'
command Debug Ack! 'NOTE|INFO|IDEA'

区分了Todo和Debug两种功能。

高亮

Vim里面会自动的高亮TODOFIXME,但是不会高亮CHANGED这样的关键字,所以需要手动的来指定一下,在~/.vimrc里面添加如下配置:

if has("autocmd")
  " Highlight TODO, FIXME, NOTE, etc.
  if v:version > 701
    autocmd Syntax * call matchadd('Todo',  '\W\zs\(TODO\|FIXME\|CHANGED\|BUG\|HACK\)')
    autocmd Syntax * call matchadd('Debug', '\W\zs\(NOTE\|INFO\|IDEA\)')
  endif
endif

上面这段代码是摘抄的,具体的功能就是匹配TODO等关键字,然后加上语法高亮。

综上,一个简单好用的TODO管理器就已经完成了。

参考资料

上文里面的绝大部分代码都是从以下资料里面摘抄拼凑的,对于Vim,我还只是一个懵懵懂懂的拿来主义者。

  1. https://coderwall.com/p/prfnnw/vim-to-do-list
  2. https://stackoverflow.com/questions/6577579/task-tags-in-vim
  3. http://harttle.com/2015/12/21/vim-search.html
  4. https://github.com/mileszs/ack.vim
  5. http://betterthanack.com/