L'outil d'importation de fichiers multiples de JMP peut créer un tableau de données des fichiers qu'il a trouvés. La JSL du complément joint (JMP 16, Windows uniquement) prend cette table de données et crée une arborescence imbriquée de l'espace disque utilisé par les fichiers. C'est une vitesse moyenne; il a chargé 500 000 fichiers à partir de la racine du lecteur C en 30 minutes environ (s'ennuyer, regarder les actualités, ne pas savoir exactement...)Il existe une visionneuse pour quelques types de fichiers ; JSL illustré ci-dessous.
Ne pas commencer à la racine est plus rapide.
Le JSL commence par une invite pour un répertoire. Cela semble toujours maladroit à utiliser car le double-clic ne sélectionne pas un répertoire... il l'ouvre. Mettez un répertoire en surbrillance et choisissez OK. Le répertoire est donné à MFI et la liste des fichiers récupérée. Quelques vérifications pour Annuler sont effectuées en cours de route.
root = Pick Directory( "show directory sizes", "$Desktop/.." );
If( root == "", Throw( "canceled" ));
// use MFI to get a recursive file list with sizes and dates
mfi = Multiple File Import(
<<SET folder=""></SET>
Du code modifié de Barre de progression avec bouton Annuler qui recyclera la fenêtre de la barre de progression à la fin.
// Progressbar https://community.jmp.com/t5/Uncharted/Progress-Bar-with-Cancel-Button/ba-p/433560
progressBarWidth = 500;
cancel = 0; // clear the cancel flag
New Window( "Directory Tree", // this window gets reused below
windowbox = V List Box( //
t = Text Box( "", <<SETWRAP></SETWRAP>
Et il est temps de sauter le pas. Ce code passe sur la table de données, créant une arborescence qui reflète la forme de l'arborescence des dossiers sur le disque. Une fois terminé, la table de données est fermée car toutes les données résident dans l'arborescence qui se trouve dans le tableau associatif. Les liens qui relient l'arbre sont en fait des clés dans le tableau associatif.
// look at every row in the data table and build a tree in an associative array. The keys in the
// array represent directories, NOT files. Files are held in a {list} at each directory level.
// file sizes are accumulated at each of the file's ancestor directory levels, so there is
// a grand total at the root.
init = {{/*immediate files and dirs*/}, 0/*aggregate file count*/, 0.0/*aggregate bytes*/}; // all dir nodes look like this; there is no date for dirs
directory = [=> ]; // associative array to hold the nodes
directory[root] = init; // the root node is handled separately
nBytes = 3;
nDate = nCount = 2;
For( irow = 1, irow <= nRowsDtFiles & !cancel, irow += 1,
levels = Words( dtFiles:FileName[irow], "/" ); // break out the directory levels for this row's file
path = root; // the root is not part of the file's path broken out in levels[] but is needed in the real path spec
size = dtFiles:FileSize[irow];
date = dtFiles:FileDate[irow];
// root handled separately...
directory[root][nCount] += 1; // item 2 in the init list is a count of files here AND below
directory[root][nBytes] += size; // item nBytes is the size of all files here AND below
For( ilevel = 1, ilevel < N Items( levels ), ilevel += 1,
parent = path;
path = path || levels[ilevel] || "\";
If( !Contains( directory, path ), // never seen this node before? create it!
directory[path] = init; // create with an empty list
// add this directory to the parent directory, at position 1 so dirs come first
// Important: the missing value in the 2-item list signals a directory, not date or size.
Insert Into( directory[parent][1], Eval List( {Eval List( {levels[iLevel], .} )} ), 1 ); // my parent dir points to me (I'm also a dir)
);
directory[path][nCount] += 1;// count files and subdirs
directory[path][nBytes] += size;
If( Mod( irow, 1000 ) == 0,
updateProgress( irow / nRowsDtFiles );
Wait( 0 );
);
);
// every row represents a file. This is inserted at the end so files come last, but that is no
// longer important since they are sorted together by descendingaggregate size
// Important: the 2nd item (date) in this list is not missing, so it is a file
If( Is Missing( date ),
Throw( "missing date?" )
);
Insert Into( directory[path][1], Eval List( {Eval List( {levels[iLevel], date, size} )} ) ); // also a 3-item list, not 2 for a dir
);
Close( dtFiles, nosave ); // done with the dt, the data is all loaded in the associative array
Vérifiez l'annulation, créez les jolies icônes, configurez la seconde moitié de la barre de progression. Oui, créer vos propres icônes personnalisées peut être aussi simple que cela ! En modifiant à la fois le rouge à gris de gauche à droite et la nuance de rouge, les icônes sont un peu plus expressives quant à la vaste gamme de tailles de fichiers.
If( cancel, // test the cancel flag
Beep(); // audio flag
left << color( "dark red" ); // visual flag something is awry
right << color( "red" );
cancelButton << setbuttonname( "Close" ); // visual flag the button function is changed
cancelButton << setscript( (cancelButton << closewindow) ); // and what to do if the button is pressed
Stop();
, //
updateProgress( 1.0 ); // people like to see the bar go to 100%
);
// make icons
redcolor = {0.627450980392157, 0.0352941176470588, 0.133333333333333};
graycolor = {0.831372549019608, 0.831372549019608, 0.831372549019608};
ncons = 64;// width, height, and n. the actual displayed icon may be smaller, the shades will help
For( i = 0, i <= ncons, i += 1,
// the width of the red and the shade of the red are both diminishing because
// there is a huge range to cover and this helps express small vs large better
H List Box(
Spacer Box( size( i, ncons ), color( RGB Color( redcolor * (i / ncons) + graycolor * (1 - i / ncons) ) ) ), // left side
Spacer Box( size( ncons - i, ncons ), color( RGB Color( graycolor ) ) ) // right side
) << savepicture( "$temp/deletemeIcon" || Char( i ) || ".png" )
);
biggestsize = Log( directory[root][nBytes] + 1 );// biggest in this tree is dark red square; near 0 is all light gray
icon = Function( {size}, // get the right icon for this file size
size = Floor( ncons * Log( size + 1 ) / biggestsize );
"$temp/deletemeIcon" || Char( size ) || ".png";
);
// update progressbar for 2nd phase this way...
updateProgress2 = Function( {fractionComplete},
leftsize = Round( .5 * progressBarWidth + progressBarWidth * fractionComplete * .5 ); // second half of progress bar
rightsize = progressBarWidth - leftsize;
left << width( leftsize );
right << width( rightsize );
t << settext( Eval Insert( "finishing up display constuction ^filesDone^ / ^nRowsDtFiles^" ) );
t << updatewindow; // this works without the wait
);
La fonction explore est récursive ; il explore l'arbre dans le tableau associatif et construit un contrôle de nœud d'arbre trié.
// recursive function to explore the tree loaded in the associative array and build the treenode structure
explore = Function( {path, attachpoint},
{treeNode, list, childpath, nfiles, bytes, appendTnode, appendSizes, m, newbutton},
list = directory[path][1]; // all the files and subdirs of this node
// keep two parallel lists of TreeNodes and the size of the object represented
// later, use rank() to sort them so the big objects are at the top of the display
appendTnode = {};
appendSizes = {};
For Each( {namedata}, list, // subdir or file? subdir will recurse, file is a leaf.
If( Is Missing( namedata[nDate] ), // then this is a dir {name, . }
If( N Items( namedata ) != 2,
Throw( "dir? " || Char( namedata ) ) // unexpected bug
);
childpath = path || namedata[1] || "\";
nfiles = directory[childpath][nCount];
bytes = directory[childpath][nBytes];
treeNode = Tree Node(
Eval Insert(
"\[^Format(bytes, "Fixed Dec", Use thousands separator( 1 ), 30, 0 )^ bytes in directory with ^nfiles^ items ^namedata[1]^]\"
)
);
treeNode << seticon( icon( bytes ) );
treeNode << setdata( childpath );
explore( childpath, treeNode ); // recursion! build the subtree
Insert Into( appendTnode, treeNode );
Insert Into( appendSizes, bytes );
, // else a file belonging to path { name, bytes, date}
treeNode = Tree Node(
Eval Insert(
"\[^Format(namedata[nBytes], "Fixed Dec", Use thousands separator( 1 ), 30, 0 )^ bytes updated ^round((today()-namedata[nDate])/inweeks(1))^ weeks ago ^namedata[1]^]\"
)
);
treeNode << seticon( icon( namedata[nBytes] ) );
childpath = path || namedata[1];
treeNode << setdata( childpath );
Insert Into( appendTnode, treeNode );
Insert Into( appendSizes, namedata[nBytes] );
filesDone += 1;
If( Mod( filesDone, 100 ) == 0,
Try( updateProgress2( filesDone / nRowsDtFiles ) );
Wait( 0 );
If( cancel,
Beep(); // audio flag
left << color( "dark red" ); // visual flag something is awry
right << color( "red" );
cancelButton << setbuttonname( "Close" ); // visual flag the button function is changed
cancelButton << setscript( (cancelButton << closewindow) ); // and what to do if the button is pressed
Throw( "canceled" );
);
);
)
);
m = Rank( appendsizes );
For( j = N Items( m ), j >= 1, j--, // biggest first, rank from small to large
i = m[j];
attachpoint << append( appendTnode[i] );
);
);
Le TreeClickHandler ouvre une visionneuse simple pour quelques types de fichiers.
// click handler for tree nodes
treeClickHandler = Function( {thistree, thisnode}, // a few viewers for common file types
{extension, data, tablename, nodedata, nodelabel},
nodelabel = thisnode << getlabel;
nodedata = thisnode << getdata;
// show(nodelabel,nodedata);
line1 << settext( nodelabel );
line2 << setbuttonname( nodedata );
// show(line2<<GETBUTTONNAME>1MB, too big for this viewer, click link above to open it" ) );//
);//
, /*else if*/
Ends With( extension, "\" ), //
view << append( Text Box( "This is a directory. Click the link above to open it." ) );//
, // else
view << append( Text Box( "Not sure what this is, don't click the link unless you want JMP to open() it." ) );//
);
);
</GETBUTTONNAME>
Enfin, créez le nœud racine, passez-le à explore (), créez une fenêtre avec une boîte d'arborescence pour afficher le contrôle du nœud d'arbre. Il y a un bouton qui ouvrira les fichiers() ; vous devez être prudent en appuyant dessus. Il y a (non montré) une courte liste d'extensions de fichiers exécutables, mais certainement pas exhaustive, pour essayer de vous éviter des ennuis...
rootTreeNode = Tree Node( Eval Insert( "\[^Format(directory[root][nBytes], "Fixed Dec", Use thousands separator( 1 ), 30, 0 )^ ^root^]\" ) );
rootTreeNode << seticon( icon( directory[root][nBytes] ) );
explore( root, rootTreeNode );
windowbox = windowbox << parent;
(windowbox << child) << delete;
windowbox << append(
H Splitter Box(
size( 1000, 600 ),
treebox = Tree Box( rootTreeNode ),
vlistbox = V List Box(
line1 = Text Box(), // also updated by treeClickHandler
line2 = Button Box( "", // gets link-style, below. Visible name updated by treeClickHandler script.
setfunction( // when the button clicks, use its current visible name to open() the file.
Function( {this},
name = this << getbuttonname;// the underlined button name *is* the file name
extension = Uppercase( Regex( name, ".*?([^\.]*)$", "\1" ) );
If( !Contains( executable, extension ), // seems a bad idea
Open( name, Add to Recent Files( 0 ) )
,
Beep()
);
)
)
),
scrollbox = Scroll Box( view = Border Box() )
)
)
);
windowbox << Set Window Title( "Directory Tree " || root );
line2 << UnderlineStyle( 1 );
line1 << Set Stretch( "fill", "off" );
line2 << Set Stretch( "fill", "off" );
scrollbox << Set Stretch( "off", "off" );
treebox << Set Auto Stretching( 1, 1 ) << Set Max Size( 10000, 10000 ) << User Resizable( {0, 0} );
scrollbox << Set Auto Stretching( 1, 1 ) << Set Max Size( 10000, 10000 ) << User Resizable( {0, 0} );
treebox << Set Node Select Script( treeClickHandler );
treebox << expand( rootTreeNode );
Une image
Une table
Une revue
Si vous installez le complément, vous pouvez jeter un coup d'œil au JSL en utilisant view->addins. Vous pouvez également le télécharger et le renommer avec une extension .zip.
(La première passe de ce JSL a utilisé des nœuds de contour, qui permettent beaucoup plus de cloches et de sifflets que les nœuds d'arbre. Mais les nœuds de contour ne sont pas bien adaptés pour afficher plus de 100 000 nœuds imbriqués ; les nœuds d'arborescence sont conçus pour ce travail. Si vous repérez des références de contour restantes (je pense que je les ai nettoyées ...) c'est ce qui s'est passé.)
Agréable! J'ai travaillé sur quelque chose d'un peu similaire chaque fois que j'ai le temps, mais avec un aperçu rapide, nous avons des idées similaires et j'en ai maintenant de nouvelles, comme l'utilisation d'un tableau associatif (je laisse la table de données en place, mais je l'utilise pour filtrer les fichiers ) et en utilisant l'importation de plusieurs fichiers.
Les objectifs finaux sont un peu différents, le mien est de fournir un outil qui listera des répertoires spécifiques et donnera un accès facile aux fichiers qu'ils contiennent avec JMP. Mais il y aura certainement des idées dans votre code dont je pourrai apprendre (le tableau associatif et le test de l'importation de plusieurs fichiers me viennent à l'esprit). Image de ma version actuelle :

Le code de travail en cours peut être trouvé ici file-indexer/bin at main · jthi-jmp-tools/file-indexer (github.com) , ça devrait marcher mais pas sûr.
J'ai fait quelque chose de similaire au travail (celui-ci est mon projet personnel), où j'ai utilisé des boîtes de contour. Dommage (ou peut-être bien), je n'étais pas aussi bon scripteur que je le sais, car ce code est un gâchis et un cauchemar à mettre à jour. Cela deviendra également, à mon avis, ennuyeux à utiliser, là où il y a d'énormes quantités de boîtes de contour.
Merci pour ce message !