Improve nixos-gui:
- Can navigate in the tree of option. - Provide information on options. svn path=/nixos/trunk/; revision=26945
This commit is contained in:
parent
ba2d96cf85
commit
0265bff844
@ -32,4 +32,4 @@ MinVersion=1.9a5
|
|||||||
; This field is optional. It specifies the maximum Gecko version that this
|
; This field is optional. It specifies the maximum Gecko version that this
|
||||||
; application requires. It should be specified if your application uses
|
; application requires. It should be specified if your application uses
|
||||||
; unfrozen interfaces.
|
; unfrozen interfaces.
|
||||||
;MaxVersion=1.9.0.*
|
MaxVersion=2.*
|
||||||
|
1
gui/chrome.manifest
Normal file
1
gui/chrome.manifest
Normal file
@ -0,0 +1 @@
|
|||||||
|
manifest chrome/chrome.manifest
|
@ -1 +1 @@
|
|||||||
content nixos-gui content/nixos-gui/
|
content nixos-gui content/
|
||||||
|
@ -1,3 +1,66 @@
|
|||||||
|
|
||||||
|
function inspect(obj, maxLevels, level)
|
||||||
|
{
|
||||||
|
var str = '', type, msg;
|
||||||
|
|
||||||
|
// Start Input Validations
|
||||||
|
// Don't touch, we start iterating at level zero
|
||||||
|
if(level == null) level = 0;
|
||||||
|
|
||||||
|
// At least you want to show the first level
|
||||||
|
if(maxLevels == null) maxLevels = 1;
|
||||||
|
if(maxLevels < 1)
|
||||||
|
return '<font color="red">Error: Levels number must be > 0</font>';
|
||||||
|
|
||||||
|
// We start with a non null object
|
||||||
|
if(obj == null)
|
||||||
|
return '<font color="red">Error: Object <b>NULL</b></font>';
|
||||||
|
// End Input Validations
|
||||||
|
|
||||||
|
// Each Iteration must be indented
|
||||||
|
str += '<ul>';
|
||||||
|
|
||||||
|
// Start iterations for all objects in obj
|
||||||
|
for(property in obj)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Show "property" and "type property"
|
||||||
|
type = typeof(obj[property]);
|
||||||
|
str += '<li>(' + type + ') ' + property +
|
||||||
|
( (obj[property]==null)?(': <b>null</b>'):('')) + '</li>';
|
||||||
|
|
||||||
|
// We keep iterating if this property is an Object, non null
|
||||||
|
// and we are inside the required number of levels
|
||||||
|
if((type == 'object') && (obj[property] != null) && (level+1 < maxLevels))
|
||||||
|
str += inspect(obj[property], maxLevels, level+1);
|
||||||
|
}
|
||||||
|
catch(err)
|
||||||
|
{
|
||||||
|
// Is there some properties in obj we can't access? Print it red.
|
||||||
|
if(typeof(err) == 'string') msg = err;
|
||||||
|
else if(err.message) msg = err.message;
|
||||||
|
else if(err.description) msg = err.description;
|
||||||
|
else msg = 'Unknown';
|
||||||
|
|
||||||
|
str += '<li><font color="red">(Error) ' + property + ': ' + msg +'</font></li>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close indent
|
||||||
|
str += '</ul>';
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run xulrunner application.ini -jsconsole -console, to see messages.
|
||||||
|
function log(str)
|
||||||
|
{
|
||||||
|
Components.classes['@mozilla.org/consoleservice;1']
|
||||||
|
.getService(Components.interfaces.nsIConsoleService)
|
||||||
|
.logStringMessage(str);
|
||||||
|
}
|
||||||
|
|
||||||
function makeTempFile(prefix)
|
function makeTempFile(prefix)
|
||||||
{
|
{
|
||||||
var file = Components.classes["@mozilla.org/file/directory_service;1"]
|
var file = Components.classes["@mozilla.org/file/directory_service;1"]
|
||||||
@ -29,7 +92,7 @@ function readFromFile(file)
|
|||||||
var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
|
var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
|
||||||
.createInstance(Components.interfaces.nsIScriptableInputStream);
|
.createInstance(Components.interfaces.nsIScriptableInputStream);
|
||||||
fstream.init(file, -1, 0, 0);
|
fstream.init(file, -1, 0, 0);
|
||||||
sstream.init(fstream);
|
sstream.init(fstream);
|
||||||
|
|
||||||
var str = sstream.read(4096);
|
var str = sstream.read(4096);
|
||||||
while (str.length > 0) {
|
while (str.length > 0) {
|
40
gui/chrome/content/main.js
Normal file
40
gui/chrome/content/main.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// global variables.
|
||||||
|
var gNixOS;
|
||||||
|
var gOptionView;
|
||||||
|
|
||||||
|
/*
|
||||||
|
var gProgressBar;
|
||||||
|
function setProgress(current, max)
|
||||||
|
{
|
||||||
|
if (gProgressBar) {
|
||||||
|
gProgressBar.value = 100 * current / max;
|
||||||
|
log("progress: " + gProgressBar.value + "%");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
log("unknow progress bar");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function updatePanel(options)
|
||||||
|
{
|
||||||
|
log("updatePanel: " + options.length);
|
||||||
|
var t = "";
|
||||||
|
for (var i = 0; i < options.length; i++)
|
||||||
|
{
|
||||||
|
log("Called with " + options[i].path);
|
||||||
|
t += options[i].description;
|
||||||
|
}
|
||||||
|
$("#option-desc").text(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onload()
|
||||||
|
{
|
||||||
|
var optionTree = document.getElementById("option-tree");
|
||||||
|
// gProgressBar = document.getElementById("progress-bar");
|
||||||
|
// setProgress(0, 1);
|
||||||
|
|
||||||
|
gNixOS = new NixOS();
|
||||||
|
gOptionView = new OptionView(gNixOS.option, updatePanel);
|
||||||
|
optionTree.view = gOptionView;
|
||||||
|
}
|
42
gui/chrome/content/myviewer.xul
Normal file
42
gui/chrome/content/myviewer.xul
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||||
|
|
||||||
|
<!DOCTYPE window>
|
||||||
|
|
||||||
|
<!-- To edit this file I recommend you to use:
|
||||||
|
http://xulfr.org/outils/xulediteur.xul
|
||||||
|
-->
|
||||||
|
|
||||||
|
<window
|
||||||
|
id = "nixos-gui"
|
||||||
|
title = "NixOS gui"
|
||||||
|
width = "800"
|
||||||
|
height = "600"
|
||||||
|
xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
|
<script src="jquery-1.3.2.js"/>
|
||||||
|
<script src="io.js"/>
|
||||||
|
<script src="nixos.js"/>
|
||||||
|
<script src="optionView.js"/>
|
||||||
|
<script src="main.js"/>
|
||||||
|
<columns height="580">
|
||||||
|
<column width = "300">
|
||||||
|
<tree flex="1" id="option-tree" persist="height" onselect="gOptionView.selectionChanged()">
|
||||||
|
<treecols>
|
||||||
|
<treecol persist="hidden width" flex="1" id="opt-name"
|
||||||
|
label="Option" primary="true"/>
|
||||||
|
<!-- Uncomment the following column to see the number of option
|
||||||
|
printed below each options. -->
|
||||||
|
<!--
|
||||||
|
<treecol persist="hidden width" flex="1" id="dbg-size"
|
||||||
|
label="Size"/>
|
||||||
|
-->
|
||||||
|
</treecols>
|
||||||
|
<treechildren id="first-child" flex="1"/>
|
||||||
|
</tree>
|
||||||
|
</column>
|
||||||
|
<column flex="1">
|
||||||
|
<description id="option-desc" hidden="false"></description>
|
||||||
|
</column>
|
||||||
|
</columns>
|
||||||
|
<!-- <progressmeter id="progress-bar" value="0%"/> -->
|
||||||
|
</window>
|
@ -1,171 +0,0 @@
|
|||||||
var COPYCOL = 2;
|
|
||||||
var gOptionListView = new treeView(["opt-success","opt-name","opt-desc"],
|
|
||||||
COPYCOL);
|
|
||||||
|
|
||||||
// Run xulrunner application.ini -jsconsole -console, to see messages.
|
|
||||||
function log(str)
|
|
||||||
{
|
|
||||||
Components.classes['@mozilla.org/consoleservice;1']
|
|
||||||
.getService(Components.interfaces.nsIConsoleService)
|
|
||||||
.logStringMessage(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// return the DOM of the value returned by nix-instantiate
|
|
||||||
function dumpOptions(path)
|
|
||||||
{
|
|
||||||
var nixInstantiate = "nix-instantiate"; // "@nix@/bin/nix-instantiate";
|
|
||||||
var nixos = "/etc/nixos/nixos/default.nix"; // "@nixos@/default.nix";
|
|
||||||
|
|
||||||
var o = makeTempFile("nixos-options");
|
|
||||||
|
|
||||||
path = "eval.options" + (path? "." + path : "");
|
|
||||||
log("retrieve options from: " + path);
|
|
||||||
|
|
||||||
runProgram(nixInstantiate+" "+nixos+" -A "+path+" --eval-only --strict --xml 2>/dev/null | tr -d '' >" + o.path);
|
|
||||||
|
|
||||||
var xml = readFromFile(o);
|
|
||||||
o.remove(false);
|
|
||||||
|
|
||||||
// jQuery does a stack overflow when converting the XML to a DOM.
|
|
||||||
var dom = DOMParser().parseFromString(xml, "text/xml");
|
|
||||||
|
|
||||||
log("return dom");
|
|
||||||
return dom;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Pretty print Nix values.
|
|
||||||
function nixPP(value, level)
|
|
||||||
{
|
|
||||||
function indent(level) { ret = ""; while (level--) ret+= " "; return ret; }
|
|
||||||
|
|
||||||
if (!level) level = 0;
|
|
||||||
var ret = "<no match>";
|
|
||||||
if (value.is("attrs")) {
|
|
||||||
var content = "";
|
|
||||||
value.children().each(function (){
|
|
||||||
var name = $(this).attr("name");
|
|
||||||
var value = nixPP($(this).children(), level + 1);
|
|
||||||
content += indent(level + 1) + name + " = " + value + ";\n";
|
|
||||||
});
|
|
||||||
ret = "{\n" + content + indent(level) + "}";
|
|
||||||
}
|
|
||||||
else if (value.is("list")) {
|
|
||||||
var content = "";
|
|
||||||
value.children().each(function (){
|
|
||||||
content += indent(level + 1) + "(" + nixPP($(this), level + 1) + ")\n";
|
|
||||||
});
|
|
||||||
ret = "[\n" + content + indent(level) + "]";
|
|
||||||
}
|
|
||||||
else if (value.is("bool"))
|
|
||||||
ret = (value.attr("value") == "true");
|
|
||||||
else if (value.is("string"))
|
|
||||||
ret = '"' + value.attr("value") + '"';
|
|
||||||
else if (value.is("path"))
|
|
||||||
ret = value.attr("value");
|
|
||||||
else if (value.is("int"))
|
|
||||||
ret = parseInt(value.attr("value"));
|
|
||||||
else if (value.is("derivation"))
|
|
||||||
ret = value.attr("outPath");
|
|
||||||
else if (value.is("function"))
|
|
||||||
ret = "<function>";
|
|
||||||
else {
|
|
||||||
var content = "";
|
|
||||||
value.children().each(function (){
|
|
||||||
content += indent(level + 1) + "(" + nixPP($(this), level + 1) + ")\n";
|
|
||||||
});
|
|
||||||
ret = "<!--" + value.selector + "--><!--\n" + content + indent(level) + "-->";
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function used to reproduce the select operator on the XML DOM.
|
|
||||||
// It return the value contained in the targeted attribute.
|
|
||||||
function nixSelect(attrs, selector)
|
|
||||||
{
|
|
||||||
var names = selector.split(".");
|
|
||||||
var value = $(attrs);
|
|
||||||
for (var i = 0; i < names.length; i++) {
|
|
||||||
log(nixPP(value) + "." + names[i]);
|
|
||||||
if (value.is("attrs"))
|
|
||||||
value = value.children("attr[name='" + names[i] + "']").children();
|
|
||||||
else {
|
|
||||||
log("Cannot do an attribute selection.");
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log("nixSelect return: " + nixPP(value));
|
|
||||||
|
|
||||||
var ret;
|
|
||||||
if (value.is("attrs") || value.is("list"))
|
|
||||||
ret = value;
|
|
||||||
else if (value.is("bool"))
|
|
||||||
ret = value.attr("value") == "true";
|
|
||||||
else if (value.is("string"))
|
|
||||||
ret = value.attr("value");
|
|
||||||
else if (value.is("int"))
|
|
||||||
ret = parseInt(value.attr("value"));
|
|
||||||
else if (value.is("derivation"))
|
|
||||||
ret = value.attr("outPath");
|
|
||||||
else if (value.is("function"))
|
|
||||||
ret = "<function>";
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
var gProgressBar;
|
|
||||||
function setProgress(current, max)
|
|
||||||
{
|
|
||||||
if (gProgressBar) {
|
|
||||||
gProgressBar.value = 100 * current / max;
|
|
||||||
log("progress: " + gProgressBar.value + "%");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
log("unknow progress bar");
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill the list of options
|
|
||||||
function setOptionList(optionDOM)
|
|
||||||
{
|
|
||||||
var options = $("attrs", optionDOM).filter(function () {
|
|
||||||
return $(this)
|
|
||||||
.children("attr[name='_type']")
|
|
||||||
.children("string[value='option']")
|
|
||||||
.length != 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
var max = options.length;
|
|
||||||
|
|
||||||
log("Number of options: " + max);
|
|
||||||
|
|
||||||
setProgress(0, max);
|
|
||||||
gOptionListView.clear();
|
|
||||||
options.each(function (index){
|
|
||||||
var success = nixSelect(this, "config.success");
|
|
||||||
var name = nixSelect(this, "name");
|
|
||||||
var desc = nixSelect(this, "description");
|
|
||||||
if (success && name && desc) {
|
|
||||||
log("Add option '" + name + "' in the list.");
|
|
||||||
gOptionListView.addRow([success, name, desc]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
log("A problem occur while scanning an option.");
|
|
||||||
setProgress(index + 1, max);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function onload()
|
|
||||||
{
|
|
||||||
var optionList = document.getElementById("option-list");
|
|
||||||
gProgressBar = document.getElementById("progress-bar");
|
|
||||||
setProgress(0, 1);
|
|
||||||
optionList.view = gOptionListView;
|
|
||||||
|
|
||||||
// try to avoid blocking the rendering, unfortunately this is not perfect.
|
|
||||||
setTimeout(function (){
|
|
||||||
setOptionList(dumpOptions("hardware"));}
|
|
||||||
, 100);
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
|
||||||
|
|
||||||
<!DOCTYPE window>
|
|
||||||
|
|
||||||
<!-- To edit this file I recommend you to use:
|
|
||||||
http://xulfr.org/outils/xulediteur.xul
|
|
||||||
-->
|
|
||||||
|
|
||||||
<window
|
|
||||||
id = "nixos-gui"
|
|
||||||
title = "NixOS gui"
|
|
||||||
xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
||||||
<script src="jquery-1.3.2.js"/>
|
|
||||||
<script src="treeView.js"/>
|
|
||||||
<script src="main.js"/>
|
|
||||||
<script src="io.js"/>
|
|
||||||
<tree flex="1" id="option-list" persist="height">
|
|
||||||
<treecols>
|
|
||||||
<treecol persist="hidden width" flex="1" id="opt-success" label="Success"/>
|
|
||||||
<splitter class="tree-splitter"/>
|
|
||||||
<treecol persist="hidden width" flex="30" id="opt-name" label="Option"/>
|
|
||||||
<splitter class="tree-splitter"/>
|
|
||||||
<treecol persist="hidden width" flex="50" id="opt-desc" label="Description"
|
|
||||||
primary="true"/>
|
|
||||||
</treecols>
|
|
||||||
<treechildren id="first-child" flex="1"/>
|
|
||||||
</tree>
|
|
||||||
<progressmeter id="progress-bar" value="0%"/>
|
|
||||||
</window>
|
|
@ -1,117 +0,0 @@
|
|||||||
// Taken from pageInfo.js
|
|
||||||
|
|
||||||
//******** define a js object to implement nsITreeView
|
|
||||||
function treeView(columnids, copycol)
|
|
||||||
{
|
|
||||||
// columnids is an array of strings indicating the names of the columns, in order
|
|
||||||
this.columnids = columnids;
|
|
||||||
this.colcount = columnids.length;
|
|
||||||
|
|
||||||
// copycol is the index number for the column that we want to add to
|
|
||||||
// the copy-n-paste buffer when the user hits accel-c
|
|
||||||
this.copycol = copycol;
|
|
||||||
this.rows = 0;
|
|
||||||
this.tree = null;
|
|
||||||
this.data = [ ];
|
|
||||||
this.selection = null;
|
|
||||||
this.sortcol = null;
|
|
||||||
this.sortdir = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
treeView.prototype = {
|
|
||||||
set rowCount(c) { throw "rowCount is a readonly property"; },
|
|
||||||
get rowCount() { return this.rows; },
|
|
||||||
|
|
||||||
setTree: function(tree)
|
|
||||||
{
|
|
||||||
this.tree = tree;
|
|
||||||
},
|
|
||||||
|
|
||||||
getCellText: function(row, column)
|
|
||||||
{
|
|
||||||
// row can be null, but js arrays are 0-indexed.
|
|
||||||
// colidx cannot be null, but can be larger than the number
|
|
||||||
// of columns in the array (when column is a string not in
|
|
||||||
// this.columnids.) In this case it's the fault of
|
|
||||||
// whoever typoed while calling this function.
|
|
||||||
return this.data[row][column.index] || "";
|
|
||||||
},
|
|
||||||
|
|
||||||
setCellValue: function(row, column, value)
|
|
||||||
{
|
|
||||||
},
|
|
||||||
|
|
||||||
setCellText: function(row, column, value)
|
|
||||||
{
|
|
||||||
this.data[row][column.index] = value;
|
|
||||||
},
|
|
||||||
|
|
||||||
addRow: function(row)
|
|
||||||
{
|
|
||||||
this.rows = this.data.push(row);
|
|
||||||
this.rowCountChanged(this.rows - 1, 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
addRows: function(rows)
|
|
||||||
{
|
|
||||||
var length = rows.length;
|
|
||||||
for(var i = 0; i < length; i++)
|
|
||||||
this.rows = this.data.push(rows[i]);
|
|
||||||
this.rowCountChanged(this.rows - length, length);
|
|
||||||
},
|
|
||||||
|
|
||||||
rowCountChanged: function(index, count)
|
|
||||||
{
|
|
||||||
this.tree.rowCountChanged(index, count);
|
|
||||||
},
|
|
||||||
|
|
||||||
invalidate: function()
|
|
||||||
{
|
|
||||||
this.tree.invalidate();
|
|
||||||
},
|
|
||||||
|
|
||||||
clear: function()
|
|
||||||
{
|
|
||||||
if (this.tree)
|
|
||||||
this.tree.rowCountChanged(0, -this.rows);
|
|
||||||
this.rows = 0;
|
|
||||||
this.data = [ ];
|
|
||||||
},
|
|
||||||
|
|
||||||
handleCopy: function(row)
|
|
||||||
{
|
|
||||||
return (row < 0 || this.copycol < 0) ? "" : (this.data[row][this.copycol] || "");
|
|
||||||
},
|
|
||||||
|
|
||||||
performActionOnRow: function(action, row)
|
|
||||||
{
|
|
||||||
if (action == "copy") {
|
|
||||||
var data = this.handleCopy(row)
|
|
||||||
this.tree.treeBody.parentNode.setAttribute("copybuffer", data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getRowProperties: function(row, prop) { },
|
|
||||||
getCellProperties: function(row, column, prop) { },
|
|
||||||
getColumnProperties: function(column, prop) { },
|
|
||||||
isContainer: function(index) { return false; },
|
|
||||||
isContainerOpen: function(index) { return false; },
|
|
||||||
isSeparator: function(index) { return false; },
|
|
||||||
isSorted: function() { },
|
|
||||||
canDrop: function(index, orientation) { return false; },
|
|
||||||
drop: function(row, orientation) { return false; },
|
|
||||||
getParentIndex: function(index) { return 0; },
|
|
||||||
hasNextSibling: function(index, after) { return false; },
|
|
||||||
getLevel: function(index) { return 0; },
|
|
||||||
getImageSrc: function(row, column) { },
|
|
||||||
getProgressMode: function(row, column) { },
|
|
||||||
getCellValue: function(row, column) { },
|
|
||||||
toggleOpenState: function(index) { },
|
|
||||||
cycleHeader: function(col) { },
|
|
||||||
selectionChanged: function() { },
|
|
||||||
cycleCell: function(row, column) { },
|
|
||||||
isEditable: function(row, column) { return false; },
|
|
||||||
isSelectable: function(row, column) { return false; },
|
|
||||||
performAction: function(action) { },
|
|
||||||
performActionOnCell: function(action, row, column) { }
|
|
||||||
};
|
|
205
gui/chrome/content/nixos.js
Normal file
205
gui/chrome/content/nixos.js
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
|
||||||
|
function NixOS () {
|
||||||
|
var env = Components.classes["@mozilla.org/process/environment;1"].
|
||||||
|
getService(Components.interfaces.nsIEnvironment);
|
||||||
|
|
||||||
|
if (env.exists("NIXOS"))
|
||||||
|
this.nixos = env.get("NIXOS");
|
||||||
|
if (env.exists("NIXOS_CONFIG"))
|
||||||
|
this.config = env.get("NIXOS_CONFIG");
|
||||||
|
if (env.exists("NIXPKGS"))
|
||||||
|
this.nixpkgs = env.get("NIXPKGS");
|
||||||
|
if (env.exists("mountPoint"))
|
||||||
|
this.root = env.get("mountPoint");
|
||||||
|
if (env.exists("NIXOS_OPTION"))
|
||||||
|
this.optionBin = env.get("NIXOS_OPTION");
|
||||||
|
this.option = new Option("options", this, null);
|
||||||
|
};
|
||||||
|
|
||||||
|
NixOS.prototype = {
|
||||||
|
root: "",
|
||||||
|
nixos: "/etc/nixos/nixos",
|
||||||
|
nixpkgs: "/etc/nixos/nixpkgs",
|
||||||
|
config: "/etc/nixos/configuration.nix",
|
||||||
|
instantiateBin: "/var/run/current-system/sw/bin/nix-instantiate",
|
||||||
|
optionBin: "/var/run/current-system/sw/bin/nixos-option",
|
||||||
|
tmpFile: "nixos-gui",
|
||||||
|
option: null
|
||||||
|
};
|
||||||
|
|
||||||
|
function Option (name, context, parent) {
|
||||||
|
this.name = name;
|
||||||
|
this.context_ = context;
|
||||||
|
if (parent == null)
|
||||||
|
this.path = "";
|
||||||
|
else if (parent.path == "")
|
||||||
|
this.path = name;
|
||||||
|
else
|
||||||
|
this.path = parent.path + "." + name;
|
||||||
|
};
|
||||||
|
|
||||||
|
Option.prototype = {
|
||||||
|
load: function () {
|
||||||
|
var env = "";
|
||||||
|
env += "'NIXOS=" + this.context_.root + this.context_.nixos + "' ";
|
||||||
|
env += "'NIXOS_PKGS=" + this.context_.root + this.context_.nixpkgs + "' ";
|
||||||
|
env += "'NIXOS_CONFIG=" + this.context_.config + "' ";
|
||||||
|
var out = makeTempFile(this.context_.tmpFile);
|
||||||
|
var prog = this.context_.instantiateBin + " 2>&1 >" + out.path + " ";
|
||||||
|
var args = "";
|
||||||
|
args += " -A eval.options" + (this.path != "" ? "." : "") + this.path;
|
||||||
|
args += " --eval-only --xml --no-location";
|
||||||
|
args += " '" + this.context_.root + this.context_.nixos + "'";
|
||||||
|
|
||||||
|
runProgram(/*env +*/ prog + args);
|
||||||
|
var xml = readFromFile(out);
|
||||||
|
out.remove(false);
|
||||||
|
|
||||||
|
// jQuery does a stack overflow when converting a huge XML to a DOM.
|
||||||
|
var dom = DOMParser().parseFromString(xml, "text/xml");
|
||||||
|
var xmlAttrs = $("attr", dom);
|
||||||
|
|
||||||
|
this.isOption = xmlAttrs
|
||||||
|
.filter (
|
||||||
|
function (idx) {
|
||||||
|
return $(this).attr("name") == "_type";
|
||||||
|
// !!! We could not rely on the value of the attribute because it
|
||||||
|
// !!! may be unevaluated.
|
||||||
|
// $(this).children("string[value='option']").length != 0;
|
||||||
|
})
|
||||||
|
.length != 0;
|
||||||
|
|
||||||
|
if (!this.isOption)
|
||||||
|
{
|
||||||
|
var cur = this;
|
||||||
|
var attrs = new Array();
|
||||||
|
|
||||||
|
xmlAttrs.each(
|
||||||
|
function (index) {
|
||||||
|
var name = $(this).attr("name");
|
||||||
|
var attr = new Option(name, cur.context_, cur);
|
||||||
|
attrs.push(attr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.subOptions = attrs;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.loadDesc();
|
||||||
|
// TODO: handle sub-options here.
|
||||||
|
}
|
||||||
|
this.isLoaded = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
loadDesc: function () {
|
||||||
|
var env = "";
|
||||||
|
env += "'NIXOS=" + this.context_.root + this.context_.nixos + "' ";
|
||||||
|
env += "'NIXOS_PKGS=" + this.context_.root + this.context_.nixpkgs + "' ";
|
||||||
|
env += "'NIXOS_CONFIG=" + this.context_.config + "' ";
|
||||||
|
var out = makeTempFile(this.context_.tmpFile);
|
||||||
|
var prog = this.context_.optionBin + " 2>&1 >" + out.path + " ";
|
||||||
|
var args = " -vdl " + this.path;
|
||||||
|
|
||||||
|
runProgram(/*env + */ prog + args);
|
||||||
|
this.description = readFromFile(out);
|
||||||
|
out.remove(false);
|
||||||
|
},
|
||||||
|
|
||||||
|
// keep the context under which this option has been used.
|
||||||
|
context_: null,
|
||||||
|
// name of the option.
|
||||||
|
name: "",
|
||||||
|
// result of nixos-option.
|
||||||
|
description: "",
|
||||||
|
// path to reach this option
|
||||||
|
path: "",
|
||||||
|
|
||||||
|
// list of options accessible from here.
|
||||||
|
isLoaded: false,
|
||||||
|
isOption: false,
|
||||||
|
subOptions: []
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Pretty print Nix values.
|
||||||
|
function nixPP(value, level)
|
||||||
|
{
|
||||||
|
function indent(level) { ret = ""; while (level--) ret+= " "; return ret; }
|
||||||
|
|
||||||
|
if (!level) level = 0;
|
||||||
|
var ret = "<no match>";
|
||||||
|
if (value.is("attrs")) {
|
||||||
|
var content = "";
|
||||||
|
value.children().each(function (){
|
||||||
|
var name = $(this).attr("name");
|
||||||
|
var value = nixPP($(this).children(), level + 1);
|
||||||
|
content += indent(level + 1) + name + " = " + value + ";\n";
|
||||||
|
});
|
||||||
|
ret = "{\n" + content + indent(level) + "}";
|
||||||
|
}
|
||||||
|
else if (value.is("list")) {
|
||||||
|
var content = "";
|
||||||
|
value.children().each(function (){
|
||||||
|
content += indent(level + 1) + "(" + nixPP($(this), level + 1) + ")\n";
|
||||||
|
});
|
||||||
|
ret = "[\n" + content + indent(level) + "]";
|
||||||
|
}
|
||||||
|
else if (value.is("bool"))
|
||||||
|
ret = (value.attr("value") == "true");
|
||||||
|
else if (value.is("string"))
|
||||||
|
ret = '"' + value.attr("value") + '"';
|
||||||
|
else if (value.is("path"))
|
||||||
|
ret = value.attr("value");
|
||||||
|
else if (value.is("int"))
|
||||||
|
ret = parseInt(value.attr("value"));
|
||||||
|
else if (value.is("derivation"))
|
||||||
|
ret = value.attr("outPath");
|
||||||
|
else if (value.is("function"))
|
||||||
|
ret = "<function>";
|
||||||
|
else {
|
||||||
|
var content = "";
|
||||||
|
value.children().each(function (){
|
||||||
|
content += indent(level + 1) + "(" + nixPP($(this), level + 1) + ")\n";
|
||||||
|
});
|
||||||
|
ret = "<!--" + value.selector + "--><!--\n" + content + indent(level) + "-->";
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function used to reproduce the select operator on the XML DOM.
|
||||||
|
// It return the value contained in the targeted attribute.
|
||||||
|
function nixSelect(attrs, selector)
|
||||||
|
{
|
||||||
|
var names = selector.split(".");
|
||||||
|
var value = $(attrs);
|
||||||
|
for (var i = 0; i < names.length; i++) {
|
||||||
|
log(nixPP(value) + "." + names[i]);
|
||||||
|
if (value.is("attrs"))
|
||||||
|
value = value.children("attr[name='" + names[i] + "']").children();
|
||||||
|
else {
|
||||||
|
log("Cannot do an attribute selection.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log("nixSelect return: " + nixPP(value));
|
||||||
|
|
||||||
|
var ret;
|
||||||
|
if (value.is("attrs") || value.is("list"))
|
||||||
|
ret = value;
|
||||||
|
else if (value.is("bool"))
|
||||||
|
ret = value.attr("value") == "true";
|
||||||
|
else if (value.is("string"))
|
||||||
|
ret = value.attr("value");
|
||||||
|
else if (value.is("int"))
|
||||||
|
ret = parseInt(value.attr("value"));
|
||||||
|
else if (value.is("derivation"))
|
||||||
|
ret = value.attr("outPath");
|
||||||
|
else if (value.is("function"))
|
||||||
|
ret = "<function>";
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
*/
|
233
gui/chrome/content/optionView.js
Normal file
233
gui/chrome/content/optionView.js
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
// extend NixOS options to handle the Tree View. Should be better to keep a
|
||||||
|
// separation of concern here.
|
||||||
|
|
||||||
|
Option.prototype.tv_opened = false;
|
||||||
|
Option.prototype.tv_size = 1;
|
||||||
|
|
||||||
|
Option.prototype.tv_open = function () {
|
||||||
|
this.tv_opened = true;
|
||||||
|
this.tv_size = 1;
|
||||||
|
|
||||||
|
// load an option if it is not loaded yet, and initialize them to be
|
||||||
|
// read by the Option view.
|
||||||
|
if (!this.isLoaded)
|
||||||
|
this.load();
|
||||||
|
|
||||||
|
// If this is not an option, then add it's lits of sub-options size.
|
||||||
|
if (!this.isOption)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < this.subOptions.length; i++)
|
||||||
|
this.tv_size += this.subOptions[i].tv_size;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Option.prototype.tv_close = function () {
|
||||||
|
this.tv_opened = false;
|
||||||
|
this.tv_size = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function OptionView (root, selCallback) {
|
||||||
|
root.tv_open();
|
||||||
|
this.rootOption = root;
|
||||||
|
this.selCallback = selCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionView.prototype = {
|
||||||
|
rootOption: null,
|
||||||
|
selCallback: null,
|
||||||
|
|
||||||
|
// This function returns the path to option which is at the specified row.
|
||||||
|
reach_cache: null,
|
||||||
|
reachRow: function (row) {
|
||||||
|
var o = this.rootOption; // Current option.
|
||||||
|
var r = 0; // Number of rows traversed.
|
||||||
|
var c = 0; // Child index.
|
||||||
|
var path = [{ row: r, opt: o }]; // new Array();
|
||||||
|
// hypothesis: this.rootOption.tv_size is always open and bigger than
|
||||||
|
|
||||||
|
// Use the previous returned value to avoid making to many checks and to
|
||||||
|
// optimize for frequent access of near rows.
|
||||||
|
if (this.reach_cache != null)
|
||||||
|
{
|
||||||
|
for (var i = this.reach_cache.length - 2; i >= 0; i--) {
|
||||||
|
var p = this.reach_cache[i];
|
||||||
|
// If we will have to go the same path.
|
||||||
|
if (row >= p.row && row < p.row + p.opt.tv_size)
|
||||||
|
{
|
||||||
|
path.unshift(p);
|
||||||
|
r = path[0].row;
|
||||||
|
o = path[0].opt;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
while (r != row)
|
||||||
|
{
|
||||||
|
// Go deeper in the child which contains the requested row. The
|
||||||
|
// tv_size contains the size of the tree starting from each option.
|
||||||
|
c = 0;
|
||||||
|
while (c < o.subOptions.length && r + o.subOptions[c].tv_size < row)
|
||||||
|
{
|
||||||
|
r += o.subOptions[c].tv_size;
|
||||||
|
c += 1;
|
||||||
|
}
|
||||||
|
if (c < o.subOptions.length && r + o.subOptions[c].tv_size >= row)
|
||||||
|
{
|
||||||
|
// Count the current option as a row.
|
||||||
|
o = o.subOptions[c];
|
||||||
|
r += 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
alert("WTF: " + o.name + " ask: " + row + " children: " + o.subOptions + " c: " + c);
|
||||||
|
path.unshift({ row: r, opt: o });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reach_cache = path;
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
|
||||||
|
// needs to return true if there is a /row/ at the same level /after/ a
|
||||||
|
// given row.
|
||||||
|
hasNextSibling: function(row, after) {
|
||||||
|
log("sibling " + row + " after " + after);
|
||||||
|
var path = reachRow(row);
|
||||||
|
if (path.length > 1)
|
||||||
|
{
|
||||||
|
var last = path[1].row + path[1].opt.tv_size;
|
||||||
|
// Has a next sibling if the row is not over the size of the
|
||||||
|
// parent and if the current one is not the last child.
|
||||||
|
return after + 1 < last && path[0].row + path[0].opt.tv_size < last;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// The top-level option has no sibling.
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Does the current row contain any sub-options?
|
||||||
|
isContainer: function(row) {
|
||||||
|
return !this.reachRow(row)[0].opt.isOption;
|
||||||
|
},
|
||||||
|
isContainerEmpty: function(row) {
|
||||||
|
return this.reachRow(row)[0].opt.subOptions.length == 0;
|
||||||
|
},
|
||||||
|
isContainerOpen: function(row) {
|
||||||
|
return this.reachRow(row)[0].opt.tv_opened;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Open or close an option.
|
||||||
|
toggleOpenState: function (row) {
|
||||||
|
var path = this.reachRow(row);
|
||||||
|
var delta = -path[0].opt.tv_size;
|
||||||
|
if (path[0].opt.tv_opened)
|
||||||
|
path[0].opt.tv_close();
|
||||||
|
else
|
||||||
|
path[0].opt.tv_open();
|
||||||
|
delta += path[0].opt.tv_size;
|
||||||
|
|
||||||
|
// Parents are alreay opened, but we need to update the tv_size
|
||||||
|
// counters. Thus we have to invalidate the reach cache.
|
||||||
|
this.reach_cache = null;
|
||||||
|
for (var i = 1; i < path.length; i++)
|
||||||
|
path[i].opt.tv_open();
|
||||||
|
|
||||||
|
this.tree.rowCountChanged(row + 1, delta);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Return the identation level of the option at the line /row/. The
|
||||||
|
// top-level level is 0.
|
||||||
|
getLevel: function(row) {
|
||||||
|
return this.reachRow(row).length - 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Obtain the index of a parent row. If there is no parent row,
|
||||||
|
// returns -1.
|
||||||
|
getParentIndex: function(row) {
|
||||||
|
var path = this.reachRow(row);
|
||||||
|
if (path.length > 1)
|
||||||
|
return path[1].row;
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// Return the content of each row base on the column name.
|
||||||
|
getCellText: function(row, column) {
|
||||||
|
if (column.id == "opt-name")
|
||||||
|
return this.reachRow(row)[0].opt.name;
|
||||||
|
if (column.id == "dbg-size")
|
||||||
|
return this.reachRow(row)[0].opt.tv_size;
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
|
||||||
|
// We have no column with images.
|
||||||
|
getCellValue: function(row, column) { },
|
||||||
|
|
||||||
|
|
||||||
|
isSelectable: function(row, column) { return true; },
|
||||||
|
|
||||||
|
// Get the selection out of the tree and give options to the call back
|
||||||
|
// function.
|
||||||
|
selectionChanged: function() {
|
||||||
|
if (this.selCallback == null)
|
||||||
|
return;
|
||||||
|
var opts = [];
|
||||||
|
var start = new Object();
|
||||||
|
var end = new Object();
|
||||||
|
var numRanges = this.tree.view.selection.getRangeCount();
|
||||||
|
|
||||||
|
for (var t = 0; t < numRanges; t++) {
|
||||||
|
this.tree.view.selection.getRangeAt(t,start,end);
|
||||||
|
for (var v = start.value; v <= end.value; v++) {
|
||||||
|
var opt = this.reachRow(v)[0].opt;
|
||||||
|
if (!opt.isLoaded)
|
||||||
|
opt.load();
|
||||||
|
if (opt.isOption)
|
||||||
|
opts.push(opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.lenght != 0)
|
||||||
|
this.selCallback(opts);
|
||||||
|
},
|
||||||
|
|
||||||
|
set rowCount(c) { throw "rowCount is a readonly property"; },
|
||||||
|
get rowCount() { return this.rootOption.tv_size; },
|
||||||
|
|
||||||
|
// refuse drag-n-drop of options.
|
||||||
|
canDrop: function (index, orientation, dataTransfer) { return false; },
|
||||||
|
drop: function (index, orientation, dataTransfer) { },
|
||||||
|
|
||||||
|
// ?
|
||||||
|
getCellProperties: function(row, column, prop) { },
|
||||||
|
getColumnProperties: function(column, prop) { },
|
||||||
|
getRowProperties: function(row, prop) { },
|
||||||
|
getImageSrc: function(row, column) { },
|
||||||
|
|
||||||
|
// No progress columns are used.
|
||||||
|
getProgressMode: function(row, column) { },
|
||||||
|
|
||||||
|
// Do not add options yet.
|
||||||
|
isEditable: function(row, column) { return false; },
|
||||||
|
setCellValue: function(row, column, value) { },
|
||||||
|
setCellText: function(row, column, value) { },
|
||||||
|
|
||||||
|
// ...
|
||||||
|
isSeparator: function(index) { return false; },
|
||||||
|
isSorted: function() { return false; },
|
||||||
|
performAction: function(action) { },
|
||||||
|
performActionOnCell: function(action, row, column) { },
|
||||||
|
performActionOnRow: function(action, row) { }, // ??
|
||||||
|
|
||||||
|
// ??
|
||||||
|
cycleCell: function (row, col) { },
|
||||||
|
cycleHeader: function(col) { },
|
||||||
|
|
||||||
|
selection: null,
|
||||||
|
tree: null,
|
||||||
|
setTree: function(tree) { this.tree = tree; }
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user