跨平台PHP调试器设计及使用方法——界面设计和实现
一个优秀的交互设计往往会影响一个产品的命运。在设计这款调试器时,我一直在构思这款调试器该长什么样子。简单、好用是我设计的原则,于是在《跨平台PHP调试器设计及使用方法——立项》一文中,我给出了一个Demo。之后实现的效果也与之变化并不大。(转载请指明出于breaksoftware的csdn博客)
在《跨平台PHP调试器设计及使用方法——立项》一文中,我阐述了该款调试器将采用网页的形式提供交互操作。这样我们需要一个Python实现的Web框架。在这个大的开源的环境下,我们可以轻易找到一些优秀的库来辅助我们快速开发一些功能。比如之前我选择pydbgp库去和Xdebug进行通信,这样就规避了很多协议的底层实现工作。同样在Web框架这块,我决定使用比较轻量级bottle库。
bottle库的官方网址是http://www.bottlepy.org/。其文档可见http://www.bottlepy.org/docs/dev/。bottle库使用起来非常方便,在我们工程的phpdebugserver.py中,我们先引入bottle库
from bottle import route, run
from bottle import static_file
from bottle import template
from bottle import request, response, get, post
route是路由功能。当我们请求一个URL时,Web框架需要将URL中路径信息或者参数信息对应到一个处理逻辑中。比如我们对http://xxxx/cmd进行发送Post请求,则可以通过下面这种路由方式指定处理逻辑
@route('/cmd', method='POST')
def request_cmd_post():ParamValue = request.forms.get("ParamKey")
我们可以从request.forms里获取Post请求的参数,上例中就是获取请求中ParamKey对应的值。
static_file用于返回一个文件。作为一个提供Web服务的服务器,它上面可能保存了一些用户需要直接拉取的文件。最典型的一个例子就是网页中往往包含了一些JavaScript文件。这些文件就可以通过这个接口返回。比如
@route('/files/<filepath:path>')
def highlight_file(filepath):return static_file(filepath, root='views')
当浏览器中请求http://xxxx/files/window.js时,就会被路由到该函数。static_file方法传入的是第一个参数是相对路径,即“files/windows.js”,第二个参数传递的是相对目录对应的根目录。上例中我们传递的是views,则返回的文件是在当前路径下的views/files/windows.js。
template是bottle提供的模板功能。当我们提供一些网页时,其大体的框架是相同的,改变的是其内容实体。比如我们展现编辑已保存的请求的界面时
@route("/request", method='get')
def request_action():
……elif "edit_data" == action:data = rdb.get_request(param_de)return template('component/edit_request', data = json.dumps(data), name = param_en)
其窗体格式样式一样,而内容我们可以通过上述template方法传入
我基本也就使用了上述几个bottle的功能。在发布工程时,把bottle.py这个文件放在工程中即可。
有了Web服务器,我们还需要一个界面框架。这么大一个工具,我们可以想象界面上需要类似桌面系统中软件界面的相关控件,比如ViewList等。当然这些东西也不会是我们从头开始实现,我们还是采用拿来主义。这次我选择的是Jquery EasyUI库。
选择Jquery EasyUI前,我先翻看了下它的支持控件和相关文档。我可以预见的控件基本都在这个框架中被找到,而且其样例和文档也非常丰富。最终我决定选择它作为我们UI框架。Jquery EasyUI的官网主页是http://www.jeasyui.com/;Demo的地址是http://www.jeasyui.com/demo/main/index.php;相关文档的地址是http://www.jeasyui.com/documentation/index.php。
我们先看下网页文件的组织结构
frame.tpl是我们主界面的描述文件,实际它的内容比较空,它只是包含了若干JavaScript文件、样式文件以及界面的组成模板的路径。
<html><head><link rel="stylesheet" type="text/css" href="/files/third/jquery-easyui-1.4.5/themes/default/easyui.css"><link rel="stylesheet" type="text/css" href="/files/third/jquery-easyui-1.4.5/themes/icon.css"><link rel="stylesheet" href="/files/third/highlight/styles/tomorrow-night-eighties.css"></link><link rel="stylesheet" type="text/css" href="/files/themes/debugger.css"><script src="/files/third/jquery3_1/jquery-3.1.0.js" type="text/javascript"></script><script type="text/javascript" src="/files/js/edit_request.js"></script><script type="text/javascript" src="/files/js/debug_setting.js"></script><script type="text/javascript" src="/files/js/tools.js"></script><script type="text/javascript" src="/files/js/modify_variable.js"></script><script type="text/javascript" src="/files/js/request.js"></script><script type="text/javascript" src="/files/js/status.js"></script><script type="text/javascript" src="/files/js/console.js"></script><script type="text/javascript" src="/files/js/variables.js"></script><script type="text/javascript" src="/files/js/call_stack.js"></script><script type="text/javascript" src="/files/js/breakpoint.js"></script><script type="text/javascript" src="/files/js/files_watch.js"></script><script type="text/javascript" src="/files/js/variables_watch.js"></script><script type="text/javascript" src="/files/js/debug.js"></script><script type="text/javascript" src="/files/js/files_tree.js"></script><script type="text/javascript" src="/files/js/view.js"></script><script type="text/javascript" src="/files/third/jquery-easyui-1.4.5/jquery.min.js"></script><script type="text/javascript" src="/files/third/jquery-easyui-1.4.5/jquery.easyui.min.js"></script><script src="/files/third/jquery_timer/jquery.timer.js"></script><script src="/files/third/highlight/highlight.pack.js"></script><script src="/files/third/json-viewer/jquery.json-viewer.js"></script><script>hljs.initHighlightingOnLoad();</script><script src="/files/third/jquery_base64_js/jquery.base64.js"></script><script type="text/javascript">//$.ajaxSetup({// async: false,// cache:false//});$.ajaxSetup({timeout : 10000});function base64_decode(data) {return $.base64.atob(data, true);}function base64_encode(data) {return $.base64.btoa(data);}function highlight_code(type, source) {return hljs.highlight(type, source);}</script><title>Cmd Shell</title></head><body><div id="php_debugger" class="easyui-window" title="Php Debugger" data-options="iconCls:'icon-sum',footer:'#ft'" style="width:100%;height:900px;min-width:900px;min-height:800px;padding:0px;top:0;"><div class="easyui-layout" style="width:100%;height:100%;min-width:800px;padding:0px;">%include('top_menu_layout.tpl')%include('botton_tab_layout.tpl') %include('folder_layout.tpl')%include('source_layout.tpl')%include('request_layout.tpl') </div></div><div id="ft" style="padding:5px;">OFF</div>%include('component/file_menu.tpl')%include('component/console_dlg.tpl')%include('component/add_folder_dlg.tpl')%include('component/edit_request_dlg.tpl')%include('component/save_request_dlg.tpl')%include('component/variables_show_dlg.tpl')%include('component/add_files_watch_dlg.tpl')%include('component/modify_variable_dlg.tpl')%include('component/breakpoint_add_dialog.tpl')%include('component/add_variables_watch_dlg.tpl')%include('component/setting_dlg.tpl')</body>
</html>
上述简单的描述,便可以组织出下图的界面。这种模板组织方式还是非常方便使用的。
除了上述几个大的模板,还有代码中列出的小的模板文件。这些文件一般是一些弹窗界面描述,以console_dlg.tpl为例,它是
由于该调试器界面元素非常多,我也不可能在一篇博文中将所有实现和细节讲完。但是为了契合该博文的标题,我就以上图界面为例,讲解下该界面的实现和工作原理。
我们先看下界面描述内容
<div id="console_dlg" class="easyui-dialog" title="Debug Console" style="width:900px;height:800px;padding:10px" data-options="iconCls:'icon-search',resizable:true,modal:true" closed="true"><div id="console_dlg_div" style="width:100%;height:100%;"><div data-options="region:'center'" style="width:100%;"><div style="margin:0px 0;width:100%;height:100%"><textarea id="console_dlg_view" style="width:100%;height:100%;" readonly="true"></textarea></div></div><div data-options="region:'south'" style="height:26px;width:100%;"><div style="margin:0px 0;width:100%;height:100%"><input id="console_dlg_cmd" class="easyui-textbox" data-options="multiline:false" value="" style="width:100%;height:100%"></div></div></div>
</div>
该界面的ID是console_dlg。它是该窗体的唯一标示,我们在主界面中点下Console按钮后,执行下面Javascript以打开该窗口
function console_dlg_open() {$('#console_dlg').dialog('open').dialog('center');
}
console_dlg的控件类型是其class描述的easyui-dialog。它可以通过东、南、西、北、中五个模块去组合。我们主界面就是通过这五个模块组合的。而console_dlg窗口只使用了中、南两个模块。位于中间的这个模块是调试窗口的输出内容的载体,其核心是一个ID为console_dlg_view的textarea控件。当我们在调试窗口输入调试指令后,它会显示在该区域中,然后该指令的结果也会显示在该区域。这些都是通过下面这个函数实现的
function append_debug_view(text) {var new_text = $('#console_dlg_view')[0].value + "\n" + text;$('#console_dlg_view')[0].value = new_text;var scrollTop = $("#console_dlg_view")[0].scrollHeight ; $("#console_dlg_view").scrollTop(scrollTop);
}
为了更加人性化,第4、5两行实现了滚动条的自动更新。
位于南部的是一个输入框,它的ID是console_dlg_cmd。我们在这个输入框中输入命令,按回车使得命令执行并清除该输入框内容。还可以按上下键翻看前后的历史命令记录。这块内容我们放在窗口初始化后的执行事件中。
var console_cmd_list = [];
var console_cmd_list_index = 0;$(document).ready(function(){$('#console_dlg_cmd').textbox('textbox').bind('keydown', function(e){if (e.keyCode == 13){ // when press ENTER key, accept the inputed value.var cmd = $(this).val();console_cmd_list.push(cmd); console_cmd_list_index = console_cmd_list.length;excute_console_cmd(cmd);$('#console_dlg_cmd').textbox('setValue', '');} else if (e.keyCode == 38){if (console_cmd_list_index > 0) {console_cmd_list_index = console_cmd_list_index -1;var cmd = console_cmd_list[console_cmd_list_index];$('#console_dlg_cmd').textbox('setValue', cmd);}} else if (e.keyCode == 40){if (console_cmd_list_index < console_cmd_list.length - 1) {console_cmd_list_index = console_cmd_list_index +1;var cmd = console_cmd_list[console_cmd_list_index];$('#console_dlg_cmd').textbox('setValue', cmd);}}});$('#console_dlg_div').layout();
});
第10行的excute_console_cmd方法才是界面和我们调试器核心交互的地方。我们看下它的实现
function excute_console_cmd(cmd) {var param = '{"cmd":"' + base64_encode(cmd) + '"}';var param_en = base64_encode(param);$.post("do", {"action":"query", "param":param_en},function(data){append_debug_view(cmd);append_debug_view(eval(data));console.log(data);}, "");
}
第二行我们先组织一个Json型的字符串指令,然后第三行对其进行Base64编码。第四行我们向服务器请求一个路径为do的请求,其参数分为action和param两个部分。在Python的Web服务器层,我们通过Bottle框架的如下代码实现对该请求的响应
@route('/do', method='POST')
def request_do_post():action = request.forms.get("action")if None == action or len(action) == 0:return template('index')param = request.forms.get("param")global debugger(ret,type) = debugger.do(action, param);if type == "json":return json.dumps(ret)else:return template('index', **request.forms)
服务器返回结果后,我们便可以通过append_debug_view方法将内容显示到界面中了。
除了一般的界面,我们还有个非常重要的控件——highlight。它负责将源码文件进行渲染,否则网页中打开的代码可能就是文本文件风格,非常不友好。它的地址是https://highlightjs.org/。目前这个控件还不支持显示行号,所以这块我们做了特殊处理。具体的处理方法可以参见之后公开的源码。
相关文章:

AJAX安全-Session做Token
个人思路,请大神看到了指点 个人理解token是防止扫号机或者恶意注册、恶意发表灌水,有些JS写的token算法,也会被抓出来被利用,个人感觉还是用会过期的Session做token更好,服务器存储,加载到客户端页面&…
跨平台PHP调试器设计及使用方法——使用
经过之前六篇博文的分析和介绍,大家应该对这套调试器有个初步的认识。本文我将讲解它的使用方法。(转载请指明出于breaksoftware的csdn博客) 上图是该软件界面的布局,我们之后的讲解也将围绕着这些功能展开。 文件夹管理 在查看一…
管理7k+工作流,月运行超10000万次,Lyft开源的Flyte平台意味着什么?
作者 | Allyson Gale译者 | 刘畅编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导读】Flyte 平台可以更容易的创建并发,可伸缩和可维护的工作流,从而进行机器学习和数据处理。Flyte 已有三年多的训练模型和数据处理经…

Jmeter组件执行顺序与作用域
一、Jmeter重要组件: 1)配置元件---Config Element: 用于初始化默认值和变量,以便后续采样器使用。配置元件大其作用域的初始阶段处理,配置元件仅对其所在的测试树分支有效,如,在同一个作用域…
跨平台PHP调试器设计及使用方法——拾遗
之前七篇博文讲解了跨平台PHP调试器从立项到实现的整个过程,并讲解了其使用方法。但是它们并不能全部涵盖所有重要内容,所以新开一片博文,用来讲述其中一些杂项。(转载请指明出于breaksoftware的csdn博客) 触发调试的…
召唤超参调优开源新神器:集XGBoost、TensorFlow、PyTorch、MXNet等十大模块于一身...
整理 | 凯隐编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导读】Optuna是一款为机器学习任务设计的自动超参数优化软件框架,是一款按运行定义(define-by-run) 原则设计的优化软件,允许用户动态地调整搜索空间&#…

Linux下的Silverlight:Moonlight 1.0 Beta 1发布了
Moonlight是微软Silverlight的一个开源实现,其目标平台是Linux与Unix/X11系统。自从2007年9月开始,Moonlight就在Mono项目下进行了开发,它是由Novell发起并资助的。现在,Moonlight 1.0 Beta 1已经向公众发布了。 Novell和Mono宣布…
在visual studio 2010中调用ffmpeg
转自:http://blog.sina.com.cn/s/blog_4178f4bf01018wqh.html 最近几天一直在折腾ffmpeg,在网上也查了许多资料,费了不少劲,现在在这里和大家分享一下。 一、准备工作本来是想自己在windows下编译ffmpeg生成lib、dll等库文件的&am…
无线路由器与无线AP的区别
摆脱线缆的羁绊,手捧一杯香醇的咖啡在家中的任何角落都可以无拘无束和网友谈天说地──这就是无线的魅力!在无线网络迅猛发展的今天,无线局域网(Wireless Local-Area Network,简称WLAN)已经成为许多SOHO家庭…

Simple Dynamic Strings(SDS)源码解析和使用说明一
SDS是Redis源码中一个独立的字符串管理库。它是由Redis作者Antirez设计和维护的。一开始,SDS只是Antirez为日常开发而实现的一套字符串库,它被使用在Redis、Disque和Hiredis等作者维护的项目中。但是作者觉得这块功能还是比较独立的,应该让其…
“不会Linux,到底有多危险?”骨灰级成程序员:基本等于自废武功!
说起程序员的必备技能,我想大家都可以说很多,比如:算法、数据结构、数学、编程语言等等。对于程序员来讲,这些底层能力固然重要,但是,工具同样也是如此,比如常被大家所忽视的:Linux。…

“Uncaught TypeError: string is not a function”
http://www.cnblogs.com/haitao-fan/archive/2013/11/08/3414678.html 今天在js中写了一个方法叫做search(),然后点击按钮的时候提示: “Uncaught TypeError: string is not a function” 百思不得其解啊,我的js木有问题啊啊.... 后来才发现酱…

关于Nikon Ai AF 28mm F1.4D遮光罩的问题
-- 好不容易找到百变妖,确实比较妖!!遮光罩不好找,原厂推荐的HK-7基本属于古董中的古董。 爬文很久,终于找到一篇国外的介绍,说可以用HK-4代替,比HK-7效果更好,而且可以用85mm 1.4D-…

Simple Dynamic Strings(SDS)源码解析和使用说明二
在《Simple Dynamic Strings(SDS)源码解析和使用说明一》文中,我们分析了SDS库中数据的基本结构和创建、释放等方法。本文将介绍其一些其他方法及实现。(转载请指明出于breaksoftware的csdn博客) 字符串连接 SDS库提供下面两种方法进行字符串…
亚马逊机器学习服务:深入研究AWS SageMaker
作者 | Manish Manalath译者 | Shawn编辑 | Carol出品 | AI科技大本营(ID: rgznai100) 机器学习是一个从数据中发现模式的强大概念。但是,如果您尝试过从零开始构建机器模型,那么您一定知道设计一个可扩展的机器学习工作流是多大的…

Java Timer 定时器的使用
一、延时执行首先,我们定义一个类,给它取个名字叫TimeTask,我们的定时任务,就在这个类的main函数里执行。 代码如下:package test;import java.util.Timer;public class TimeTaskTest { public static void main(Str…
Redis源码解析——前言
今天开启Redis源码的阅读之旅。对于一些没有接触过开源代码分析的同学来说,可能这是一件很麻烦的事。但是我总觉得做一件事,不管有多大多难,我们首先要在战略上蔑视它,但是要在战术上重视它。除了一些高大上的技术,我们…

asp.net客户端脚本验证小技巧
通用的客户端脚本验证 Code//验证客户端function checkclient() { var list document.all; for(var i0 ;i<list.length; i) { var h list[i].hint; if(h ! null && h ! "") { if(list[i].isdrop"…
5个可以帮助你提高工作效率的新AI工具
作者 | Kyrylo Lyzanets译者 | 火火酱编辑 | Carol出品 | AI科技大本营(ID: rgznai100) 毫无意义的新闻、故事和活动会占用你每天多少的工作时间?假如你是一名需要高绩效的高管或专业人士,如果在工作中可以不分心,那你…

Centos6.5更换163源 epel源
想必大家都遇到过,安装新的centos系统,使用yum去安装软件的时候,要么找不到,要么慢的让人发疯。网上其实办法很多,直接更换163源就ok,但是基本所有的文章都是直接wget下163的源,但是不知道为什么…
图模型+Bert香不香?完全基于注意力机制的图表征学习模型Graph-Bert
作者 | Jiawei Zhang、Haopeng Zhang、Congying Xia、Li Sun译者 | 凯隐编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导读】本文提出了一种全新的图神经网络 Graph-Bert,仅仅基于 Attention 机制而不依赖任何类卷积或聚合操作…

闭关纪要17.Google app engine的简单应用
在上面用了十一篇博客的文章详细的介绍了,Step1账户登录系统之后,从现在开始,继续写闭关纪要,因为Step1账户登录系统也是闭关工作的一部分,因此保留序号,这篇纪要在上次的闭关纪要5.WML,UTF-8,BOM,签名及其…
Redis源码解析——内存管理
在《Redis源码解析——源码工程结构》一文中,我们介绍了Redis可能会根据环境或用户指定选择不同的内存管理库。在linux系统中,Redis默认使用jemalloc库。当然用户可以指定使用tcmalloc或者libc的原生内存管理库。本文介绍的内容是在这些库的基础上&#…

poj_2479 动态规划
题目大意 给定一列数,从中选择两个不相交的连续子段,求这两个连续子段和的最大值。 题目分析 典型的M子段和的问题,使用动态规划的方法来解决。 f[i][j] 表示将A[1...i] 划分为j个不相交连续子串,且A[j]属于第i个子串,…
Redis源码解析——字典结构
C语言中有标准的字典库,我们可以通过pair(key,value)的形式存储数据。但是C语言中没有这种的库,于是就需要自己实现。本文讲解的就是Redis源码中的字典库的实现方法。(转载请指明出于breaksoftware的csdn博客) 一般情况下…
十步,教你把Python运行速度提升 30%
作者 | Martin Heinz译者 | 陆离编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导读】一直以来,诟病 Python语言的人经常说,他们不想使用的一个原因是 Python 的速度太慢了。不管使用哪一种编程语言,程序…

字符串转换成NSDate类型的 为nil解决方法
方法一 通过下列函数来解决 但是得到的日期会改变 修改方法fix- (NSDate *)timeForString:(NSString *)string {NSMutableString *timeString [[NSMutableString alloc] initWithString:string]; [timeString setString:[timeString stringByReplacingOccurrence…
Redis源码解析——字典基本操作
有了《Redis源码解析——字典结构》的基础,我们便可以对dict的实现进行展开分析。(转载请指明出于breaksoftware的csdn博客) 创建字典 一般字典创建时,都是没有数据的,但是字典类型需要确定,所以我们看到R…

[转]控制 Cookie 的作用范围
默认时,网站的所有 Cookies 都一起被存储在客户端,并且所有 Cookies 连同网站的任何请求一起被发送到服务器。换句话说,网站中的每个页面都能够为网站获取所有的 Cookies。但是,你能够通过两个方式来设置 Cookies 的作用范围&…
强化学习70年演进:从精确动态规划到基于模型
作者 | Nathan Lambert译者 | 泓礼编辑 | 夕颜出品 | AI科技大本营(ID: rgznai100)【导读】这是一份帮你了解强化学习算法本质的资源,无需浏览大量文档,没有一条公式,非常适合学生和研究人员阅读。作为强化学习研究人员…