¶ M$ DLL Hell

2010-10-09 01:00

  • Windows编程革命简史 | 酷壳 - CoolShell.cn
  • 忒给力了! 想起: 新同事 | zonble’s promptbook
    • http://zonble.net/archives/2007_05/937.php

      忍不住给个歌词吧,谁人有给力的曲,我们配首 微软革命之歌 :

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      M$ DLL Hell
       
      起初,我说起初
      那儿只有 Windows API 和 DLL Hell
      为了消灭 DLL Hell
      然后,DDE 出现了
       
      但是,他们说DDE 有重大缺陷:
      丫不是俺写的!
      于是 OLE 出现了
      除了名字和 DDE 不同
      其它其实真的一样的哈!
       
      后来,他们信了 C++
      为嘛?!你懂的,你真得懂的!
      于是 OLE 通过 MFC 进化成 COM
      为嘛?!你懂的,你真得懂的!
       
      但是,他们说 MFC 有重大缺陷:
      丫不是俺写的!
      于是 ATL 出现了
      除了名字和 MFC 不同
      其它其实真的一样的哈!
       
      然后 COM 变身成 ActiveX
      为嘛?!你懂的,你真得懂的!
      于是 DLL Hell 再次被消灭鸟
      为嘛?!你懂的,你真得懂的!
       
      同时,他们说 JAVA 有重大缺陷:
      丫不是俺写的!
      于是 ActiveJ 出现了
      除了名字和 JAVA 不同
      其它其实真的一样的哈!
       
      然后 COM 变身成 MTS
      为嘛?!你懂的,你真得懂的!
      于是 DLL Hell 再次被消灭鸟
      为嘛?!你懂的,你真得懂的!
       
      现在,他们说 Internet 有重大缺陷:
      你懂的,你真得懂的!
      于是 .NET 出现了
      除了名字和 Internet 不同
      其它其实真的一样的哈!
       
      然后 C++ 变身成 C#
      为嘛?!你懂的,你真得懂的!
      于是 DLL Hell 再次被消灭鸟
      为嘛?!你懂的,你真得懂的!
       
      你懂的!
      你真得要懂的!
      为嘛?! DLL Hell 总在被消灭?!
      为嘛?!
      因为 M$ 在革命!


  • t2t渲染:: 2010-10-09 02:21:39
  • 动力源自::txt2tags

§ 写于: Sat, 09 Oct 2010 | 永久链接;源文: rdf ,rss ,raw | 分类: /easy/music §
[MailMe] [Print] Creative Commons License

作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ rst2s5

2010-09-18 23:23

1. 缘起

最近讲演频繁:

  1. 100820: IntroPy - Woodpecker Wiki for CPUG
  2. 100826: TDD4Py - Woodpecker Wiki for CPUG
  3. 100918: Software Freedom Day 2010 - SZLUG
  4. ...

    俺在 S5: A Simple Standards-Based Slide Show System 中使用高桥流,幻灯页码轻易超过100! 即使有 Leo 帮助也感觉吃不住, 主要原因是直接写 HTML 太多标签干扰思維了...

    其实早就知道有 rst->s5 的工具:

2. 运用

文档的确不多,中文的就找到上述 Inet6 兄的,但是和谐后,也无法方便的直接咨询

仔细看了哈文档,发现的确不复杂;

之于俺,只是要引用原先的样式,只要:

1
2
3
4
5
6
7
8
9
#   先随便生成个默认工程
rst2s5.py --theme <base-theme> <doc>.txt <doc>.html
#   再复制原先样式过来
ui/<base-theme> to ui/<new-theme>.
#   然后聲明使用新样式就好:
rst2s5.py --theme-url ui/<new-theme> <doc>.txt <doc>.html
 
#   俺在对应目录中的实际命令就是:
python rst2s5-Pygments.py  --current-slide --theme-url=ui/freedom index.rst index.html
  • --current-slide 是要求生成页码的开关参数
  • rst2s5 --help 居然看到有几十种参数,才发现
  • Docutils Front-End Tools 实在是个丰富的工具集!
  • rst2s5-Pygments.py 是定制过的支持 Pygments 语法颜色的 rst2s5 转换脚本!

最后发布时的 s5演示目录分布是:

1
2
3
4
5
6
7
8
9
+-- i                   资源目录(图片什么的...)
|-- index.html          S5
|-- index.rst           rst撰写原文稿
|-- rst2s5-Pygments.py  定制支持 Pygments 语法颜色渲染的s5生成脚本
|-- s5defs.txt          rst2s5引用的 s5 配置文件
`-- ui                  样式入口
    +-- default         默认样式
    `-- freedom         定制样式

2.1. 初步

和S5 幻灯组织是完全对等的撰写:

  • HTML 首页:
    1
    2
    3
    <div class="slide" id="slide0">
    Blalala
    </div>
  • rST首页:
    1
    2
    3
    ==================================================================
    我的工具箱
    ==================================================================
  • HTML幻灯页:
    1
    2
    3
    4
    5
    6
    7
    <div class="slide">
    <h1>标题</h1>
    内容
        <div class="handout">
        隐藏提示
        </div>
    </div>
  • rST幻灯页:
    1
    2
    3
    4
    5
    6
    7
    8
    标题
    =========
     
    .. container:: handout
     
       隐藏提示
     
    内容

唯一注意:

2.2. rst

html->rst 的撰写只有更加 WYTIWYG 很容易习惯;

参考: http://docutils.sourceforge.net/docs/user/rst/quickref.html Quick

主要得习惯的只有:

  1. 标题和 MoinMoin 语法不同使用 ==================独立的标题引用行来聲明第几层的标题
    • 建议顺序是:= - ` : ' " ~ ^ _ * + # < >
    • 一般就用到前4个:
      1
      2
      3
      4
      5
      6
      7
      8
      大标题
      ======
      小标题
      ------
      次标题
      ''''''
      小节标
      """"""
  2. 列表项更加简单可以使用 + 以及 - 相比* 要少按个键 ;-)
  3. 链接更加灵活了:
    • 就地链接: 是也乎,是也乎`显示文字 <http://foo.org>`__ 是也乎
    • 匿名链接:
      1
      2
      3
      是也乎,是也乎`显示文字`__ 是也乎
       
      • 和行内链接聲明最近的独立URL 定义进行自动匹配
    • 全局链接:
      1
      2
      3
      4
      5
      .. _Zoom.Quiet:
       
      ...
      是也乎,是也乎`Zoom.Quiet`_ 是也乎
  4. 就地图片:
    1
    2
    3
    4
    5
    .. image:: pix/Takahashi-method.jpg
        :align: center
        :scale: 150 %
        :alt: 高橋流
    • 这个 target 就是链接! 文档中没有例子,找了半天...
  5. 全局图片:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    .. |Takahashi| image:: pix/Takahashi-method.jpg
        :align: top
        :scale: 150 %
        :alt: 高橋流
     
    ...
     
    任意文字中 |Takahashi| 嵌入图片...
    • align 这时,就是图片和文字流的行内相对位置,而不是图片在整个页面的排版,所以,只能有"top", "middle", "bottom"
    • 其实不用专门记忆,每次渲染时, rst2s5 会进行检查,比如说:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      $ python rst2s5-Pygments.py  --current-slide --theme-url=ui/freedom index.rst index.html
      index.rst:34: (ERROR/3) Error in "image" directive:
      "center" is not a valid value for the "align" option within a substitution definition. 
      Valid values for "align" are: "top", "middle", "bottom".
      index.rst:34: (WARNING/2) Substitution definition "S5icon" empty or invalid.
       
      .. |S5icon| image:: pix/S5icon.GIF
          :align: center
          :scale: 100 %
          :target: http://www.meyerweb.com/eric/tools/s5/
      index.rst:1284: (ERROR/3) Undefined substitution referenced: "S5icon".

嗯嗯嗯, 使用 rst 进行 s5 撰写,完全是脚本组织的感觉了...连输出时的交互 error 通告都是 Python 味的..

2.3. 定制

比较囧是只有俺习惯并坚持的样式很搞:

默认的样式排版:
  • 和 s5 的默认样式100% 一致!

定制的Freedome样式:
主要的设计:
对应的rst2s5 效果:

对应的设计:
  • A: 表格的作者和版权聲明
    1
    2
    3
    4
    5
    6
    7
    8
    .. footer::
       .. class:: borderless
     
       =============  ====================================
       `Zoom.Quiet`_  |cc-byncsa15|
       -------------  ------------------------------------
       v10.09.16      推荐用 `FireFox`_ 获得最佳游览效果
       =============  ====================================
  • B: 页码/页数 提示
  • C: 当页内容提示
    1
    2
    3
    4
    5
    6
    7
    8
    标题
    =========
     
    .. container:: handout
     
       俺->工具->持续完善 |zqeye|
     
    ...
    • 嗯嗯嗯,当然的得配合CSS:
    • 注释掉默认样式ui/<theme>/s5-core.css 中的
      1
      .handout {display: none;}
    • 在专用CSSui/<theme>/takahashi.css中定制
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      div.handout{
          float: right;
          top:0;
          margin-top: -85px;
          margin-right: -30px;
          font-size: 16px; font-style : italic ;
          color: #385775;
      }
      div.handout a{
          color: #385775;
      }
       
      div.handout a:hover{
          color: orange;
          font-style : bold ;
      }
    • 还得在默认的样式调配文件ui/<theme>/slides.css中引入自个儿定制的:
      1
      2
      3
      4
      5
      @import url(s5-core.css); /* required to make the slide show run at all */
      @import url(framing.css); /* sets basic placement and size of slide components */
      @import url(pretty.css);  /* stuff that makes the slides look better than blah */
      @import url(takahashi.css);  /* stuff that makes the takahashi style slides */
      @import url(rst2s5.css);  /* stuff that support slides gen. by rst2s5 */
  • D: 专用高桥流样式, 在s5专用子聲明文件:s5defs.txt 中追加俺习惯的CSS类名:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    .. Text Sizes
       ==========
     
    .. role:: huge
    .. role:: big
    .. role:: small
    .. role:: tiny
    .. role:: takahashi0
    .. role:: takahashi1
    .. role:: takahashi2
    ...
成果对比:

3. 时间帐单

小结这次迁移的时间消耗

  1. 0.5h 试用 rst2s5
  2. 0.5h 接入样式
  3. 0.5h 重构Leo 模式
  4. 2h 定制基于 rst2s5 的样式
  5. 3h 逐一页面重写
  6. 1h 修订发布

    共计 7.5h
    • 嗯嗯嗯,主要是在重过过程中,反复调整了对应的 CSS 样式...
    • 好在一切可以快速复用到其它幻灯中了...

  • t2t渲染:: 2010-10-09 02:21:37
  • 动力源自::txt2tags

§ 写于: Sat, 18 Sep 2010 | 永久链接;源文: rdf ,rss ,raw | 分类: /utility/py4str/StructuredText §
[MailMe] [Print] Creative Commons License

作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ 然何买书不读?

2010-08-06 11:01

1. 问

  • 发觉近来自个儿买的书被读的越来越少
  • 好容易得空也是积极看网络中可以免费获得的

    何也?

2. 答

  • 买了就是自个儿的了,什么时候读就不重要了
  • 网络中免费得的也都是有网络费用的,但是相对低很多,很占便宜的!在没有和谐前,多看哪...

    然也?

3. 解

时间的投入产出,是自个儿的控制和选择...

  • 但是,读书的体验和氛围是有技巧营造的,就看自个儿是否愿意了...

  • t2t渲染:: 2010-10-09 02:21:39
  • 动力源自::txt2tags

§ 写于: Fri, 06 Aug 2010 | 永久链接;源文: rdf ,rss ,raw | 分类: /mind §
[MailMe] [Print] Creative Commons License

作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ MoinMoin插件hacking

2010-07-30 15:00

1. 需求

GraphVizForMoin 插件部署到MoinMoin 中之后,很爽直!

参考: 在维基中使用 Graphviz~ 啄木鸟中的效果

可以说,解决了在维基中无法快速表达思维导图的问题:

  • 以往都是使用 FreeMind 绘制后截屏附件上来
  • 或是使用插件 ParserMarket/FreeMind - MoinMoin 将文件使用Flash 控件就地发布出来~中文一直是个问题

    但是,使用 Graphviz 的dot 图形脚本在维基中书写图谱一直以来残念的问题是无法输出可点击的有热区的导图!

1.1. Hacking

思路:
  • hacked MoinGraphViz 令其使用-Tcmapx -o **.mp命令,输出热区定义
  • hacked MoinMoin 相关脚本令输出到HTML 的图片认识可能的热区定义
fxied:
  • path/2/moin运行实例/data/plugin/parser/MoinGraphViz/main.py 是插件的主体
  • 很直白,快速就定位了具体代码进行了修订

diff:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Index: tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py
===================================================================
--- tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py (revision 16946)
+++ tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py (revision 16975)
@@ -56,4 +56,5 @@
         p = request.formatter.page
         self.renderer = Renderer(tool, targetdir=p.getPagePath('attachments'), encoding=config.charset)
+        self.attapath = p.getPagePath('attachments')
 
     def format(self, formatter):
@@ -61,5 +62,10 @@
         ##w('<div style="border:3px ridge gray; padding:5px; width:95%; overflow:auto">')
         s = self.renderer.render(self.raw)
+        imgname = os.path.basename(s)
+        #s = wiki2html(self.request, '{{attachment:%s}}' % os.path.basename(s))
         s = wiki2html(self.request, '{{attachment:%s}}' % os.path.basename(s))
+        #   100728 Zoom.Quiet fixed for include URL hotarea map define
+        pfImgMap = "%s/%s.map"%(self.attapath,imgname)
+        s += fread(pfImgMap)
         print '[TRACE] attachment URL:', s
         w(s)
@@ -182,5 +193,7 @@
 
 def renderGraphImage(tool, format, imagefilename, dotfilename):
-    cmd = '%(tool)s -T%(format)s -o"%(imagefilename)s" "%(dotfilename)s"' % locals()
+    #100728 Zoom.Quiet fixed for export URL hotarea map export
+    cmd = '%(tool)s -T%(format)s -o"%(imagefilename)s" -Tcmapx -o "%(imagefilename)s.map" "%(dotfilename)s"' % locals()
+    #cmd = '%(tool)s -T%(format)s -o"%(imagefilename)s" "%(dotfilename)s"' % locals()
     print '[TRACE] executing:', cmd
     os.system(cmd
html 输出:

1
2
3
4
5
6
7
8
<img alt="graphviz-hostLegendG-a58ce04d28b92b59230a72964c27a9f8fc867de5.png"
class="attachment"
src="/moin/KupHostsMapping/MapLegend?action=AttachFile&amp;do=get&amp;target=graphviz-hostLegendG-a58ce04d28b92b59230a72964c27a9f8fc867de5.png"
title="graphviz-hostLegendG-a58ce04d28b92b59230a72964c27a9f8fc867de5.png" />
<map id="hostLegendG" name="hostLegendG">
<area shape="rect" href="http://wiki.s.kingsoft.net/moin/KupHosts" title="普配主机" alt="" coords="101,36,173,63"/>
<area shape="rect" href="http://wiki.s.kingsoft.net/moin/KupHosts" title="高配主机" alt="" coords="197,36,269,63"/>
</map>

注意: 发现,插件是直接使用{{attachment:导图图片名}} 标准的图片附件形式来发布的!
  • 然而,HTML 中要想啓用热区图,至少要有专用属性的对应:
    <img usemap="#俺的ImgMap" src="..."/>
    <map id="俺的ImgMap" name="俺的ImgMap">
    <area shape="rect" href="..." title="普配主机" alt="" coords="101,36,173,63"/>
    ...
    </map>
    
    • 在dot 输出的map 数据中,id/name 就是``digraph G { `` 第一行的那个G,可以任意命名,当然最好是E文
  • 所以,就得找到方法来让 MoinMoin 对附件图片追加usemap属性

    找哈找,幸好有 ack-grep 快速从一堆脚本中定位到靠谱的代码段:
path/2/python2.5/site-packages/MoinMoin/formatter/text_html.py
...
    def attachment_image(self, url, **kw):
        ...
        if exists:
            ...
            if not 'alt' in kw:
                kw['alt'] = kw['title']
            #   100729 Zoom.Quiet fixed for support imagemap for Graphviz
            kw['usemap'] = "#%s"%kw['alt']
            return self.image(**kw)
        ...

追加一行就好...

1.2. jQuery

虽然目标完成了,但是心里总感觉不好:

  • MoinMoin 本身的脚本被hacking 了,就等于,以后升級,迁移时,都要维护这一hacking
  • 很不 Pythonic 哪...

    怎么样脱离 MoinMoin 系统本身来给附件图片追加usemap 属性?
  • 答案,自然是的 Ajax 哪
  • jQuery 就是为这类快速夹塞儿式行为诞生的哪...
部署jQuery:
  • 这是样式的事儿,所以:

    path/2/moin实例/
    +-- data
        +-- plugin
            +-- theme
                +-- 你的样式定义脚本
                +-- woodpecker.py ~ 俺用的
            def footer(self, d, **keywords):
                ... # 追加
                u'<!-- Finally, to loading jQuery Ajax Lib. -->',
                u'<script src="/wiki/common/js/jquery-1.4.2.min.js" type="text/javascript"></script>',
                u'<script src="/wiki/common/js/jquery-graphviz-map.js" type="text/javascript"></script>',
    
    +-- htdoc
        +-- common
            +-- js
                +-- jquery-1.4.2.min.js ~ 官方运营用压缩版本
                +-- jquery-graphviz-map.js  ~ 动态行为定义用
    
使用jQuery:
  • 看看文档,就两行搞定..
    1
    2
    3
    4
    5
    $(document).ready(function() {
        $("img[class='attachment']").each(function(){
            $(this).attr("usemap","#"+$(this).attr("alt"));
        });
    });
    • 特别的:得考虑一页多个导图时的情况,所以是要进行 each() 循环处置
  • 当然的,需要 MoinGraphViz/main.py插件的配合,以便从附图的 alt 中获得正确的图片热区ID

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Index: tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py
    ===================================================================
    --- tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py (revision 17010)
    +++ tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py (revision 17013)
    @@ -65,5 +65,8 @@
             fImgName = os.path.basename(s)
             pfImgMap = "%s/%s.map"%(self.attapath,fImgName)
    -        s = wiki2html(self.request, '{{attachment:%s}}' % os.path.basename(s))
    +        #s = wiki2html(self.request, '{{attachment:%s}}' % os.path.basename(s))
    +        s = wiki2html(self.request, '{{attachment:%s|%s}}' % (os.path.basename(s)
    +                        ,fImgName.split("-")[1])
    +                    )
             #   100728 Zoom.Quiet appended <map> data
             if os.path.exists(pfImgMap):

1.3. 编码问题

一切表现良好,无意间发现凡是有URL包含的 dot 图谱,被其它页面包含时就出错!

囧rz...:
  • 尝试各种编码,未果
  • 嘗試各种<map>的包装形式:
    1. 使用 <pre>
    2. 使用 <textarea>
  • 都在 Include 时,可怜的出错了...
  • 实在是因为 MoinMoin 不想处理正常的 HTML 标签属性的其它编码内容
  • 好吧,俺就不给出无用的中文内容!
  • path/2/moin运行实例/data/plugin/parser/MoinGraphViz/main.py 追加一小段正则表达式替换
1
2
3
4
5
6
7
8
9
def format(self, formatter):
    w = self.request.write
    #...
    #   100728 Zoom.Quiet appended <map> data
    if os.path.exists(pfImgMap):
        import re
        p=re.compile( 'title=\".+?\"')
        s += p.sub("title=\"\"",fread(pfImgMap))
        #s += fread(pfImgMap)

HTML 输出:

1
2
3
4
5
6
7
8
<img alt="graphviz-hostLegendG-a58ce04d28b92b59230a72964c27a9f8fc867de5.png"
class="attachment"
src="/moin/KupHostsMapping/MapLegend?action=AttachFile&amp;do=get&amp;target=graphviz-hostLegendG-a58ce04d28b92b59230a72964c27a9f8fc867de5.png"
title="graphviz-hostLegendG-a58ce04d28b92b59230a72964c27a9f8fc867de5.png" />
<map id="hostLegendG" name="hostLegendG">
<area shape="rect" href="http://wiki.s.kingsoft.net/moin/KupHosts" title="" alt="" coords="101,36,173,63"/>
<area shape="rect" href="http://wiki.s.kingsoft.net/moin/KupHosts" title="" alt="" coords="197,36,269,63"/>
</map>

一切安定了...

2. 小结

  • 思路不乱的情况下,主要问题就是定位代码段!以及测试!
    • 面对一运行中的MoinMoin 进行测试开发时
    • 使用sshfs 可以快速挂接远程服务器的任意目录,非常方便!
    • 使用沙箱页面进行修订插件的测试,可以避免正常文章页面的中间调试失常..
    • 在调试中,直接输出预想数据到 HTML 里看,比看系统日志,使用print 要方便
    • MoinMoin 有完备的缓冲机制,要及时看到修订效果,得重启HTTPD
  • jQuery 真的很好用,也好学!
下载:
diff: MoinMoin_parser_MoinGraphViz_main.py-from-r16946-to-r17013.diff

2.1. 时间清单

  1. 00:05 定目标
  2. 00:15 准备环境
  3. 01:30 探查运行环境,明确修订目标脚本
  4. 01:45 插件修订完成
  5. 00:15 图片应用map jQuery 嘗試
  6. 00:35 图片插入系统修订完成
  7. 01:00 正则表达式+jQuery 解决 Incldue() 时的编码问题
  8. 00:45 整理代码,发布到Blog
  9. 00:35 整理代码,反馈到MoinMoin.in
  10. 00:40 8次中断,回到工作场景的心理浪费

总计: ~ 6小时


  • t2t渲染:: 2010-10-09 02:21:37
  • 动力源自::txt2tags

§ 写于: Fri, 30 Jul 2010 | 永久链接;源文: rdf ,rss ,raw | 分类: /utility/py4web/MoinMoin §
[MailMe] [Print] Creative Commons License

作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ [Py4SA]智能关闭触摸板

2010-07-14 10:22

1. 问题

嗯嗯嗯,俺一直使用 WACOM 的数字绘图板替代鼠标的 ;-)

  • 去年生日,老婆升级了俺的 FAVO CTE-430BAMBOO CTH-461
  • 随着 Ubuntu 10.04系统,一同升级了驱动,非常好用:
    1. 小横板 248.2 x 176.1,和屏幕 1:1 对应,不用拖动,有空间感觉了直接就可以移动到对应区域!
    2. 无线压感笔,直接点击就是左击鼠标
    3. 笔有侧键,配置默认就是原先最舒服的:
      • 上键是右击
      • 下键是中击
  • 触摸板的手指点击就不必要了
    • 如何令系统不启动触摸板的 Touch 响应?

2. 尝试

  • Command Line Configuration Interface (xsetwacom)
    • 官方是有命令行级别的配置工具的
    • 但是!真TMD难以看明白哈
    • 广泛的搜索后,大致明白,xsetwacom 提供两种配置输出:
      1. 命令行用的执行字串
      2. 配置文件中使用的配置文本
    • 整个使用流程是:
      1. 使用其它系统监察命令,确认 WACOM 设备USB接入后的设备号
      2. 使用 xsetwacom 进行模拟配置,选择输出合适的配置命令
      3. 然后将输出的配置内容,部署到合适的系统启动脚本或是配置文件中

  • 俺习惯使用命令行配置,这样可以直接测试是否靠谱,但是发现:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ~> xinput --list
    ⎡ Virtual core pointer                      id=2    [master pointer  (3)]
    ⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
    ⎜   ↳ Wacom Bamboo Craft Pen eraser             id=11   [slave  pointer  (2)]
    ⎜   ↳ Wacom Bamboo Craft Pen                    id=12   [slave  pointer  (2)]
    ⎜   ↳ Wacom Bamboo Craft Finger pad             id=13   [slave  pointer  (2)]
    ⎜   ↳ Wacom Bamboo Craft Finger                 id=14   [slave  pointer  (2)]
    ⎜   ↳ TPPS/2 IBM TrackPoint                     id=16   [slave  pointer  (2)]
    ⎜   ↳ Macintosh mouse button emulation          id=18   [slave  pointer  (2)]
    ⎣ Virtual core keyboard                     id=3    [master keyboard (2)]
        ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
    ...
    • 使用 xinput 探察出来的 Wacom Bamboo Craft Finger 触摸板设备号,每次重启系统时不一定一样!
    • FT! 那就得编程让系统重启时,自个儿探察一下设备号,然后使用固定的配置命令关闭 BAMBOO 对应设备
如何在Shell 中获取另外系统命令的输出并截获对应数码组合成新的命令?:
嗯嗯嗯,,, 嗯嗯嗯,,,,直接囧掉! 俺没有这种功力!

3. Py之

  • 直接使用模板记录要执行的命令,预留可能变动的设备号:
    1
    2
    3
    #!/bin/sh
    ### xinput4bamboo.tpl
    sleep 3 && xsetwacom set %s Touch "off"

  • 配合xinput4bamboo.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import sys,os
    if __name__ == '__main__':      # this way the module can be
        """usage:
        $ xinput --list | grep "Wacom Bamboo Craft Finger" | python ./xinput4bamboo.py
        """
        if sys.stdin:
            for l in sys.stdin.readlines():
                if "pad" not in l:
                    bambooid= l.split()[6].split("=")[1]
                    open("xinput4bamboo.sh","w").write(open("xinput4bamboo.tpl").read()%bambooid)
                    os.chmod("xinput4bamboo.sh",0755)

  • 组合成启动脚本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #!/bin/sh
    VER="main-rcloc.sh v10.7.14"
    DATE=`date "+%y%m%d"`
    MYRC="/home/zoomq/.zoomq/rc.local"
    #=========================================================== path defines
    LOGF="/var/log/0day/$DATE-myrcloc.log"
    #=========================================================== action defines
    cd $MYRC
    xinput --list | grep "Wacom Bamboo Craft Finger" | python ./xinput4bamboo.py  >> $LOGF 2>&1
    ./xinput4bamboo.sh  >> $LOGF 2>&1

  • 部属成启动应用:
    • 齐活! 没有疑惑,随手写成! 5分钟!
Python在Unix和Linux系统管理中的应用(影印版):
  • 绝对好书! 值得慢慢看,E文非常简洁,Py清明的代码,直接看/尝试就可以体会到本意了...

动力源自::txt2tags


§ 写于: Wed, 14 Jul 2010 | 永久链接;源文: rdf ,rss ,raw | 分类: /utility/py4sys §
[MailMe] [Print] Creative Commons License

作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ [Py4SA]批量文件重命名

2010-07-13 22:02

1. 问题

嗯嗯嗯,最近从 六哥的 DUKU 重新激发了评书的记忆,下载了全本 袁阔成(40回)红岩魂的mp3 ;-)

可是解开都是乱码文件名:

  • :;
  • 可恶的中文编码! 一定是在XP 中使用CP936 压出来的
  • 怎么整呢?
Python在Unix和Linux系统管理中的应用(影印版):
  • 绝对好书! 值得慢慢看,E文的也非常简洁,Py 清明的代码,直接看,尝试就可以体会到本意了...

2. 尝试

已知有很多途径...参考:linux下批量修改文件名_乌哥的家常菜_百度空间

  1. 使用find、awk/grep/sed、mv等命令组合来实现
    • 俺都不熟练,而且要串很多步操作,忒不直爽了..

  2. 编写Shell脚本运行~ 不就是命令组合嘛,,.,
  3. rename ... NGU/Linux 中的标准化重命名工具,但是,怎么可以将批量文件中指定前几个字符替换成指定字符?
    1
    2
    3
    4
    5
    ~/media/4talking/袁阔诚/try> rename -n 's/^.{6}/红岩魂/' *.mp3
    ...
    ���һ�38.mp3 renamed as 红岩魂38.mp3
    ���һ�39.mp3 renamed as 红岩魂39.mp3
    ���һ�40.mp3 renamed as 红岩魂40.mp3
    • 反复尝试半小时才获得满意的...

3. Py之

想嘗試Py 的思路...

1
2
3
4
5
6
7
8
import sys,shutil
if __name__ == '__main__':      # this way the module can be
    '''usage:
        $ ls *.mp3 | python rename-zh.py
    '''
    if sys.stdin:
        for l in sys.stdin.readlines():
            shutil.copyfile(l[:-1], "redStoneSoul-%s"%l[6:-1])
  • 好的,没有尝试,随手写来,3分钟搞掂

Python在Unix和Linux系统管理中的应用(影印版):
  • 绝对好书! 值得慢慢看,E文非常简洁,Py清明的代码,直接看/尝试就可以体会到本意了...

动力源自::txt2tags


§ 写于: Tue, 13 Jul 2010 | 永久链接;源文: rdf ,rss ,raw | 分类: /utility/py4sys §
[MailMe] [Print] Creative Commons License

作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ index_static.py

2010-05-31 11:10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# -*- coding: utf-8 -*-
"""
index_static.py
    - just fixed for pyblosxom-cmd  staticrender export all .html links
base index.py from:
Ryan Barrett <[email protected]>
 
This plugin displays an alphabetical index of all entries. It uses these
optional config variables from config.py, shown here with their defaults:
 
py['index_trigger']            = '/site-index'
py['index_num_columns']        = 2
py['index_letters_first']      = True
py['index_title']              = 'index'
py['index_use_story_template'] = True
 
 
VERSION:
0.2
 
TODO:
- use a template instead of hard-coded HTML
 
Copyright 2006 Ryan Barrett
 
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
"""
import math
import os.path
import time
from Pyblosxom import tools
import Pyblosxom.entries
 
__author__ = 'Ryan Barrett'
__version__ = '0.2'
__description__ = 'Displays an alphabetical index of all entries.'
 
 
def verify_installation(request):
  return 1
 
def cb_filelist(args):
  request = args['request']
  http = request.getHttp()
  data = request.getData()
  config = request.getConfiguration()
 
  trigger = config.get('index_trigger', 'site-index')
  if http['PATH_INFO'] != trigger:
    return
 
  # get the entries
  datadir = config['datadir']
  files = tools.Walk(request, datadir)
  files.sort()
 
  # sort into sections, one for each letter. the dictionary is
  # letter => (entry name, path) where path is the relative to datadir.
  sections = {}
  entry_extensions = data['extensions'].keys()
 
  for file in files:
    assert file.startswith(datadir)
    path, ext = os.path.splitext(file[len(datadir):])
    if ext[1:] in entry_extensions:  # strip the leading period from ext
      entry_name = os.path.basename(path)
      sections.setdefault(entry_name[0].upper(), []).append((entry_name, path))
 
  # extract the first letters. sort as usual, except that numbers and other
  # non-letters go *after* letters.
  def letters_before_symbols(a, b):
    if a.isalpha() and not b.isalpha():
      return -1
    elif not a.isalpha() and b.isalpha():
      return 1
    else:
      return cmp(a, b)
 
  letters = sections.keys()
  if config.get('index_letters_first', 1):
    letters.sort(letters_before_symbols)
  else:
    letters.sort()
 
  # add the header with links to each section
  body = '<p class="index-header">'
  letter_links = ['<a href="#%s">%s</a>' % (l, l) for l in letters]
  body += ' |\n'.join(letter_links)
  body += '</p>\n<hr class="index">\n\n'
 
  # add the sections themselves, with one link per entry, in a table. the
  # number of columns is taken from the index_num_columns config variable.
  # entries are ordered down each column, in order.
  num_cols = config.get('index_num_columns', 2)
 
  for l in letters:
    body += '<h3 class="index">%s</h3> <a name="%s"></a>\n' % (l, l)
    body += '\n'
 
    entries = sections[l]
    entries.sort()
    num_rows = int(math.ceil(float(len(entries)) / num_cols))
 
    for row in range(0, num_rows):
      # alternate the  tags' class between index-row-stripe-0 and
      # index-row-stripe-1, so you can use CSS to alternate their color for
      # readability, if you want.
      body += '\n' % (row % 2)
      for col in range(0, num_cols):
        entry_index = col * num_rows + row
        if entry_index < len(entries):
          entry_name, path = entries[entry_index]
        else:
          entry_name = path = ''
        body += '\n' % (path[1:], entry_name)
      body += '\n'
 
    body += '<table class="index"><tbody><tr></tr><tr class="index-row-stripe-%d"><td><a href="%s.html">%s</a></td></tr></tbody></table>\n<hr class="index">\n\n'
 
  data = {'title': config.get('index_title', 'index')}
  # use the epoch for mtime. otherwise, pyblosxom uses the current time, which
  # makes other plugins (like weblogsping) think this is a new entry.
  epoch = time.localtime(0)
  fe = Pyblosxom.entries.base.generate_entry(request, data, body, epoch)
  return [fe]
 
def cb_story(args):
  request = args['request']
  http = request.getHttp()
  config = request.getConfiguration()
  trigger = config.get('index_trigger', 'site-index')
 
  if (http['PATH_INFO'] == trigger and
      not config.get('index_use_story_template', 1)):
    title = config.get('index_title', 'index')
    args['template'] = '<h1 class="index">%s</h1>\n<hr>\n$body' % title
 
  return args

§ 写于: Mon, 31 May 2010 | 永久链接;源文: rdf ,rss ,raw | 分类: /techic/PyBlosxom/plugins §
[MailMe] [Print] Creative Commons License

作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ hardcodedates.py

2010-05-31 11:10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# -*- coding: utf-8 -*-
from Pyblosxom import tools
import os, os.path, posix, re, stat, time
 
FILETIME = re.compile('^([0-9]{4})-([0-1][0-9])-([0-3][0-9])-([0-2][0-9])-([0-5][0-9]) +(.*)$')
 
all_timestamps = {}
extensions = []
timestamps_to_save = {}
 
 
#mode: python; indent-tabs-mode: t, tab-width: 4
"""
This allows the user to create a file "timestamps" in their datadir,
that will override the timestamp of any given blog entry. Each line
in this file should be of the form "YYYY-MM-DD-hh-mm file-name".
Then for any entry that one of these lines exist the system will use
that timestamp instead of the actual files modification time.
 
Note: the filename is relative to your data-dir.
Example of a line for the file /var/data-dir/school/abc.txt
   where the datadir is "/var/data-dir/" and the date is Aug 9, 2004.
 
2004-08-09-00-00 school/abc.txt
"""
__author__ = 'Nathan Kent Bullock, Ryan Barrett'
__email__ = 'nathan_kent_bullock -at- yahoo.ca, hardcodedates -at- ryanb.org'
__version__ = '1.4'
def init(request):
    if all_timestamps:
        return  # already initialized
 
    datadir = request.getConfiguration()['datadir']
    timestamp_file = os.path.join(datadir, 'timestamps')
 
    if os.path.isfile(timestamp_file):
        f = file(timestamp_file)
        for str in f.readlines():
            m = FILETIME.search(str.strip())
            if m:
                year = int(m.group(1))
                mo = int(m.group(2))
                day = int(m.group(3))
                hr = int(m.group(4))
                minute = int(m.group(5))
                mtime = time.mktime((year,mo,day,hr,minute,0,0,0,-1))
                filename = os.path.join(datadir, m.group(6))
                all_timestamps[filename] = mtime
 
        f.close()
 
    extensions.extend(request.getData()['extensions'].keys())
    extensions.append(request.getConfiguration().get('comment_ext', 'cmt'))
 
def cb_filestat(args):
    request = args['request']
    init(request)
 
    filename = args['filename']
    extension = os.path.splitext(filename)[1][1:]
    datadir = request.getConfiguration()['datadir']
 
    if all_timestamps.has_key(filename):
        # we know this file's timestamp
        mtime = args['mtime']
        assert isinstance(mtime, (tuple, posix.stat_result))
        args['mtime'] = (mtime[0:stat.ST_MTIME] + (all_timestamps[filename],) +
                         mtime[stat.ST_MTIME + 1:])
 
    elif extension in extensions and filename.startswith(datadir):
        # we don't know it, but we should. ask the os for it, and remember it.
        args['mtime'] = os.stat(filename)
        all_timestamps[filename] = args['mtime'][stat.ST_MTIME]
        timestamps_to_save[filename] = args['mtime'][stat.ST_MTIME]
 
    return args
 
def cb_end(args):
    if timestamps_to_save:
        datadir = args['request'].getConfiguration()['datadir']
        datadir = os.path.normpath(datadir)
        tsfile = file(os.path.join(datadir, 'timestamps'), 'a')
        for filename, mtime in timestamps_to_save.items():
            time_str = time.strftime('%Y-%m-%d-%H-%M', time.localtime(mtime))
            # strip the datadir prefix and directory separator slash
            filename = filename[len(datadir) + 1:]
            tsfile.write('%s %s\n' % (time_str, filename))
            tools.getLogger().info('Saved mtime %s for %s' % (time_str, filename))
        tsfile.close()
        timestamps_to_save.clear()

§ 写于: Mon, 31 May 2010 | 永久链接;源文: rdf ,rss ,raw | 分类: /techic/PyBlosxom/plugins §
[MailMe] [Print] Creative Commons License

作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ category_static.py

2010-05-31 11:10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# -*- coding: utf-8 -*-
"""
category_static.py
    - just fixed for pyblosxom-cmd  staticrender export all .html links
base index.py from:
Ryan Barrett <[email protected]>
    - Zoom.Quiet 100401
 
This plugin displays an alphabetical index of all entries. It uses these
optional config variables from config.py, shown here with their defaults:
 
py['cindex_trigger']            = '/site-index'
py['cindex_num_columns']        = 2
py['cindex_letters_first']      = True
py['cindex_title']              = 'index'
py['cindex_use_story_template'] = True
 
 
VERSION:
0.2
 
TODO:
- use a template instead of hard-coded HTML
 
Copyright 2006 Ryan Barrett
 
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
"""
 
 
import math
import os.path
import time
from operator import itemgetter
from Pyblosxom import tools
import Pyblosxom.entries
#import re
 
__author__ = 'Zoom.Quiet <zoomquiet+pyb at="" gmail="" dot="" com="">'
__version__ = '11.09.7'
__description__ = 'Displays an Category index as Tree for all entries. in one page'
DEFAULT_ROOT = []
def verify_installation(request):
  return 1
 
def cb_filelist(args):
    request = args['request']
    http = request.getHttp()
    data = request.getData()
    config = request.getConfiguration()
    _baseurl = config.get("base_url", "")
 
    trigger = config.get('cindex_trigger', 'site-index')
    if http['PATH_INFO'] != trigger:
        return
 
    # get the entries
    datadir = config['datadir']
    files = tools.Walk(request, datadir)
    files.sort()
 
    body = '<div id="categoriselist">'
    #print files
    # sort into sections, one for each letter. the dictionary is
    # letter => (entry name, path) where path is the relative to datadir.
    #sections = {}
    #   the entrise dictionary is
    # path => (entry name, 0)
    entrise = []
 
    entry_extensions = data['extensions'].keys()
 
    for file in files:
        #objEntry = entries.fileentry.FileEntry(request, file,datadir)
        #print objEntry.keys() 
        assert file.startswith(datadir)
        path, ext = os.path.splitext(file[len(datadir):])
        if ext[1:] in entry_extensions:  # strip the leading period from ext
            entry_name = os.path.basename(path)
            #sections.setdefault(entry_name[0].upper(), []).append((entry_name, path))
            entrise.append((entry_name,path,file))
    #print entrise
    #sortPaths = sorted(entrise.iteritems(), key=itemgetter(1), reverse=True)
    #print sortPaths
    #print _baseurl
    etree = {}
    """{
    "pathID":[(path.split()),"title",...]
    ,
    }
    """
    for entry in entrise:
        e = Pyblosxom.entries.fileentry.FileEntry(request, entry[2], entry[1])
        deeps = entry[1].split("/")[:-1]
        pathID = "".join(deeps)
        if pathID in etree:
            etree[pathID].append((e['title'],entry[1]))
        else:
            etree[pathID]= [tuple(deeps),(e['title'],entry[1])]
    #print etree.keys()
    root_path_list = config.get("category_root_list", DEFAULT_ROOT)
    root_entry_list = []
    for opath in root_path_list:
        #print opath
        crtRoot = []
        for k in etree.keys():
            if opath in k:
                crtRoot.append(k)
        crtRoot.sort()
        root_entry_list.append((opath,crtRoot))
 
    '''root_entry_list as::
    [('Zen', ['ZenChinese', 'ZenGoogle', 'Zenpythonic']), ('oss', ['oss', 'ossFreeBSD', 'ossMozillaFireFox', 'ossUbuntu']), ('opening', []), ('mind', ['mind']), ('Quiet', ['Quietliving', 'Quietnomeans']), ('utility', ['utilitySubversion', 'utilitySubversionhooks', 'utilitypy4strStructuredText', 'utilitypy4webDjango', 'utilitypy4webMoinMoin', 'utilitypy4webQuixote', 'utilitypy4zh', 'utilityzqlib']), ('internet', ['internet', 'internetFolksonomy']), ('easy', ['easymovie', 'easymusic']), ('techic', ['techic', 'techicEmacs', 'techicPyBlosxom', 'techicPyBlosxomblosxom', 'techicPyBlosxomplugins'])]
    '''
    body += '<h3>/</h3>'
    for e in etree[''][1:]:
        #print "etree[''] include::",e
        body += '<span class="indents">%s</span><a href="%s%s.html">%s</a><br>\n'%(
            "...."
            ,_baseurl
            ,e[1]
            ,e[0]
            )
    #print root_entry_list
 
    for k in root_entry_list:
        #['techic', 'techicEmacs', 'techicPyBlosxom', 'techicPyBlosxomblosxom', 'techicPyBlosxomplugins']
        body += '<h4>%s/</h4>'%k[0]
        cpath = ""
        for p in k[1]:
            #print etree[p]
            #[('', 'Zen', 'Chinese'), '9.18', 'CC Salon BJ', '\xe2\x80\x9c\xe5\x9b\xbd\xe9\x99\x85\xe8\x87\xaa\xe7\x94\xb1\xe8\xbd\xaf\xe4\xbb\xb6\xe6\x97\xa5\xe2\x80\x9d\xe4\xb9\x8b\xe5\xa4\xb4\xe8\x84\x91\xe9\xa3\x8e\xe6\x9a\xb4', '\xe8\xa1\xa8\xe5\xbd\xa2\xe7\xa0\x81\xe7\x9a\x84\xe6\xb6\x88\xe4\xba\xa1\xe8\x83\x8c\xe6\x99\xaf\xef\xbc\x81']
            epath = "/".join(etree[p][0][2:])
            if k[0] != "".join(etree[p][0]):
                if cpath != epath:
                    cpath = epath
                    ldeep = len(etree[p][0][1:])
                    if 3 > ldeep:
                        body += '<h5>%s/</h5>'%"/".join(etree[p][0][2:])
                    else:
                        body += '<h6>%s/</h6>'%"/".join(etree[p][0][3:])
            for e in etree[p][1:]:
                body += '<span id="%s" class="indents">%s</span><a href="%s%s.html">%s</a><br>\n'%(
                        "/".join(etree[p][0])
                        ,"..."*len(etree[p][0])
                        ,_baseurl
                        ,e[1]
                        ,e[0]
                        )
 
    '''
    [('', 'easy', 'movie'), '\xe4\xb8\x96\xe9\x97\xb4\xe5\xae\x89\xe5\xbe\x97\xe5\x8f\x8c\xe5\x85\xa8\xe6\xb3\x95,\xe4\xb8\x8d\xe8\xb4\x9f\xe5\xa6\x82\xe6\x9d\xa5\xe4\xb8\x8d\xe8\xb4\x9f\xe5\x8d\xbf!']
    [('', 'easy', 'music'), 'ZARD\xe6\xb6\x88\xe9\x80\x9d\xe4\xba\x86']
 
    for entry in entrise:
        e = Pyblosxom.entries.fileentry.FileEntry(request, entry[2], entry[1])
        #print e['title']
        #print entry[1].split("/")[:-1]
        body += '<span class="indents">%s</span><a href="%s%s.html">%s</a>%s<br>\n'%(
                "...."*len(entry[1].split("/"))
                ,_baseurl
                ,entry[1]
                ,e['title'] #entry[0]
                ,entry[1]
                )
    '''
    #print body
    body +="</div>"
    data = {'title': config.get('cindex_title', 'index')}
    # use the epoch for mtime. otherwise, pyblosxom uses the current time, which
    # makes other plugins (like weblogsping) think this is a new entry.
    epoch = time.localtime(0)
    fe = Pyblosxom.entries.base.generate_entry(request, data, body, epoch)
    return [fe]
 
def cb_story(args):
  request = args['request']
  http = request.getHttp()
  config = request.getConfiguration()
  trigger = config.get('cindex_trigger', 'site-index')
 
  if (http['PATH_INFO'] == trigger and
      not config.get('cindex_use_story_template', 1)):
    title = config.get('cindex_title', 'index')
    args['template'] = '<h1 class="index">%s</h1>\n<hr>\n$body' % title
 
  return args
</zoomquiet+pyb></[email protected]>

§ 写于: Mon, 31 May 2010 | 永久链接;源文: rdf ,rss ,raw | 分类: /techic/PyBlosxom/plugins §
[MailMe] [Print] Creative Commons License

作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ IOP:实践之一

2010-05-13 19:19

1. 背景

什么事儿呢?

  • 在一高压力服务环境中,需要加速系统的响应
  • 现行系统对于数据查询要尝试三种数据库源:
    1. memcache
    2. redis
    3. MySQL
  • 期望查询加速至少 300%
运行环境
客户机:
    - 俺的Laptop HP 520
    - 双核CPU 2G内存
    - Ubuntu 9.10
DB主机:
    - CentOS 5.0
    - 单核CPU 4G 内存

1.1. 预案

这事儿,一想,简单哪,都放内存就好的哪

  • 统计了一下需要用来查询的数据不到2千万条
  • 折算成文件不过1G
  • 加载到内存中,使用 Python 字典结构的话,也最多涨一倍,也完全可以接受
  • 速度?!

2. IOP的加速技巧

没有想到,加速,只要不断将代码住短里面写就好!

不知道什么是 IOP?

  • PyIOP
  • 咔咔咔,沈游侠总结的编程态度>...

2.1. 10万:170+4秒

最直接的实现
  • 从redis 读
  • 生成 dict 对象
  • 以pickle dump 出序列化文件
  • 用pickle load 加载成dict对象

2.1.1. code

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/python
# -*- coding: utf-8 -*-
import struct,sys,time
import cPickle as pickle
import redis
REVERSION = "r2d.py v10.5.7"
def _push2dict(dictall,key,smembers):
    dictall[struct.pack('I',int(key[1:]))]=[s.split("|") for s in rb.smembers(k)]   
    return dictall
if __name__ == '__main__':      # this way the module can be
    if 3 != len(sys.argv):
        print """ %s usage::
$ python r2d.py redistIP limitnumber [like 10000]
        """ % REVERSION
    else:
        hostIP = sys.argv[1]
        limitn = sys.argv[2]
        rb = redis.Redis(host=hostIP, port=6379, db=9)
        rbkeys = rb.keys().split()
        loop = int(limitn)
        s4dict={}
        for k in rbkeys:
            if 0 == loop:
                break
            else:
                loop -=1
                _push2dict(s4dict,k,rb.smembers(k))
        pickle.dump(s4dict, open('r4d.dump', 'wb'))

2.1.2. speed

  • 代码足够简单了,单函式,20行
  • 速度测试::
    • 10W 值对导出 >170秒,导入>4秒;
    • 100W 值对导出 >500秒,导入>25秒;
    • 1000W 值对导出 失败! 内存提前耗光!

2.1.3. improve

这完全无法接受哪...

加速尝试::
  1. 根据 IOP 尽量不用函式,将那个一行函式清除,代码填回循环 ~ 立即获得几秒的加速
  2. 本来用的就是cPickle 了,模块效率没有办法了
  3. 嗯嗯嗯,可以不用 cPickle卟?
    • 直接输出自然 .py 哈?!
1
2
3
4
5
6
# 使用 str() 将字典对象用文本的方式记入 .py
vdf = open("r2d.define.py","wa")
vdf.write("s4dict=")
vdf.write(str(s4dict))
vdf.close()
# 使用时直接 import 就好

  • 改进后测试::
    • 10W 值对导出 >160秒,导入>3秒;
    • 100W 值对导出 >400秒,导入>19秒;
    • 1000W 值对导出 依然杯具
  • 加速不明显:
    • 导出时速度变化很小
    • 载入时速度有提升
  • 进一步观察到,导出时内存飞速增长:
    • 100W级别,要食掉1.6G左右的内存
    • redis 本身也很占内存,千万级别时,也要占上G (当然这和条目数量/内容有关)
    • 这对于服务器是个不可接受的方式

2.2. 10万:150+10秒

改进中间数据格式:
  • 从redis 读
  • 生成 中间log文件
  • 导入成 字典对象

2.2.1. thinking

因为有这些现实:

  1. 想输出不论 pickle 或是 .py 的字典对象,都得先在内存中构造出这一对象
  2. 随着字典对象的规模增加,必然导致这一构建过程的时间加长

经沈游侠提醒,发现字典对象其实是可以线性输出的:

  • 比如说,字典结构如:
    {key:[(v1,v2,v3),..]
    ,...
    }
    
    • K:[list] 形式的两层结构
  • 那么,就可以通过中间数据文本:
    ('key', [(v1, 'v2', v3)])
    ...
    
  • 进行线性加载,e.g:
    1
    2
    3
    4
    5
    6
    for l in open("r2d.define.py.log","r").readlines():
        dd = eval(l)
        if dd[0] in s4d:
            s4d[dd[0]].append((dd[1],dd[2],dd[3]))
        else:
            s4d[dd[0]]=[(dd[1],dd[2],dd[3])]

2.2.2. code

快速修订对应行动代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/python
# -*- coding: utf-8 -*-
import struct,sys,time
import redis
REVERSION = "r2d.py v10.5.8"
if __name__ == '__main__':      # this way the module can be
    if 3 != len(sys.argv):
        print """ %s usage::
$ python r2d.py redistIP limitnumber [like 10000] > mid-data.log
        """ % REVERSION
    else:
        hostIP = sys.argv[1]
        limitn = sys.argv[2]
        rb = redis.Redis(host=hostIP, port=6379, db=9)
        rbkeys = rb.keys().split()
        loop = int(limitn)
        for k in rbkeys:
            if 0 == loop:
                break
            else:
                loop -=1
                dictkey = struct.pack('I',int(k[1:]))
                sli = []
                for s in rb.smembers(k):
                    rli = s.split("|")[:3]
                    if rli:
                        rli[0] = int(rli[0])
                        rli[1] = struct.pack('I',int(rli[1]))
                        rli[2] = int(rli[2])
                        sli.append(tuple(rli))
                print >> d2f,`dictkey,sli`
                #注意: `obj` 等同 repr(obj) 

2.2.3. improve

代码依然简单了,无函式,30行

  • 速度测试::
    • 10W 值对导出 >150秒,导入>10秒;
    • 100W 值对导出 >500秒,导入>20秒;
    • 1000W 值对导出 >1300秒,导入>90秒;
  • 速度看起来没有什么明显的提高
  • 不过!
    1. 千万级别的数据可以在低配置环境中跑完了!
    2. 内存占用很稳定永远90M左右,不会随字典对象的增长而增长!

嗯嗯嗯,这算是可用了...

2.3. 10万:100+200秒?!

继续改进:
  • 放弃 redis 直接从MySQL 读
  • 生成 中间日志
  • 导入成 dict 文件

2.3.1. thinking

虽然redis 是号称最快的 K/V 数据库产品,但是,明显就是它将整个业务响应速度拖慢了..

为什么呢?
  • 服务器程序和本地程序面对的环境是不同的
  • 高压力服务器程序和小压力服务器程序也是不同的
  • 简单来说:
    1. 小型服务~=每秒<C60
    2. 中型服务~=每秒<C600
    3. 大型服务~=每秒>C1000
  • 面对的矛盾是完全不同的:
    1. 小型服务->语言执行效率
    2. 中型服务->框架执行效率
    3. 大型服务->I/O 响应速度
  • 所以,对于面向Web 的查询服务,不论 Redis/MySQL 对于业务系统,都是进程间通讯!
  • 每次跨进程通讯,都意味着至少四次I/O操作!
  • 所以,当前 Redis->log->内存字典的转换流程,其实包含了 MySQL->Redis 的进程操作
  • 另外:
    • 遍查 Redis 文档,居然没有 iterkeys() 类似的操作!
    • 每次不论转换多少 Redis 的值对,都得使用keys() 将键先取出来然后再逐一匹配处理
    • 怪不得使用 Redis 测试用小仓库(包含20万值对)时,脚本运行速度和使用全数据Redis(千万值对)时速度要相差5倍以上!内存也占用多几倍!

所以!要直接从 MySQL 相关表中读取

2.3.2. code

配合一SQL 模板:

1
2
-- _tpl/all_black.tpl
SELECT v1,v2,v3,v4,id FROM t_black LIMIT %(limitMAX)s;

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/python
# -*- coding: utf-8 -*-
REVERSION = "m2d.py v10.5.9"
import struct,sys,time
 
if __name__ == '__main__':      # this way the module can be
    """usage:
$ python m2d.py limit [such as 100] |\
  mysql -h xx.xx.xx.xx -u User -D --password=***  |\
  python m2d.py > m2d.log
    """
    if sys.stdin:
        if 1 < len(sys.argv):
            limit = sys.argv[1]
            limitMAX = int(limit)
            print >> sys.stderr, info
            print open("_tpl/all_black.tpl","r").read()%locals()
        else:
            s4dict={}
            virusname={}
            totalN = 0
            for l in sys.stdin.readlines():
                if "id" in l:
                    pass
                else:
                    totalN += 1
                    lkv=l.split()
                    dictkey = struct.pack('I',int(lkv[0]))
                    lkv[1] = int(lkv[1])
                    lkv[2] = struct.pack('I',int(lkv[2]))
                    lkv[3] = int(lkv[3])
                    print >> d2f,`dictkey,tuple(lkv[1:])`
  • 用是否有额外参数来判定是否生成SQL,还是进行MySQL 的输出数据处理
  • 为了不影响标准输出,调试信息,输出到标准错误IO
  • 调试也应该根据管道串的层级,一级级运行尝试
  • 调用的shell:
    1
    2
    3
    4
    5
    6
    7
    #!/bin/sh
    #   m2d.sh v10.5.9
    python m2d.py go |\
        mysql -h xx.xx.xx.xx -u User --password=***|\
        python mysql4dict.py $1 |\
        mysql -h xx.xx.xx.xx -u User --password=***|\
        python m2d.py > m2d.log

    加载时代码没有怎么变

2.3.3. speed

  • 速度测试::
    • 0.1W 值对导出 <5秒,导入<1秒;
    • 10W 值对导出 >300秒,导入>10秒;
    • 100W 值对导出 杯具鸟
    • 1000W 值对导出 杯具鸟
  • 速度怎么和查询的条目多少有关? 而且一大MySQL 就拒绝服务?

2.3.4. improve

嗯嗯嗯,FT! 当然了,MySQL 请求响应时间是有限制的,大数据传输肯定是有问题的, 利用 LIMIT 的切片!

  • 改造前述m2d.py v10.5.9 SQL生成部分的代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    step = 5000.0
    limitMAX = int(limit)
    sqltpl = open("_tpl/all_black.tpl","r").read()
    if 1 >= limitMAXb/step:
        print sqltpl%locals()
    else:
        for i in range(int(limitMAX/step)):
            limitMAX = "%d,%d"%(step*i,int(step))
            print sqltpl%locals()
  • 以 5000 为界限,生成类似 LIMIT 15000,5000 的限制
  • 速度测试::
    • 100W 值对导出 >290秒,导入>190秒;
    • 1000W 值对导出 杯具鸟

FT!怎么回事儿?速度意外的慢!

2.4. 1000万:1100秒+480秒

冷静后改进:
  • 从MySQL 读使用id 进行限制切分
  • 生成 中间日志,但是重新设计结构
  • 导入成 dict 文件

2.4.1. thinking

MySQL 为什么这么慢?!

2.4.2. code

SQL 模板配合改进:

1
2
-- _tpl/all_black.tpl
SELECT v1,v2,v3,v4,id FROM t_black WHERE id>%(LIMbwID)s AND id<=%(MAXbwID)s;

SQL 生成代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
step = 3000.0
offset = int(step)
# < <gen_sql_with_max> > 使用Leo 时可以定义子节点将成堆代码变成语义标记
for l in sys.stdin.readlines():
    if "max(id)" not in l:
        amount = l.split()
        MAXbwID = int(amount[0]
        MINbwID = int(amount[1]
        MAXbwA = MAXbID-MINbID
MAXbMAX = MAXbwID
lastID = 0
sqltpl = open("_tpl/all_black.tpl","r").read()
for i in range(MAXbwA/offset):
    MAXbwID,LIMbwID = (MAXbMAX-offset*i,MAXbMAX-offset*(i+1))
    print sqltpl%locals()
    lastID = LIMbID
MAXbID,LIMbID = lastID,MINbID   # 将切片限数之内的尾数个ID也查询出来
print sqltpl%locals()
  • 咔咔咔,当然的,要进行基于ID 的精确切分选择,就得先知道最大和最小的ID
  • 使用SQL SELECT max(id),min(id) FROM t_black ;
  • 自然运用系统管道串接成处理过程:
    1
    2
    3
    4
    5
    6
    7
    #!/bin/sh
    #   m2d.sh v10.5.9
    DATE=`date "+%y%m%d-%H%M%S"`
    mysql -h xx.xx.xx -u User --password=*** < _tpl/total_black.sql |\
        python m2d.py go |\
        mysql -h xx.xx.xx -u User --password=*** |\
        python m2d.py > m2d_$DATE.log

2.4.3. improve

  • 速度测试::
    • 100W 值对导出 >280秒,导入>120秒;
      • m2d.log > 150M
    • 1000W 值对导出 >1300秒,导入>780秒;
      • m2d.log > 570M

进一步的,发现业务其实可以将双层,两次查询优化成一次查询的!

  • 简单的将输出字典的数据结构变成:
    {key:v,...
    }
    
  • 将原先的2个值结合原先的key 变成键,就成为了全局唯一的key
  • 即和MySQL 每行数据完成一一对应
  • 速度再测试::
    • 100W 值对导出 >250秒,导入>100秒;
      • m2d.log > 130M
    • 1000W 值对导出 >1100秒,导入>480秒;
      • m2d.log > 490M
  • 哗! 而且加载完后,字典对象所点内存体积也同样减少了 30% !

2.5. 小记

综上:
  • 应用 IOP 方面的主要招术:
    1. 表用 函式
    2. 表用 模块
    3. 尽量使用 OS 的标准 I/O 进行功能串接
  • DOP~Data Oriented Programming
    • 面向数据编程
    • 归根到底,程序都得操作数据解决问题
    • 服务器端,大并发压力时,最有效的节省I/O 的方式,就是高效方式
    • 压缩输入/出的数据量自然是最好的代码!

3. 提速100倍

嗯嗯嗯?!怎么说到最后好象也没有加速到100倍哪?

  • 是也乎,是也乎,以上分享的是内存化字典数据的转换脚本加速过程
  • 但是最后要加速的是整个查询业务哈?
  • 因为涉及公司核心服务,代码就无法展示了
  • 不过,现实是:
    1. 使用了以上 IOP 中提及的基础技巧
    2. 每查询业务的处理时间从原先的 0.* 秒,加速到 0.00*秒,至少100倍
    3. 而对应的代码从 几千行,精简到几十行

所以,基本上可以这么理解:

  • 将代码每精简一倍体积
  • 运行速度就有望提高10倍

    不相信? 尝试一哈噻...

  • t2t渲染:: 2010-10-09 02:21:36
  • 动力源自::txt2tags

§ 写于: Thu, 13 May 2010 | 永久链接;源文: rdf ,rss ,raw | 分类: /Zen/IOP §
[MailMe] [Print] Creative Commons License

作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ 回到PyBlosxom

2010-04-18 23:23

1. 怎么了?

  • 曾经的 PyBlosxom 1.3.1 发布的
  • 从2005年开始用到2007年南下到金山,远离了服务器,发布不便就停了下来,但是总之还在存活...
  • 100401 啄木鸟新SA,小白同学升级 FreeBSD 时,发现 PyBlosxom 的cgi 有安全隐患,算是彻底关闭了当年定制了很多东西的hacker 级别Blog 实例;
  • 刚好 PyBlosxom1.5-rc1 也发布了,就趁机整体搞一下

2. 规划

习惯性的先计划,再作...

2.1. 系统

  • 从PyBlosxom 1.3.1 升级到 1.5-rc1
  • 发布方式从 cgi 升级到 静态发布 (类似 MT 的那种)
  • 版本管理从 SVN 迁移到 Hg (依然在 code.google 空间中)

2.2. 样式

  • 从自个儿模仿 deviantART 设计的暗绿色,迁移到 Wordpress 中最轻的样式: doc

2.3. 插件

从用途来分类

归档:
  • 使用 xhtml作为最终输出
  • 按照年月归档并计数
  • 按照分类归档并计数
  • 对所有图片进行简单的图库管理

导览:
  • 对静态页面网站提供搜索
  • 前后关联文章的快速链接
  • 分页式的导航链接 杯具的插件,在静态发布状态中
  • 按照文章首字母的总索引
  • 支持多种语言的语法高亮

SNS:
  • rdf/rss 自动生成
  • 评注?-( (mail 先)
  • TAG云?-( (和分类有重叠)

3. 成果

就是当前这个重生的 ZQ's PyBlosxom 了

  • 通过对 t2t 的hack,可以直接在 t2t 文本中聲明是否使用 SyntaxHighlighter ,以及使用哪种语法刷来显示

demo
1
2
3
4
5
6
7
8
9
py["plugin_dirs"] = [os.path.join(blogdir, "plugins")
                        ,os.path.join(blogdir, "plugins/archives")
                        ,os.path.join(blogdir, "plugins/navigate")
                        ,os.path.join(blogdir, "plugins/date")                       
                        ,os.path.join(blogdir, "plugins/preformatters")
                        ,os.path.join(blogdir, "plugins/folksonomy")
                        ,os.path.join(blogdir, "plugins/willplugins")
                        ,os.path.join(blogdir, "plugins/zqpyb+")   
                        ]

3.1. TODO

不断维护的完美之途紀...

PyBlosxom 静态化发布体系:
  1. 升級到 1.5.1b
  2. 摸索静态化发布
    1. 升迁旧样式
    2. 兼容插件
    3. 微调样式:
      • 头部的标题索引入口文字,怎么也消除不了下划綇
      • //SyntaxHighlighter 的行号后有意外空白 //
        • 伪问题 fixed!
      • 归档插件的输出页面中,日期链接失常,应该清除
  3. Hg的 hooks 部署
  4. dot 的自动包含图片热区定义的 t2t 处理
  5. Leo 中的自动化发布脚本按钮

动力源自::txt2tags


§ 写于: Sun, 18 Apr 2010 | 永久链接;源文: rdf ,rss ,raw | 分类: /techic/PyBlosxom §
[MailMe] [Print] Creative Commons License

作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。

¶ OpenBookProject

2010-01-01 23:23

历史小记:
  1. 2005-12-10 07:34:19 梁如军,Osmond创建维基入口
  2. 2007-07-26 22:07:06 +0800 Zoom.Quiet 创建工程环境 openbookproject - Project Hosting on Google Code
  3. 在OBP组织下2007-12-07 《Python核心编程》(中文第二版) (豆瓣) 完成翻译,也引发了书评__China-Pub 《Python 核心编程》应属于社区翻译 吉广事件!
  4. 在OBP组织下2008-04-01 Python源码剖析——深度探索动态语言核心技术 (豆瓣) 完成校对
  5. 在OBP组织下2009-09-01 可爱的Python (豆瓣) 方式发售
  6. http://obp.zoomquiet.org/ 开始使用Sphinx 持续日编译发布最新 OBP 成果...

  • t2t渲染:: 2010-10-09 02:21:43
  • 动力源自::txt2tags

§ 写于: Fri, 01 Jan 2010 | 永久链接;源文: rdf ,rss ,raw | 分类: /opening/OpenBookProj §
[MailMe] [Print] Creative Commons License

作品Zoom.Quiet创作,采用知识共享署名-相同方式共享 2.5 中国大陆许可协议进行许可。 基于zoomquiet.org上的作品创作。