B4J Question Cannot get jShell stdOut, it only show on the log when close the app

max123

Well-Known Member
Licensed User
Longtime User
Hi all,

I access YUICompressor java file with jShell, this library minify JS and CSS files by removing whitespaces, tabs,
end of lines and any character that not really needed and so to be smaller when load in the webpage.

I had 2 chances, or use the java library and manage it with java, or access directly the jarfile with a shell and manage it directly on B4J. I opted the second solution.

All commands worked when I just have to save files, but I cannot get to work the command where I inject the (js or css) code string
to stdIn and get back the minified string from stdOut.

The string only is returned back when I close the app, and here I can see it printed to the log, and received in stdOut event.
I tried even to manually kill the process with shell.KillProcess, but this way the string do not return back because the process is killed.

I tried experimenting directly with jar file and Windows command prompt, and this is how they works.

Omitting the -o OutputFile.js but placing in the command just a source eg. InputFile.js, it read source file and return a minified string on stdOut
Omitting both -o OutputFile.js and InputFile.js, it expects an input from stdIn and output the minified string to stdOut. (here we only have to specify a file type, see below)

Here I want to use a second option, so pass the string and get the resulting minified string.

On Windows command prompt, here is what happen:
  1. Send the command java -jar yuicompressor-2.4.8 --type js (--type js or --type css should be used so the compressor know what type of file have to manage)
  2. At this point the compressor open the stdIn pipe and undefinitely wait for input
  3. This happen until a RETURN and EOF character are received in sequence. From my search on the web I see that EOF is CTRL+Z on Windows and CTRL+D on Unix machines
  4. After that seem the process have to be killed. Here on Windows CTRL+C stop the process, but even dismiss the current input. I found that CTRL-BREAK do some tricks, in the prompt it return some log errors because the current thread stopped, but at the end, before return to the command prompt, it show the minified string that was processed.
In my demo code I placed 2 buttons, the first start the process, the second kill it.
To get a resulting string, just press the first button, here you do not see nothing in the log, at this point close the app with top-right X.
After that you will see that stdOut event is raised and the log show a resulting string.

Probably I have to send an EOF before killing the process, but I do not know how I can do it, may using Robot library ? Or just using shell.WriteToInputStream ?

I attached the small project to reproduce it, here I just pass a code string to the compressor and try to get back the resulting minified string.
In the project the yuicompressor-2.4.8.jar file is placed in DirAssets and copied to DirTemp where it is executed. This is a small file (800kb), but because upload limit, if you want to help and reproduce it you have to download it from here and place to DirAssets. https://github.com/yui/yuicompressor/releases

Please can someone help me to get it working ?

Many thanks
 

Attachments

  • ShellJarReturnBackStdOut.zip
    10.5 KB · Views: 87
Last edited:

Daestrum

Expert
Licensed User
Longtime User
I have a feeling it's to do with writing to the stream, but not being able to flush the stream, thus ensuring the eof is read by the compressor.

I did get it to work by creating a temp file and passing that to the command line, then after processing, deleting it.
B4X:
' after the code = line
    Dim outs As OutputStream = File.OpenOutput("c:/temp","compcode.txt",False)
    Dim tewr As TextWriter
    tewr.Initialize(outs)
    tewr.Write(Code)
    tewr.Flush
    tewr.Close
    outs.close
B4X:
    Args.Initialize
    Args.Add("-jar")
    Args.Add(Chr(34) & jarPath & Chr(34))
    Args.Add("--type") ' This open the pipe and wait js code on stdIn
    Args.Add("js") ' This open the pipe and wait js code on stdIn
    Args.Add("c:/temp/compcode.txt")
B4X:
    sh1.RunWithOutputEvents(-1)
    
    ' Here we run the command, but after this, if '--type js' is passed as arguments, the compressor just open
    ' a stdIn pipe and wait a code string, so it never return here a ProcessCompleted event.

    
    
    'sh1.WriteToInputStream(Code.GetBytes("UTF-8")) ' Inject the code string to stdIn

    Wait For sh1_ProcessCompleted (Success As Boolean, ExitCode As Int, StdOut As String, StdErr As String)
    Log("Ended " &  StdOut)
    File.Delete("c:/temp","compcode.txt")
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Hi @Daestrum, many thanks for fast reply and to trying it and your advices.

I already do it, I tried to save to a temp file (without capturing stdOut), then read it with File.ReadString and finally remove the temp file,
but this is so slow (550-600 ms for a 200 characters string) and for sure not the correct way to manage it.

The compressor just out the string with standard out in just a few milliseconds without save and delete a temporary file that is time consuming.

Here is a part of my library I wrote and so slow is not a right solution for me, I attached a screenshot, it should not save and delete any file, this is used to process recursively many files, not just one, I have to get it from stdOut without involve files.

The current but very slow code:
Public Sub MinifyStringToStringAsync (CodeString As String, CodeType As String) As ResumableSub
    Dim fName As String
    If CodeType = "js" Then
        fName = "tmp.js"
    Else If CodeType = "css" Then
        fName = "tmp.css"
    Else
        Log("Wrong CodeType, should be 'js' or 'css'")
        If SubExists(mModule, mEventName & "_FileComplete") Then
            CallSubDelayed3(mModule, mEventName & "_FileComplete", False, Null)
        End If
        Return True
    End If
 
    File.WriteString(File.DirTemp, fName, CodeString)
'    Log("Original file size: " & File.Size(File.DirTemp, fName) & " Bytes")
 
    mInternalOperation = True
    MinifyFileToFileAsync (File.DirTemp, fName, File.DirTemp, fName)   ' Overwrite itself with a minified version
    Wait For FileDone (Success As Boolean, Item As MinifiedFileItem)
 
    If Success Then
        If File.Exists(File.DirTemp, fName) Then
            File.Delete(File.DirTemp, fName)
'            Log("Temp file deleted")
        End If
        If SubExists(mModule, mEventName & "_FileComplete") Then
            CallSubDelayed3(mModule, mEventName & "_FileComplete", True, Item)
        End If
    Else
        If SubExists(mModule, mEventName & "_FileComplete") Then
            CallSubDelayed3(mModule, mEventName & "_FileComplete", False, Item)
        End If
    End If
 
    Return True
End Sub

You cannot see here where I return the string back, it currently return back the MinifiedFileItem type that do not only return a minified string
but even a full report of a compression before-after and even error log if any.
B4X:
Type MinifiedFileItem (OriginalFile As String, MinifiedFile As String, OriginalSize As Int, MinifiedSize As Int, Compression As Float, MinifiedString As String, ErrorLog As String)

In my demo project, just press the first button (without touching the second) and then close the app, the output string is there printed to the log.

It expects EOF before the process is closed. I do not send it at all. But probably when the app is closed, it send EOF and because the process is killed, the minified string is received by stdOut event that works in background and not in the main thread.

And then after this I'm not sure if shell.KillProcess do the trick, may even it want CTRL+BREAK as in windows command prompt,
but because this should work on Linux and Mac too, probably I have to know the system type, this is not a problem, I can do it.

By viewing your code now I've another question, it is best to use a TextWriter to write the file instead of use File.WriteString ?
I've to optimize the speed, but always search a solution to not use files at all.

Many thanks
 

Attachments

  • Screenshot 2024-05-15 162011.png
    Screenshot 2024-05-15 162011.png
    98.7 KB · Views: 64
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
I have all working but using a temporary file when just I get the minified string is very slow and a single call require 500-600 milliseconds.

I wrote a full library on this. I have exposed all YUICompressor functionalities and have follow methods:
B4X:
'Minify a JS or CSS file in asynchronus way and save it to a file.
MinifyFileToFileAsync (SourceDir, SourceFileName, DestDir, Rename) As ResumableSub
'Minify a JS or CSS file in asynchronus way and just get a minified string without save the file.
MinifyFileToStringAsync (SourceDir, SourceFileName) As ResumableSub
'Minify a JS or CSS string in asynchronus way and save it to a file.
MinifyStringToFileAsync (DestDir, DestFileName, CodeString, CodeType) As ResumableSub
'Minify a JS or CSS string in asynchronus way and just return a minified string without save any file.
MinifyStringToStringAsync (CodeString, CodeType) As ResumableSub
'Minify a JS or CSS file in synchronus blocking way and save it to a file.
MinifyFileToFile (SourceDir, SourceFileName, DestDir, Rename) As Boolean  ' Sync

'Recursively minify all JS and CSS files in asynchronus way inside a directory and all nested sub directories.
MinifyAllFilesOnFolderAsync (SourceDir, DestDir, UseMinPrefix)

'Set/Get the compressor charset used to read the input file. By default "utf8".
setCharSet(CharSet As String)
getCharSet As String
'Set/Get the compressor line break.
setLineBreak(Column As Int)
getLineBreak As Int
'Set/Get the compressor verbose output.
setVerbose(Value As Boolean)
getVerbose As Boolean
'Set JavaScript only options.
SetJavaScriptOptions(DoNotObfuscate As Boolean, PreserveSemicolons As Boolean, DisableOptimizations As Boolean)

The methods that do not involves files will be slow, even more slow that others that save files, because instead of just get the string directly passed from stdOut, here a temporary file is created and then deleted, that is very time consuming. Instead these should be faster that others that save files and not slower.

This compressor is capable to compress JS and CSS from 15% to 80%, depending on a file. These are tested and with a detailed full output report on the log.
It do not only minify, because even variable obfuscation is possible, that replace variable names with shorter ones, it even parse your code to chek for errors and warnings, so eg, variables declared but not used, syntax errors and so on.

The result is that your web pages, expecilally if import a lots of external linked JS or CSS files, will end up to be faster to load, faster to interact, with less overhead and with better performances, better cashed from browser, and this even save space on the disk and on http requests.

I even still work on a Sub to minify HTML files and use yuicompressor to minify all contents inside inline <STYLE> and <SCRIPT> tags.

Imagine being on a road in the car, if you go, but every 50 meters there is something that needs to be moved to continue, you have to get out of the car, move that thing, then get back in the car and continue your journey. But if every 50 meters you encounter yet another thing to move and then another one, and then another, so indefinitely, this becomes a problem. Imagine if instead someone comes in front of you who always moves the things that prevent you from continuing. This library does this on web pages, it removes those obstacles beforehand, not all but at least decrease significatly there, so that you will then have a clear road to go with your car, faster and without always having to stop to move some things. ;)

At this point I will accept any good solution to really get the stdOut or I will release my library with some commands so slow as is.

Thats it
 
Last edited:
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
Just trying to see if it's possible to use the jar directly (#AdditionalJar: ...), without using jShell.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Just trying to see if it's possible to use the jar directly (#AdditionalJar: ...), without using jShell.
I don't think it is an executable java file, not a library, and have not methods exposed, instead expets arguments in java command.
 
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
Currently trying to pursuade it to give me its stdin and stdout streams lol.

I can call the compressor and pass a filename, but not send source code via stream so far.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
I'm not sure to understand. To send a source code string over stdIn you just will have to omit the argument for source input, and to force to output on stdOut just omit the -o OutputFile.js. Note that '--type js' or '--type css' is needed if input or output files are omitted so the compressor know how to manage it.

The best way to test it is to use a jar file directly in command prompt.

On windows just:
java -jar yuicompressor-2.4.8.jar --type js
write or copy here some single or multiline JS code
press RETURN, CTRL+Z, CTRL+BREAK

In linux I think is the same but replacing CTRL+Z with CTRL+D.

It should return back the minified string.

So I think I will have to send CTRL+Z and CTRL+BREAK at the end to get stdOut in B4J code.

Here a yuicompressor main java file:
https://github.com/yui/yuicompresso...oo/platform/yui/compressor/YUICompressor.java

Take a look on these lines :
Java:
in = new InputStreamReader(System.in, charset);
out = new OutputStreamWriter(System.out, charset);

Usage:
                        + "\nUsage: java -jar yuicompressor-@VERSION@.jar [options] [input file]\n"
                        + "\n"
                        + "Global Options\n"
                        + "  -V, --version             Print version information\n"
                        + "  -h, --help                Displays this information\n"
                        + "  --type <js|css>           Specifies the type of the input file\n"
                        + "  --charset <charset>       Read the input file using <charset>\n"
                        + "  --line-break <column>     Insert a line break after the specified column number\n"
                        + "  -v, --verbose             Display informational messages and warnings\n"
                        + "  -p, --preservehints       Don't elide unrecognized compiler hints (e.g. \"use strict\", \"use asm\")\n"
                        + "  -m <file>                 Place a mapping of munged identifiers to originals in this file\n\n"
                        + "  -o <file>                 Place the output into <file>. Defaults to stdout.\n"
                        + "                            Multiple files can be processed using the following syntax:\n"
                        + "                            java -jar yuicompressor.jar -o '.css$:-min.css' *.css\n"
                        + "                            java -jar yuicompressor.jar -o '.js$:-min.js' *.js\n\n"

                        + "JavaScript Options\n"
                        + "  --nomunge                 Minify only, do not obfuscate\n"
                        + "  --preserve-semi           Preserve all semicolons\n"
                        + "  --disable-optimizations   Disable all micro optimizations\n\n"

                        + "If no input file is specified, it defaults to stdin. In this case, the 'type'\n"
                        + "option is required. Otherwise, the 'type' option is required only if the input\n"
                        + "file extension is neither 'js' nor 'css'.");
 
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
So, I can send strings to the process, even I can append, but how to send CTRL+Z and CTRL+BREAK key combinations ?
I have to send at least the first that is interpreted as EOF, nothing to do with Undo.
 

Attachments

  • Screenshot 2024-05-16 170121.png
    Screenshot 2024-05-16 170121.png
    65.3 KB · Views: 65
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User

And CTRL+BREAK ?

When I send 26 it do not more return back the string, even if I close the application.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
So any other help here please.....

The problem was not solved.
How to send CTRL+Z and CTRL+BREAK with shell.WriteToInputStream ?

I tried to send ASCII 26 but this do not worked.

CTRL+Z is recognized as EOF, on the command prompt it works.
 
Upvote 0
Top