parent
0ac4b76be2
commit
447ef0e4ce
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
AccessModifierOffset: -1
|
||||||
|
AlignEscapedNewlinesLeft: true
|
||||||
|
AlignTrailingComments: false
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: true
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakBeforeMultilineStrings: true
|
||||||
|
AlwaysBreakTemplateDeclarations: true
|
||||||
|
BinPackArguments: false
|
||||||
|
BinPackParameters: false
|
||||||
|
BreakBeforeBinaryOperators: false
|
||||||
|
BreakBeforeBraces: Attach
|
||||||
|
BreakConstructorInitializersBeforeComma: false
|
||||||
|
ColumnLimit: 80
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
|
ConstructorInitializerIndentWidth: 4
|
||||||
|
ContinuationIndentWidth: 4
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
ExperimentalAutoDetectBinPacking: true
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentFunctionDeclarationAfterType: false
|
||||||
|
IndentWidth: 2
|
||||||
|
MaxEmptyLinesToKeep: 1
|
||||||
|
NamespaceIndentation: None
|
||||||
|
ObjCSpaceBeforeProtocolList: false
|
||||||
|
PenaltyBreakBeforeFirstCallParameter: 10
|
||||||
|
PenaltyBreakComment: 60
|
||||||
|
PenaltyBreakFirstLessLess: 20
|
||||||
|
PenaltyBreakString: 1000
|
||||||
|
PenaltyExcessCharacter: 1000000
|
||||||
|
PenaltyReturnTypeOnItsOwnLine: 200
|
||||||
|
PointerAlignment: Left
|
||||||
|
SortIncludes: false
|
||||||
|
SpaceAfterControlStatementKeyword: true
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesBeforeTrailingComments: 1
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
Standard: Cpp11
|
||||||
|
TabWidth: 8
|
||||||
|
UseTab: Never
|
||||||
|
...
|
@ -0,0 +1,2 @@
|
|||||||
|
Ocamldot was written by Trevor Jim. It is in the public domain; use
|
||||||
|
it however you like, at your own risk.
|
@ -0,0 +1,23 @@
|
|||||||
|
# Try to automatically guess whether we are running under Windows.
|
||||||
|
# Set WIN32=true manually if this doesn't work.
|
||||||
|
#
|
||||||
|
ifeq (${OSTYPE},cygwin32) # Cygwin Beta 19
|
||||||
|
WIN32=true
|
||||||
|
else
|
||||||
|
ifeq (${OSTYPE},cygwin) # Cygwin Beta 20
|
||||||
|
WIN32=true
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifdef WIN32
|
||||||
|
EXE=.exe
|
||||||
|
else # UNIX
|
||||||
|
EXE=
|
||||||
|
endif
|
||||||
|
|
||||||
|
ocamldot$(EXE): ocamldot.mll
|
||||||
|
ocamllex ocamldot.mll
|
||||||
|
ocamlc -o $@ ocamldot.ml
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) ocamldot$(EXE) ocamldot.ml *.cmi *.cmo *.o *.obj
|
@ -0,0 +1,34 @@
|
|||||||
|
Ocamldot generates program dependency graphs for ocaml programs.
|
||||||
|
|
||||||
|
The dependency graph output by ocamldot can be rendered by a separate
|
||||||
|
program, dot. Dot is freely available from
|
||||||
|
|
||||||
|
http://www.research.att.com/sw/tools/graphviz/
|
||||||
|
|
||||||
|
Ocamldot is designed to process the output of ocamldep. A typical use
|
||||||
|
would be
|
||||||
|
|
||||||
|
ocamldep *.ml | ocamldot > dep.dot
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
ocamldep *.ml > .depend
|
||||||
|
ocamldot .depend > dep.dot
|
||||||
|
|
||||||
|
This will output a dot graph into the file dep.dot. You can then use
|
||||||
|
the program dotty to view, edit, and print the graph.
|
||||||
|
|
||||||
|
Ocamldot has the following options:
|
||||||
|
|
||||||
|
-fullgraph draw the full graph (default is to draw only the kernel)
|
||||||
|
-landscape output in landscape format (default is portrait)
|
||||||
|
-lr draw graph from left to right (default is top to bottom)
|
||||||
|
-r <r> use <r> as a root in the graph; nodes reachable from <r>
|
||||||
|
will be shown
|
||||||
|
|
||||||
|
(The transitive kernel of a dag is the smallest subset of the dag
|
||||||
|
whose transitive closure is the same as the transitive closure of the
|
||||||
|
dag. For example, the kernel of A->B, A->C, B->C is just the two
|
||||||
|
edges A->B, B->C.)
|
||||||
|
|
||||||
|
-Trevor Jim
|
@ -0,0 +1,52 @@
|
|||||||
|
.TH OCAMLDOT 1
|
||||||
|
.SH NAME
|
||||||
|
ocamldot \- generate dependency graphs of ocaml programs
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B ocamldot
|
||||||
|
.I
|
||||||
|
.RI "[options] <dependency-file>"
|
||||||
|
.SH "DESCRIPTION"
|
||||||
|
This manual page documents briefly the
|
||||||
|
.BR ocamldot
|
||||||
|
command.
|
||||||
|
.PP
|
||||||
|
.B ocamldot
|
||||||
|
generates program dependency graphs for ocaml programs. The
|
||||||
|
dependency graph output by ocamldot can be rendered by a separate
|
||||||
|
program, \fIdot\fR.
|
||||||
|
.P
|
||||||
|
Ocamldot is designed to process the output of ocamldep. A typical use would be
|
||||||
|
.P
|
||||||
|
ocamldep *.ml | ocamldot > dep.dot
|
||||||
|
.P
|
||||||
|
or
|
||||||
|
.P
|
||||||
|
ocamldep *.ml > .depend
|
||||||
|
.br
|
||||||
|
ocamldot .depend > dep.dot
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
.B \-fullgraph
|
||||||
|
Draw the full graph (default is to draw only the kernel)
|
||||||
|
.TP
|
||||||
|
.B \-landscape
|
||||||
|
Output in landscape format (default is portrait)
|
||||||
|
.TP
|
||||||
|
.B \-lr
|
||||||
|
Draw graph from left to right (default is top to bottom)
|
||||||
|
.TP
|
||||||
|
.B \-r <r>
|
||||||
|
Use \fI<r>\fR as a root in the graph; nodes reachable from \fI<r>\fR
|
||||||
|
will be shown.
|
||||||
|
.P
|
||||||
|
The transitive kernel of a dag is the smallest subset of the dag whose transitive closure is the same as the transitive closure of the dag.
|
||||||
|
For example, the kernel of A->B, A->C, B->C is just the two edges A->B, B->C.
|
||||||
|
|
||||||
|
.SH SEE ALSO
|
||||||
|
.BR ocamldep (1),
|
||||||
|
.BR dot(1)
|
||||||
|
.P
|
||||||
|
\fIhttp://www.research.att.com/~trevor/ocamldot\fR.
|
||||||
|
|
||||||
|
.SH AUTHOR
|
||||||
|
Trevor Jim <trevor@research.att.com>
|
@ -0,0 +1,355 @@
|
|||||||
|
(* ocamldot.mll, July 1999, Trevor Jim *)
|
||||||
|
|
||||||
|
{
|
||||||
|
module StringSet =
|
||||||
|
Set.Make(struct type t = string let compare = compare end)
|
||||||
|
|
||||||
|
let dependencies = ref []
|
||||||
|
let currentSource = ref ""
|
||||||
|
let addDepend t =
|
||||||
|
let s = !currentSource in
|
||||||
|
if s<>t
|
||||||
|
then dependencies := (s,t)::(!dependencies)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
rule processSource = parse
|
||||||
|
['.' '-' '/' 'A'-'Z' 'a'-'z' '_' '\192'-'\214' '\216'-'\246'
|
||||||
|
'\248'-'\255' '\'' '0'-'9' ]+ '.' ['A'-'Z' 'a'-'z']+
|
||||||
|
[' ' '\009']* ':'
|
||||||
|
{ let s = Lexing.lexeme lexbuf in
|
||||||
|
let i = String.rindex s '.' in
|
||||||
|
let s = String.sub s 0 i in
|
||||||
|
let s = Filename.basename s in
|
||||||
|
let s = String.capitalize s in
|
||||||
|
currentSource := s;
|
||||||
|
processTargets lexbuf }
|
||||||
|
| eof
|
||||||
|
{ () }
|
||||||
|
| _
|
||||||
|
{ processSource lexbuf }
|
||||||
|
|
||||||
|
and processTargets = parse
|
||||||
|
[' ' '\009']+
|
||||||
|
{ processTargets lexbuf }
|
||||||
|
| '\\' [' ' '\009']* ['\010' '\013']+ [' ' '\009']+
|
||||||
|
{ processTargets lexbuf }
|
||||||
|
| ['.' '/' 'A'-'Z' 'a'-'z' '_' '\192'-'\214' '\216'-'\246'
|
||||||
|
'\248'-'\255' '\'' '0'-'9' ]+ '.' ['A'-'Z' 'a'-'z']+
|
||||||
|
{ let t = Lexing.lexeme lexbuf in
|
||||||
|
let i = String.rindex t '.' in
|
||||||
|
let t = String.sub t 0 i in
|
||||||
|
let t = Filename.basename t in
|
||||||
|
let t = String.capitalize t in
|
||||||
|
addDepend t;
|
||||||
|
processTargets lexbuf }
|
||||||
|
| eof
|
||||||
|
{ () }
|
||||||
|
| _
|
||||||
|
{ processSource lexbuf }
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
(********************************)
|
||||||
|
(* Utility functions for graphs *)
|
||||||
|
(********************************)
|
||||||
|
|
||||||
|
|
||||||
|
(**********************************************************************)
|
||||||
|
(* A graph is represented by a (string * StringSet) list, *)
|
||||||
|
(* that is, a list of (source,targets) pairs. *)
|
||||||
|
(**********************************************************************)
|
||||||
|
|
||||||
|
let emptyGraph = []
|
||||||
|
|
||||||
|
(**********************************************************************)
|
||||||
|
(* divideGraph graph source = (sourceTargets, graphWithoutSource) *)
|
||||||
|
(* *)
|
||||||
|
(* Return the targets of a source in a graph and the graph with the *)
|
||||||
|
(* source substracted from the sources. GraphWithoutSources may *)
|
||||||
|
(* still contain source as a target. *)
|
||||||
|
(**********************************************************************)
|
||||||
|
let divideGraph graph source =
|
||||||
|
let rec aux l =
|
||||||
|
match l with
|
||||||
|
[] -> (StringSet.empty,[])
|
||||||
|
| (s,ts)::tl ->
|
||||||
|
if s=source then (ts,tl)
|
||||||
|
else
|
||||||
|
let (sourceTargets,tlWithoutSource) = aux tl in
|
||||||
|
(sourceTargets,(s,ts)::tlWithoutSource) in
|
||||||
|
aux graph
|
||||||
|
|
||||||
|
(*********************************************)
|
||||||
|
(* Add the edge (source,target) to the graph *)
|
||||||
|
(*********************************************)
|
||||||
|
let addEdge graph source target =
|
||||||
|
let (sourceTargets,graphWithoutSource) = divideGraph graph source in
|
||||||
|
(source,StringSet.add target sourceTargets)::graphWithoutSource
|
||||||
|
|
||||||
|
(************************************************************)
|
||||||
|
(* Add the edges { (source,t) | t in targets } to the graph *)
|
||||||
|
(************************************************************)
|
||||||
|
let addEdges graph source targets =
|
||||||
|
let (sourceTargets,graphWithoutSource) = divideGraph graph source in
|
||||||
|
(source,StringSet.union targets sourceTargets)::graphWithoutSource
|
||||||
|
|
||||||
|
(**************************************************)
|
||||||
|
(* Remove the edge (source,target) from the graph *)
|
||||||
|
(**************************************************)
|
||||||
|
let removeEdge graph source target =
|
||||||
|
let rec loop l =
|
||||||
|
match l with
|
||||||
|
[] -> []
|
||||||
|
| (s,ts)::tl ->
|
||||||
|
if s=source
|
||||||
|
then (s,StringSet.remove target ts)::tl
|
||||||
|
else (s,ts)::(loop tl)
|
||||||
|
in loop graph
|
||||||
|
|
||||||
|
(*****************************************************************)
|
||||||
|
(* Remove the edges { (source,t) | t in targets } from the graph *)
|
||||||
|
(*****************************************************************)
|
||||||
|
let removeEdges graph source targets =
|
||||||
|
let rec loop l =
|
||||||
|
match l with
|
||||||
|
[] -> []
|
||||||
|
| (s,ts)::tl ->
|
||||||
|
if s=source
|
||||||
|
then (s,StringSet.diff ts targets)::tl
|
||||||
|
else (s,ts)::(loop tl)
|
||||||
|
in loop graph
|
||||||
|
|
||||||
|
(**********************************************************************)
|
||||||
|
(* Convert between an edge-list representation of graphs and our *)
|
||||||
|
(* representation. *)
|
||||||
|
(**********************************************************************)
|
||||||
|
let edgesOfGraph graph =
|
||||||
|
List.concat
|
||||||
|
(List.map
|
||||||
|
(fun (s,ts) ->
|
||||||
|
List.map (fun t -> (s,t)) (StringSet.elements ts))
|
||||||
|
graph)
|
||||||
|
|
||||||
|
let graphOfEdges edges =
|
||||||
|
List.fold_left
|
||||||
|
(fun g (s,t) -> addEdge g s t)
|
||||||
|
emptyGraph
|
||||||
|
edges
|
||||||
|
|
||||||
|
(****************************)
|
||||||
|
(* Is an edge in the graph? *)
|
||||||
|
(****************************)
|
||||||
|
let isEdge graph source target =
|
||||||
|
try
|
||||||
|
let sourceTargets = List.assoc source graph in
|
||||||
|
StringSet.mem target sourceTargets
|
||||||
|
with Not_found -> false
|
||||||
|
|
||||||
|
(*****************)
|
||||||
|
(* Print a graph *)
|
||||||
|
(*****************)
|
||||||
|
let printGraph graph =
|
||||||
|
let printEdges(source,targets) =
|
||||||
|
StringSet.iter
|
||||||
|
(fun t -> Printf.printf " \"%s\" -> \"%s\" ;\n" source t)
|
||||||
|
targets in
|
||||||
|
List.iter printEdges graph
|
||||||
|
|
||||||
|
(********************************)
|
||||||
|
(* Targets of a node in a graph *)
|
||||||
|
(********************************)
|
||||||
|
let targetsOf graph node = (* A set of nodes *)
|
||||||
|
try List.assoc node graph
|
||||||
|
with Not_found -> StringSet.empty
|
||||||
|
|
||||||
|
(*****************************************)
|
||||||
|
(* Sources that target a node in a graph *)
|
||||||
|
(*****************************************)
|
||||||
|
let sourcesOf graph node = (* A list of nodes *)
|
||||||
|
let rec aux l =
|
||||||
|
match l with
|
||||||
|
[] -> []
|
||||||
|
| (s,ts)::tl ->
|
||||||
|
if StringSet.mem node ts then s::(aux tl)
|
||||||
|
else aux tl in
|
||||||
|
aux graph
|
||||||
|
|
||||||
|
(******************************************************************)
|
||||||
|
(* Add an edge to a transitively closed graph, and return the new *)
|
||||||
|
(* transitive closure. *)
|
||||||
|
(******************************************************************)
|
||||||
|
let addEdgeTc graph source target =
|
||||||
|
let targetTargets = targetsOf graph target in
|
||||||
|
let (sourceTargets,graphWithoutSource) = divideGraph graph source in
|
||||||
|
let sourceSources = sourcesOf graphWithoutSource source in
|
||||||
|
let newSourceTargets =
|
||||||
|
StringSet.add target
|
||||||
|
(StringSet.union sourceTargets targetTargets) in
|
||||||
|
(source,newSourceTargets)::
|
||||||
|
(List.fold_right
|
||||||
|
(fun s g -> addEdges g s newSourceTargets)
|
||||||
|
sourceSources
|
||||||
|
graphWithoutSource)
|
||||||
|
|
||||||
|
(**********************************************************)
|
||||||
|
(* Compute the transitive closure of a graph from scratch *)
|
||||||
|
(**********************************************************)
|
||||||
|
let tc graph =
|
||||||
|
let loop graph (source,targets) =
|
||||||
|
let reachableFromSource =
|
||||||
|
List.fold_left
|
||||||
|
(fun r (s,ts) ->
|
||||||
|
if StringSet.mem s r then StringSet.union r ts
|
||||||
|
else r)
|
||||||
|
targets
|
||||||
|
graph in
|
||||||
|
(source,reachableFromSource)::
|
||||||
|
(List.map
|
||||||
|
(fun (s,ts) ->
|
||||||
|
if StringSet.mem source ts
|
||||||
|
then (s,StringSet.union ts reachableFromSource)
|
||||||
|
else (s,ts))
|
||||||
|
graph) in
|
||||||
|
List.fold_left loop [] graph
|
||||||
|
|
||||||
|
(************************************************************************)
|
||||||
|
(* The transitive kernel (tk) of a dag is a subset of the dag whose *)
|
||||||
|
(* transitive closure is the same as the transitive closure of the dag. *)
|
||||||
|
(* *)
|
||||||
|
(* IF THE GRAPH IS NOT A DAG, THIS CODE WON'T WORK PROPERLY!!! *)
|
||||||
|
(************************************************************************)
|
||||||
|
|
||||||
|
(************************************************************************)
|
||||||
|
(* Add an edge to a kernel dag and return the new kernel and transitive *)
|
||||||
|
(* closure of the new kernel. Requires the transitive closure of the *)
|
||||||
|
(* old kernel. *)
|
||||||
|
(************************************************************************)
|
||||||
|
let addEdgeTk kernel tcKernel source target =
|
||||||
|
if isEdge tcKernel source target
|
||||||
|
then (kernel,tcKernel)
|
||||||
|
else if source=target
|
||||||
|
then (addEdge kernel source target,tcKernel)
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
let (sourceTargets,kernelWithoutSource) = divideGraph kernel source in
|
||||||
|
let targetTargets = StringSet.add target (targetsOf tcKernel target) in
|
||||||
|
let sourceSources = sourcesOf tcKernel source in
|
||||||
|
let kernelWithoutSource =
|
||||||
|
List.fold_left
|
||||||
|
(fun kws s -> removeEdges kws s targetTargets)
|
||||||
|
kernelWithoutSource
|
||||||
|
sourceSources in
|
||||||
|
((source,
|
||||||
|
StringSet.add target
|
||||||
|
(StringSet.diff sourceTargets targetTargets))
|
||||||
|
::kernelWithoutSource,
|
||||||
|
addEdgeTc tcKernel source target)
|
||||||
|
end
|
||||||
|
|
||||||
|
(**********************************)
|
||||||
|
(* The transitive kernel of a dag *)
|
||||||
|
(**********************************)
|
||||||
|
let tk dag =
|
||||||
|
let edges = edgesOfGraph dag in
|
||||||
|
let (kernel,tcKernel) =
|
||||||
|
List.fold_left
|
||||||
|
(fun (k,tck) (s,t) -> addEdgeTk k tck s t)
|
||||||
|
(emptyGraph,emptyGraph)
|
||||||
|
edges in
|
||||||
|
kernel
|
||||||
|
|
||||||
|
(**************************)
|
||||||
|
(* Print the dependencies *)
|
||||||
|
(**************************)
|
||||||
|
let doKernel = ref true
|
||||||
|
let printDepend graph =
|
||||||
|
if (!doKernel) then printGraph (tk graph)
|
||||||
|
else printGraph graph
|
||||||
|
|
||||||
|
let calledOnFile = ref false
|
||||||
|
let getDependFromFile file =
|
||||||
|
calledOnFile := true;
|
||||||
|
try
|
||||||
|
let ic = open_in file in
|
||||||
|
let lexbuf = Lexing.from_channel ic in
|
||||||
|
processSource lexbuf;
|
||||||
|
close_in ic
|
||||||
|
with Sys_error msg -> ()
|
||||||
|
| Exit -> ()
|
||||||
|
let getDependFromStdin () =
|
||||||
|
try
|
||||||
|
let lexbuf = Lexing.from_channel stdin in
|
||||||
|
processSource lexbuf
|
||||||
|
with Sys_error msg -> ()
|
||||||
|
| Exit -> ()
|
||||||
|
|
||||||
|
(***************)
|
||||||
|
(* Entry point *)
|
||||||
|
(***************)
|
||||||
|
|
||||||
|
let usage = "Usage: ocamldot [options] <files>"
|
||||||
|
|
||||||
|
let leftToRight = ref false
|
||||||
|
let landscape = ref false
|
||||||
|
let roots = ref []
|
||||||
|
;;
|
||||||
|
|
||||||
|
Arg.parse
|
||||||
|
[
|
||||||
|
("-fullgraph",
|
||||||
|
Arg.Clear doKernel,
|
||||||
|
" draw the full graph (default is to draw only the kernel)");
|
||||||
|
("-landscape",
|
||||||
|
Arg.Set landscape,
|
||||||
|
" output in landscape format (default is portrait)");
|
||||||
|
("-lr",
|
||||||
|
Arg.Set leftToRight,
|
||||||
|
" draw graph from left to right (default is top to bottom)");
|
||||||
|
("-r",
|
||||||
|
Arg.String(fun s -> roots := s::!roots),
|
||||||
|
"<r> use <r> as a root in the graph; nodes reachable from <r>\n will be shown")
|
||||||
|
]
|
||||||
|
getDependFromFile usage;
|
||||||
|
if not(!calledOnFile) then getDependFromStdin();
|
||||||
|
print_string "digraph G {\n";
|
||||||
|
if !landscape
|
||||||
|
then print_string " size=\"10,7.5\" ;\n rotate=90 ;\n"
|
||||||
|
else print_string " size=\"7.5,10\" ;\n";
|
||||||
|
if (!leftToRight) then print_string " rankdir = LR ;\n"
|
||||||
|
else print_string " rankdir = TB ;\n";
|
||||||
|
let graph = graphOfEdges(!dependencies) in
|
||||||
|
begin
|
||||||
|
match !roots with
|
||||||
|
[] -> printDepend graph
|
||||||
|
| roots ->
|
||||||
|
(* Set up the graph so that the roots are printed at the same level *)
|
||||||
|
print_string " { rank=same ;\n";
|
||||||
|
List.iter
|
||||||
|
(fun r ->
|
||||||
|
print_string " ";
|
||||||
|
print_string r;
|
||||||
|
print_string " ;\n")
|
||||||
|
roots;
|
||||||
|
print_string " };\n";
|
||||||
|
(* Find the graph reachable from the roots *)
|
||||||
|
let tcGraph = tc graph in
|
||||||
|
let reachable node =
|
||||||
|
(List.exists (fun r -> r=node) roots)
|
||||||
|
or
|
||||||
|
(List.exists (fun r -> isEdge tcGraph r node) roots) in
|
||||||
|
let reachableFromRoots =
|
||||||
|
List.concat
|
||||||
|
(List.map
|
||||||
|
(fun (source,targets) ->
|
||||||
|
if reachable source
|
||||||
|
then [(source,targets)]
|
||||||
|
else [])
|
||||||
|
graph) in
|
||||||
|
printDepend reachableFromRoots
|
||||||
|
end;
|
||||||
|
print_string "}\n";
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in new issue