I am getting an error running the following Erlang code on an Ubuntu Server machine, it runs properly on my machine running Solus. I am new to Erlang and am not sure how to read the error, as all other examples only have one function and module in the error code.
I have two files
Sensor:
-module(sensor).
-export([start/2, senseAndReport/3]).
start(WatcherPid, SensorId) ->
Ref = make_ref(),
senseAndReport(WatcherPid, SensorId, Ref).
senseAndReport(WatcherPid, SensorId, Ref) ->
Measurement = rand:uniform(11),
if
Measurement == 11 ->
WatcherPid ! {kill, {self(), SensorId}, error},
exit(error);
true ->
WatcherPid ! {Measurement, {self(), SensorId}, Ref}
end,
receive
{ok, Ref} ->
Sleep_time = rand:uniform(10000),
timer:sleep(Sleep_time),
senseAndReport(WatcherPid, SensorId, Ref)
end.
And Watcher:
-module(watcher).
-export([start/1, createSensor/3, restartASensor/2, watch/1]).
start(NumOfSensor) ->
if
NumOfSensor == 0 ->
io:format("Please enter a number greater than 0.~n");
true ->
createSensor(NumOfSensor, [], 0)
end.
createSensor(NumOfSensor, SensorList, SensorId) ->
if
length(SensorList) == 10 ->
io:format("Start watching:~n"),
[io:format(" Id: ~p, Pid: ~p~n", [Id, Pid]) || {Id, Pid} <- SensorList],
if NumOfSensor /= 0 ->
spawn(watcher, createSensor, [NumOfSensor, [], SensorId]),
watch(SensorList);
true ->
watch(SensorList)
end;
NumOfSensor == 0 ->
io:format("Start watching~n"),
[io:format(" Id: ~p, Pid: ~p~n", [Id, Pid]) || {Id, Pid} <- SensorList],
watch(SensorList);
true ->
SensorPid = spawn_monitor(sensor, start, [self(), SensorId]),
createSensor(NumOfSensor - 1, lists:merge(SensorList, [{SensorId, SensorPid}]), SensorId + 1)
end.
restartASensor(SensorList, SensorId) ->
{SensorPid, _} = spawn_monitor(sensor, start, [self(), SensorId]),
io:format(" Restarted sensor: ~p, new Pid: ~p~n", [SensorId, SensorPid]),
NewList = lists:merge(SensorList, [{SensorId, SensorPid}]),
watch(NewList).
watch(SensorList) ->
receive
{kill, {From, FromId}, error} ->
io:format(" Sensor ~p died~n", [FromId]),
restartASensor(lists:delete({FromId, From}, SensorList), FromId);
{Measurement, {From, FromId}, Ref} ->
io:format("MSG: ~2p, From sensor ~4p~n", [Measurement, FromId]),
From ! {ok, Ref},
watch(SensorList)
end.
This give me the following output:
Eshell V5.8.5 (abort with ^G)
1> c(watcher).
{ok,watcher}
2> watcher:start(1).
Start watching
Id: 0, Pid: <0.39.0>
=ERROR REPORT==== 18-Nov-2016::19:32:35 ===
Error in process <0.39.0> with exit value: {undef,[{rand,uniform,[11]},{sensor,senseAndReport,3}]}
Dogbert's comment is the correct answer. The version of Erlang I was using was older on the Ubuntu machine, needed to replace the rand module with the random module.
Related
I'm inconsistently getting this error in a first experiment with OCaml 5.0.0~beta1:
Fatal error: exception Stdlib.Effect.Unhandled(Domainslib__Task.Wait(_, _))
My setup:
Processor: Intel(R) Core(TM) i7-8750H CPU # 2.20GHz
Debian 10 (buster)
opam version 2.1.3 installed as binary from this script
opam switch: "→ 5.0.0~beta1 ocaml-base-compiler.5.0.0~beta1 5.0.0~beta1"
After a quick read of this tutorial, I copied the parallel_matrix_multiply function and added some code in the end just to use it:
open Domainslib
let parallel_matrix_multiply pool a b =
let i_n = Array.length a in
let j_n = Array.length b.(0) in
let k_n = Array.length b in
let res = Array.make_matrix i_n j_n 0 in
Task.parallel_for pool ~start:0 ~finish:(i_n - 1) ~body:(fun i ->
for j = 0 to j_n - 1 do
for k = 0 to k_n - 1 do
res.(i).(j) <- res.(i).(j) + a.(i).(k) * b.(k).(j)
done
done);
res ;;
let pool = Task.setup_pool ~num_domains:3 () in
let a = Array.make_matrix 2 2 1 in
let b = Array.make_matrix 2 2 2 in
let c = parallel_matrix_multiply pool a b in
for i = 0 to 1 do
for j = 0 to 1 do
Printf.printf "%d " c.(i).(j)
done;
print_char '\n'
done;;
I then compile it with no errors with
ocamlfind ocamlopt -linkpkg -package domainslib parallel_for.ml
and then comes the problem: executing the generated a.out file sometimes (rarely) prints the expected output
4 4
4 4
but usually ends with the error mentioned earlier:
Fatal error: exception Stdlib.Effect.Unhandled(Domainslib__Task.Wait(_, _))
Sorry if I am making some trivial mistake, but I can't understand what is going on, especially given that the error happens inconsistently.
The parallel_matrix_multiply computation is running outside of the Domainslib scheduler, thus whenever a task yields to the scheduler, the Wait effect is unhandled and transformed into a Effect.Unhandled exception.
The solution is to run the parallel computation within Task.run:
...
let c = Task.run pool (fun () -> parallel_matrix_multiply pool a b) in
...
i'm trying to run a test command in sbt-shell of intellij ide for this -> [https://github.com/theiterators/akka-http-microservice#akka-http-microservice-example] project then it shows Akka version conflict. how to resolve it?
this is build.sbt
enablePlugins(JavaAppPackaging , GatlingPlugin)
name := "akka-http-microservice"
organization := "com.theiterators"
version := "1.0"
scalaVersion := "2.13.5"
scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8",
"-target:jvm-1.8",
"-feature",
"-language:implicitConversions",
"-language:postfixOps")
libraryDependencies ++= {
val akkaHttpV = "10.2.4"
val akkaV = "2.6.14"
val scalaTestV = "3.2.8"
val circeV = "0.13.0"
val akkaHttpCirceV = "1.36.0"
val gatlingVersion = "3.5.1"
Seq(
"com.typesafe.akka" %% "akka-actor" % akkaV,
"com.typesafe.akka" %% "akka-stream" % akkaV,
"com.typesafe.akka" %% "akka-http" % akkaHttpV,
"io.circe" %% "circe-core" % circeV,
"io.circe" %% "circe-generic" % circeV,
"de.heikoseeberger" %% "akka-http-circe" % akkaHttpCirceV,
"com.typesafe.akka" %% "akka-testkit" % akkaV,
"com.typesafe.akka" %% "akka-http-testkit" % akkaHttpV % "test",
"org.scalatest" %% "scalatest" % scalaTestV % "test",
"io.gatling.highcharts" % "gatling-charts-highcharts" % gatlingVersion % "test,it",
"io.gatling" % "gatling-test-framework" % gatlingVersion % "test,it"
)
}
Revolver.settings
this is plugin.sbt
addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.8.1")
addSbtPlugin("io.gatling" % "gatling-sbt" % "3.2.2")
addSbtPlugin("io.gatling" % "gatling-sbt" % "MANUALLY_REPLACE_WITH_LATEST_VERSION")
this is service specification
import akka.event.NoLogging
import akka.http.scaladsl.model.ContentTypes._
import akka.http.scaladsl.model.{HttpRequest, HttpResponse}
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.testkit.ScalatestRouteTest
import akka.stream.scaladsl.Flow
import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should.Matchers
class ServiceSpec extends AsyncFlatSpec with Matchers with ScalatestRouteTest with Service with Protocols {
override def testConfigSource = "akka.loglevel = WARNING"
override def config = testConfig
override val logger = NoLogging
val ip1Info = IpInfo("8.8.8.8", Option("United States"), Option("Mountain View"), Option(37.386), Option(-122.0838))
val ip2Info = IpInfo("8.8.4.4", Option("United States"), None, Option(38.0), Option(-97.0))
val ipPairSummary = IpPairSummary(ip1Info, ip2Info)
override lazy val ipApiConnectionFlow = Flow[HttpRequest].map { request =>
if (request.uri.toString().endsWith(ip1Info.query))
HttpResponse(status = OK, entity = marshal(ip1Info))
else if(request.uri.toString().endsWith(ip2Info.query))
HttpResponse(status = OK, entity = marshal(ip2Info))
else
HttpResponse(status = BadRequest, entity = marshal("Bad ip format"))
}
"Service" should "respond to single IP query" in {
Get(s"/ip/${ip1Info.query}") ~> routes ~> check {
status shouldBe OK
contentType shouldBe `application/json`
responseAs[IpInfo] shouldBe ip1Info
}
Get(s"/ip/${ip2Info.query}") ~> routes ~> check {
status shouldBe OK
contentType shouldBe `application/json`
responseAs[IpInfo] shouldBe ip2Info
}
}
it should "respond to IP pair query" in {
Post(s"/ip", IpPairSummaryRequest(ip1Info.query, ip2Info.query)) ~> routes ~> check {
status shouldBe OK
contentType shouldBe `application/json`
responseAs[IpPairSummary] shouldBe ipPairSummary
}
}
it should "respond with bad request on incorrect IP format" in {
Get("/ip/asdfg") ~> routes ~> check {
status shouldBe BadRequest
responseAs[String].length should be > 0
}
Post(s"/ip", IpPairSummaryRequest(ip1Info.query, "asdfg")) ~> routes ~> check {
status shouldBe BadRequest
responseAs[String].length should be > 0
}
Post(s"/ip", IpPairSummaryRequest("asdfg", ip1Info.query)) ~> routes ~> check {
status shouldBe BadRequest
responseAs[String].length should be > 0
}
}
}
Error given below--
[info] ServiceSpec *** ABORTED ***
[info] java.lang.IllegalStateException: You are using version 2.6.14 of Akka, but it appears you (perhaps indirectly) also depend on older versions of related artifacts. You can solve this by adding an explicit dependency on version 2.6.14 of the [akka-slf4j] artifacts to your project. Here's a complete collection of detected artifacts: (2.6.11, [akka-slf4j]), (2.6.14, [akka-actor, akka-protobuf-v3, akka-stream, akka-testkit]). See also: https://doc.akka.io/docs/akka/current/common/binary-compatibility-rules.html#mixed-versioning-is-not-allowed
[info] at akka.util.ManifestInfo.checkSameVersion(ManifestInfo.scala:184)
[info] at akka.util.ManifestInfo.checkSameVersion(ManifestInfo.scala:162)
[info] at akka.actor.ActorSystemImpl.liftedTree2$1(ActorSystem.scala:1033)
[info] at akka.actor.ActorSystemImpl._start$lzycompute(ActorSystem.scala:1022)
[info] at akka.actor.ActorSystemImpl._start(ActorSystem.scala:1022)
[info] at akka.actor.ActorSystemImpl.start(ActorSystem.scala:1045)
[info] at akka.actor.ActorSystem$.apply(ActorSystem.scala:272)
[info] at akka.actor.ActorSystem$.apply(ActorSystem.scala:316)
[info] at akka.actor.ActorSystem$.apply(ActorSystem.scala:290)
[info] at akka.http.scaladsl.testkit.RouteTest.createActorSystem(RouteTest.scala:36)
[info] ...
[info] Run completed in 1 second, 568 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 0, aborted 1
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] *** 1 SUITE ABORTED ***
[error] Error during tests:
[error] ServiceSpec
[error] (Test / test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 8 s, completed 22 Apr. 2021, 3:14:59 pm
Adding
"com.typesafe.akka" %% "akka-slf4j" % akkaV
to the Seq in your libraryDependencies block should resolve this. In general in Akka it's best to not rely on transitive dependencies.
Ive got a file containing several thousand lines like this.
Mr|David|Smith|david.smith#gmail.com
Mrs|Teri|Smith|teri.smith#gmail.com
...
I want to read the file emitting each line downstream but in a throttled manner ie. 1 per/sec.
I cannot quite figure out how to get the throttling working in the flow.
flow1 (below) outputs the first line after 1 sec and then terminates.
flow2 (below) waits 1 sec then outputs the whole file.
val source: Source[ByteString, Future[IOResult]] = FileIO.fromPath(file)
val flow1 = Flow[ByteString].
via(Framing.delimiter(ByteString(System.lineSeparator),10000)).
throttle(1, 1.second, 1, ThrottleMode.shaping).
map(bs => bs.utf8String)
val flow2 = Flow[ByteString].
throttle(1, 1.second, 1, ThrottleMode.shaping).
via(Framing.delimiter(ByteString(System.lineSeparator), 10000)).
map(bs => bs.utf8String)
val sink = Sink.foreach(println)
val res = source.via(flow2).to(sink).run().onComplete(_ => system.terminate())
I couldn't glean any solution from studying the docs.
Would greatly appreciate any pointers.
Use runWith, instead of to, with flow1:
val source: Source[ByteString, Future[IOResult]] = FileIO.fromPath(file)
val flow1 =
Flow[ByteString]
.via(Framing.delimiter(ByteString(System.lineSeparator), 10000))
.throttle(1, 1.second, 1, ThrottleMode.shaping)
.map(bs => bs.utf8String)
val sink = Sink.foreach(println)
source.via(flow1).runWith(sink).onComplete(_ => system.terminate())
to returns the materialized value of the Source (i.e., the source.via(flow1)), so you're terminating the actor system when the "left-hand side" of the stream is completed. What you want to do is to shut down the system when the materialized value of the Sink is completed. Using runWith returns the materialized value of the Sink parameter and is equivalent to:
source
.via(flow1)
.toMat(sink)(Keep.right)
.run()
.onComplete(_ => system.terminate())
A homework assignment for a computer networking software dev class, the prof has us building a port scanner for ports 1-1024 to be run against the local host. The point of the exercise is to demonstrate task level parallelism using actors. The prof provided code that scans each port in sequence. We are to create a version that does this in parallel, with an actor for each processor or hyper thread available to the system. The goal is to get the time to complete a full scan of all ports 1-1024 and compare the results of a parallel scan against the results of a serial scan. Here's my code for the parallel scan:
import java.net.Socket
import scala.actors._
import Actor._
import scala.collection.mutable.ArrayBuffer
object LowPortScanner {
var lastPort = 0
var openPorts = ArrayBuffer[Int]()
var longestRunTime = 00.00
var results = List[Tuple3[Int, Range, Double]]()
val host = "localhost"
val numProcs = 1 to Runtime.getRuntime().availableProcessors()
val portsPerProc = 1024 / numProcs.size
val caller = self
def main(args: Array[String]): Unit = {
//spawn an actor for each processor that scans a given port range
numProcs.foreach { proc =>
actor {
val portRange: Range = (lastPort + 1) to (lastPort + portsPerProc)
lastPort = lastPort + portsPerProc
caller ! scan(proc, portRange)
}
}
//catch results from the processor actors above
def act {
loop {
reactWithin(100) {
//update the list of results returned from scan
case scanResult: Tuple3[Int, Range, Double] =>
results = results ::: List(scanResult)
//check if all results have been returned for each actor
case TIMEOUT =>
if (results.size == numProcs.size) wrapUp
case _ =>
println("got back something weird from one of the port scan actors!")
wrapUp
}
}
}
//Attempt to open a socket on each port in the given range
//returns a Tuple3[procID: Int, ports: Range, time: Double
def scan(proc: Int, ports: Range) {
val startTime = System.nanoTime()
ports.foreach { n =>
try {
println("Processor " + proc + "is checking port " + n)
val socket = new Socket(host, n)
//println("Found open port: " + n)
openPorts += n
socket.close
} catch {
case e: Exception =>
//println("While scanning port " + n + " caught Exception: " + e)
}
}
(proc, ports, startTime - System.nanoTime())
}
//output results and kill the main actor
def wrapUp {
println("These are the open ports in the range 1-1024:")
openPorts.foreach { port => println(port) }
results.foreach { result => if (result._3 > longestRunTime) { longestRunTime = result._3} }
println("Time to scan ports 1 through 1024 is: %3.3f".format(longestRunTime / 1000))
caller ! exit
}
}
}
I have a quad core i7, so my numProcs = 8. On this hardware platform, each proc actor should scan 128 ports (1024/8 = 128). My intention is for the proc1 actor scan 0 - 128, proc2 should scan 129-256, etc... However, this isn't what's happening. Some of the actors end up working on the same range as other actors. The output sample below illustrates the issue:
Processor 2 is checking port 1
Processor 7 is checking port 385
Processor 1 is checking port 1
Processor 5 is checking port 1
Processor 4 is checking port 1
Processor 8 is checking port 129
Processor 3 is checking port 1
Processor 6 is checking port 257
Processor 1 is checking port 2
Processor 5 is checking port 2
Processor 1 is checking port 3
Processor 3 is checking port 2
Processor 5 is checking port 3
Processor 1 is checking port 4
EDIT
Final "working" code:
import java.net.Socket
import scala.actors._
import Actor._
import scala.collection.mutable.ArrayBuffer
object LowPortScanner {
var lastPort = 0
var openPorts = ArrayBuffer[Int]()
var longestRunTime = 00.00
var results = List[Tuple3[Int, Range, Double]]()
val host = "localhost"
val numProcs = 1 to Runtime.getRuntime().availableProcessors()
val portsPerProc = 1024 / numProcs.size
val caller = self
val procPortRanges = numProcs.foldLeft(List[Tuple2[Int, Range]]()) { (portRanges, proc) =>
val tuple2 = (proc.toInt, (lastPort + 1) to (lastPort + portsPerProc))
lastPort += portsPerProc
tuple2 :: portRanges
}
def main(args: Array[String]): Unit = {
//spawn an actor for each processor that scans a given port range
procPortRanges.foreach { proc =>
actor {
caller ! scan(proc._1, proc._2)
}
}
//catch results from the processor actors above
def act {
loop {
reactWithin(100) {
//update the list of results returned from scan
case scanResult: Tuple3[Int, Range, Double] =>
results = results ::: List(scanResult)
//check if results have been returned for each actor
case TIMEOUT =>
if (results.size == numProcs.size) wrapUp
case _ =>
println("got back something weird from one of the port scan actors!")
wrapUp
}
}
}
//Attempt to open a socket on each port in the given range
//returns a Tuple3[procID: Int, ports: Range, time: Double
def scan(proc: Int, ports: Range) {
val startTime = System.nanoTime()
ports.foreach { n =>
try {
println("Processor " + proc + "is checking port " + n)
val socket = new Socket(host, n)
//println("Found open port: " + n)
openPorts += n
socket.close
} catch {
case e: Exception =>
//println("While scanning port " + n + " caught Exception: " + e)
}
}
(proc, ports, startTime - System.nanoTime())
}
//output results and kill the main actor
def wrapUp {
println("These are the open ports in the range 1-1024:")
openPorts.foreach { port => println(port) }
results.foreach { result => if (result._3 > longestRunTime) { longestRunTime = result._3} }
println("Time to scan ports 1 through 1024 is: %3.3f".format(longestRunTime / 1000))
caller ! exit
}
}
}
On this hardware platform, each proc actor should scan 128 ports (1024/8 = 128).
Except you have
val portsPerProc = numProcs.size / 1024
and 8/1024 is 0. Note that you also have an off-by-one error which causes every actor to scan 1 more port than portsPerProc, it should scan either lastPort to (lastPort + portsPerProc) - 1 or (lastPort + 1) to (lastPort + portsPerProc).
For the future, if you have a different question, you should ask it separately :) But here you have a very obvious race condition: all actors are trying to execute
val portRange: Range = (lastPort + 1) to (lastPort + portsPerProc)
lastPort = lastPort + portsPerProc
concurrently. Think what happens when (for example) actors 1 and 2 execute first line before any actor gets to the second one.
Trying to create a script that will send a 'sh run | b interface' to a Cisco switch. Write the output to an array. Split that array with a vbcr so each line of the config is in a sep elemant of the array.
I have tried to skin the cat many ways and still I am struggling.
Logic in English:
Send command to Cisco device
Capture the output to an array
define expected lines 'This are lines that are required under each 'interface' of the switch
Match the 'interface' name and corresponding number and write it to a file.
Check under that interface for the specific lines in the expected
If it finds it, write the line & ", YES"
If it does not find it, write the line & ", NO"
Keep doing this until you do not find any more '^interface\s[FG][a-z].+'
Output should look like this:
Interface GigabitEthernet 0/2
spanning-tree portfast, YES
This is the sample code that is failing:
'These are the expected line (not being compared in the script below but is my intention to have it compare the matched elements)
Dim vExpectedINT(4)
vExpectedINT(0) = "spanning-tree portfast"
vExpectedINT(1) = "switchport access vlan 17"
vExpectedINT(2) = "switchport mode access"
vExpectedINT(3) = "ip mtu 1400"
'objStream.Write "######################################################### " & vbcrlf
'objStream.Write "# I N T E R F A C E # " & vbcrlf
'objStream.Write "######################################################### " & vbcrlf
nCount = 0
vConfigLines = Split(strResultsINT, vbcr)
Set re = new RegExp
re.Global = False
re.IgnoreCase = True
re.Multiline = False
re.Pattern = "^interface [FG]"
' Regex Ex Definition
Set re2 = new RegExp
re2.Global = False
re2.IgnoreCase = True
re2.Multiline = False
re2.Pattern = "\sspanning-tree\sportfast"
' Regex Ex Definition
Set re3 = new RegExp
re3.Global = False
re3.IgnoreCase = True
re3.Multiline = False
re3.Pattern = "ip\smtu\s1400"
Set re4 = new RegExp
re4.Global = False
re4.IgnoreCase = True
re4.Multiline = False
re4.Pattern = "!"
' Compares the information
x = 1
Do While x <= Ubound(vConfigLines) - 1 do
MsgBox chr(34) & strLine & chr(34)
If re.Test(vConfigLines(x)) Then
' Write data to not expected section
x=x+1
do
If ! re4.Test(vConfigLines(x)) Then
MsgBox vConfigLines(x)
'objStream.Write vConfigLines(x) & vbcr
elseif re2.Test(vConfigLines(x)) Then
MsgBox vConfigLines(x)
elseif re3.Test(vConfigLines(x)) Then
MsgBox vConfigLines(x)
else
exit do
end if
x=x+1
loop
end IF
End If
Loop
This is a sample of the vConfigLines output:
There could be 48+ port per switch.
interface FastEthernet1/0/1
switchport access vlan 127
switchport mode access
switchport voice vlan 210
srr-queue bandwidth share 10 10 60 20
srr-queue bandwidth shape 0 3 0 0
priority-queue out
mls qos trust cos
auto qos voip trust
spanning-tree portfast
!
interface FastEthernet1/0/2
switchport access vlan 127
switchport mode access
switchport voice vlan 210
srr-queue bandwidth share 10 10 60 20
srr-queue bandwidth shape 0 3 0 0
priority-queue out
mls qos trust cos
auto qos voip trust
spanning-tree portfast
!
interface FastEthernet1/0/3
switchport access vlan 127
switchport mode access
switchport voice vlan 210
srr-queue bandwidth share 10 10 60 20
srr-queue bandwidth shape 0 3 0 0
priority-queue out
mls qos trust cos
auto qos voip trust
spanning-tree portfast
When facing a difficult and complex task, just follow these rules:
Divide the task in independently solvable subproblems
getting the info from Cisco
processing the resulting file
gather interesting info
output
Concentrate on the difficult subtask(s)
processing the resulting file
Solve a simplified but generalized version of (each) subtask using handmade data
for easy testing
You have items and are interested in whether they (don't) have given properties
Data to play with:
Item 0 (both props)
prop_a
prop_b
!
Item 1 (just b)
prop_b
!
Item 2 (a only)
prop_a
!
Item 3 (none)
!
Item 4 (irrelevant prop)
prop_c
!
Item 5 (Richy)
prop_c
prop_b
prop_a
!
Item 6 (Junky)
junk
prop_b
whatever
!
#Item 7 (Nasty)
# prop_a_like_but_not_prop_a
# prop_b
#!
Keep it simple
don't do more than absolutely necessary
don't use variables/components you can do without
So let's start:
You have to deal with a text file (lines). So don't do more than
Dim tsIn : Set tsIn = goFS.OpenTextFile("..\data\TheProblem.txt")
Dim sLine
Do Until tsIn.AtEndOfStream
sLine = Trim(tsIn.ReadLine())
If "" <> sLine Then
End If
Loop
tsIn.Close
90 % of the code using Split on .ReadAll is just fat. Yes, it's Do Until tsIn.AtEndOfStream and not Do While tsIn.AtEndOfStream = False. No Set tsIn = Nothing,
please.
The data is organized in blocks (Item n ... !), so make sure you
recognize the parts and know what to do when finding them:
Dim tsIn : Set tsIn = goFS.OpenTextFile("..\data\TheProblem.txt")
Dim sItem : sItem = "Item"
Dim sEnd : sEnd = "!"
Dim sLine
Do Until tsIn.AtEndOfStream
sLine = Trim(tsIn.ReadLine())
If "" <> sLine Then
Select Case True
Case 1 = Instr(sLine, sItem)
WScript.Echo "Begin, note item (name)"
Case 1 = Instr(sLine, sEnd)
WScript.Echo "End, output info"
WScript.Echo "----------"
Case Else
WScript.Echo "Middle, gather info"
End Select
End If
Loop
tsIn.Close
output:
Begin, note item (name)
Middle, gather info
Middle, gather info
End, output info
----------
Begin, note item (name)
Middle, gather info
End, output info
----------
...
For each item the output should be:
name, property, yes|no
The easiest way to do that is
WScript.Echo Join(aData, ", ")
Joining beats concatenation, especially if you want to set/manipulate the
parts independently and/or to pre-set some of them in the beginning.
Dim aData : aData = Array( _
Array( "Item?", "prop_a", "NO") _
, Array( "Item?", "prop_b", "NO") _
)
Dim sLine, aTmp, nIdx
Do Until tsIn.AtEndOfStream
sLine = Trim(tsIn.ReadLine())
If "" <> sLine Then
Select Case True
Case 1 = Instr(sLine, sItem)
aTmp = aData
For nIdx = 0 To UBound(aTmp)
aTmp(nIdx)(0) = sLine
Next
Case 1 = Instr(sLine, sEnd)
For nIdx = 0 To UBound(aTmp)
WScript.Echo Join(aTmp(nIdx), ", ")
Next
WScript.Echo "----------"
Case Else
WScript.Echo "Middle, gather info"
End Select
End If
Loop
tsIn.Close
The output
...
Item 3 (none), prop_a, NO
Item 3 (none), prop_b, NO
...
shows that by setting sensible defaults (NO), this version of the script
deals correctly with items having none of the interesting properties.
So lets tackle the middle/Case Else part:
Case Else
For nIdx = 0 To UBound(aTmp)
If 1 = Instr(sLine, aTmp(nIdx)(1)) Then
aTmp(nIdx)(2) = "YES"
Exit For
End If
Next
output now:
Item 0 (both props), prop_a, YES
Item 0 (both props), prop_b, YES
----------
Item 1 (just b), prop_a, NO
Item 1 (just b), prop_b, YES
----------
Item 2 (a only), prop_a, YES
Item 2 (a only), prop_b, NO
----------
Item 3 (none), prop_a, NO
Item 3 (none), prop_b, NO
----------
Item 4 (irrelevant prop), prop_a, NO
Item 4 (irrelevant prop), prop_b, NO
----------
Item 5 (Richy), prop_a, YES
Item 5 (Richy), prop_b, YES
----------
Item 6 (Junky), prop_a, NO
Item 6 (Junky), prop_b, YES
----------
But what about Nasty:
#Item 7 (Nasty)
# prop_a_like_but_not_prop_a
# prop_b
#!
The simple Instr() will fail, if one property name is a prefix of
another. To prove that starting simple and add complexity later
is good strategy:
Dim sFSpec : sFSpec = "..\data\TheProblem.txt"
WScript.Echo goFS.OpenTextFile(sFSpec).ReadAll
Dim tsIn : Set tsIn = goFS.OpenTextFile(sFSpec)
Dim sItem : sItem = "Item"
Dim sEnd : sEnd = "!"
Dim aData : aData = Array( _
Array( "Item?", "prop_a", "NO") _
, Array( "Item?", "prop_b", "NO") _
)
Dim aRe : aRe = Array(New RegExp, New RegExp)
Dim nIdx
For nIdx = 0 To UBound(aRe)
aRe(nIdx).Pattern = "^" & aData(nIdx)(1) & "$"
Next
Dim sLine, aTmp
Do Until tsIn.AtEndOfStream
sLine = Trim(tsIn.ReadLine())
If "" <> sLine Then
Select Case True
Case 1 = Instr(sLine, sItem)
aTmp = aData
For nIdx = 0 To UBound(aTmp)
aTmp(nIdx)(0) = sLine
Next
Case 1 = Instr(sLine, sEnd)
For nIdx = 0 To UBound(aTmp)
WScript.Echo Join(aTmp(nIdx), ", ")
Next
WScript.Echo "----------"
Case Else
For nIdx = 0 To UBound(aTmp)
If aRe(nIdx).Test(sLine) Then
aTmp(nIdx)(2) = "YES"
Exit For
End If
Next
End Select
End If
Loop
tsIn.Close
output:
Item 0 (both props)
prop_a
prop_b
!
Item 1 (just b)
prop_b
!
Item 2 (a only)
prop_a
!
Item 3 (none)
!
Item 4 (irrelevant prop)
prop_c
!
Item 5 (Richy)
prop_c
prop_b
prop_a
!
Item 6 (Junky)
junk
prop_b
whatever
!
Item 7 (Nasty)
prop_a_like_but_not_prop_a
prop_b
!
Item 0 (both props), prop_a, YES
Item 0 (both props), prop_b, YES
----------
Item 1 (just b), prop_a, NO
Item 1 (just b), prop_b, YES
----------
Item 2 (a only), prop_a, YES
Item 2 (a only), prop_b, NO
----------
Item 3 (none), prop_a, NO
Item 3 (none), prop_b, NO
----------
Item 4 (irrelevant prop), prop_a, NO
Item 4 (irrelevant prop), prop_b, NO
----------
Item 5 (Richy), prop_a, YES
Item 5 (Richy), prop_b, YES
----------
Item 6 (Junky), prop_a, NO
Item 6 (Junky), prop_b, YES
----------
Item 7 (Nasty), prop_a, NO
Item 7 (Nasty), prop_b, YES
----------